Renderer
import { WebGLRenderer } from 'three'
class Renderer {
constructor(element) {
this.renderer = new WebGLRenderer({ antialias: true, alpha: true })
this.setRenderer(element)
}
setRenderer(element) {
this.renderer.setPixelRatio(1)
this.renderer.shadowMap.enabled = true
this.renderer.setSize(element.clientWidth || window.innerWidth, element.clientHeight || window.innerHeight)
element.appendChild(this.renderer.domElement)
}
get domElement() {
return this.renderer.domElement
}
get rendererElement() {
return this.renderer
}
}
export { Renderer }
Camera
import { CinematicCamera } from 'three/examples/jsm/cameras/CinematicCamera'
class Camera {
constructor() {
this.camera = new CinematicCamera(1000, 1, 1, 2000)
this.setCamera()
}
setCamera() {
this.camera.position.set(100, 100, 100)
}
get cameraElement() {
return this.camera
}
}
export { Camera }
I want to make a square, but it stretches and shrinks according to the aspect ratio.
How can I fix the proportions and only change the canvas size?
I've been advised to fix the size of the container, but I don't know how to do it.
change renderer
I made it responsive like this. The problem is that the click area doesn't work.
// this.renderer.setSize(element.clientWidth|| 1000, element.clientHeigh|| 1000)
if(window.innerWidth >=1024){
this.renderer.setSize(1000 || 1000, 1000 ||1000)
}else if(window.innerWidth >=768){
this.renderer.setSize(500 || 500, 500 || 500)
}else if(window.innerWidth <=767){
this.renderer.setSize(300 || 300, 300|| 300)
}
mouse click on object
The mouse.x value inside the if statement doesn't work. But the console prints a message. The commented out mouse.x works just fine, but getting inside the if statement is a problem.
let selectedObject = null
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
function onclick(event) {
// mouse.x = (event.clientX / 1000) * 2 - 1
// mouse.y = -(event.clientY / 1000) * 2 + 1
if(window.innerWidth >=1024){
this.mouse.x = (event.clientX / 1000) * 2 - 1
}else if( window.innerWidth >=768){
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1
}else{
this. mouse.x = (event.clientX / 400) * 2 - 1
}
if(window.innerWidth >=1024){
this.mouse.y = (event.clientY / window.innerHeight) * 2 + 1
}else if(window.innerWidth >=768){
this.mouse.y = (event.clientY / 500) * 2 + 1
}else{
this.mouse.y = (event.clientY / 300) * 2 + 1
}
I have recently been asked to learn about Two.js, and specificaly to modify this example. I have tried for few day to alter this code ..
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
var stage = new Two.Group();
for (var i = 0; i < 100; i++) {
var x = Math.random() * two.width * 2 - two.width;
var y = Math.random() * two.height * 2 - two.height;
var size = 50;
var shape = new Two.Rectangle(x, y, size, size);
shape.rotation = Math.random() * Math.PI * 2;
shape.noStroke().fill = '#ccc';
stage.add(shape);
}
shape.fill = 'red';
shape.position.set(two.width / 2, two.height / 2);
two.add(stage);
addZUI();
function addZUI() {
var domElement = two.renderer.domElement;
var zui = new Two.ZUI(stage);
var mouse = new Two.Vector();
var touches = {};
var distance = 0;
var dragging = false;
zui.addLimits(0.06, 8);
domElement.addEventListener('mousedown', mousedown, false);
domElement.addEventListener('mousewheel', mousewheel, false);
domElement.addEventListener('wheel', mousewheel, false);
domElement.addEventListener('touchstart', touchstart, false);
domElement.addEventListener('touchmove', touchmove, false);
domElement.addEventListener('touchend', touchend, false);
domElement.addEventListener('touchcancel', touchend, false);
function mousedown(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
var rect = shape.getBoundingClientRect();
dragging = mouse.x > rect.left && mouse.x < rect.right
&& mouse.y > rect.top && mouse.y < rect.bottom;
window.addEventListener('mousemove', mousemove, false);
window.addEventListener('mouseup', mouseup, false);
}
function mousemove(e) {
var dx = e.clientX - mouse.x;
var dy = e.clientY - mouse.y;
if (dragging) {
shape.position.x += dx / zui.scale;
shape.position.y += dy / zui.scale;
} else {
zui.translateSurface(dx, dy);
}
mouse.set(e.clientX, e.clientY);
}
function mouseup(e) {
window.removeEventListener('mousemove', mousemove, false);
window.removeEventListener('mouseup', mouseup, false);
}
function mousewheel(e) {
var dy = (e.wheelDeltaY || - e.deltaY) / 1000;
zui.zoomBy(dy, e.clientX, e.clientY);
}
function touchstart(e) {
switch (e.touches.length) {
case 2:
pinchstart(e);
break;
case 1:
panstart(e)
break;
}
}
function touchmove(e) {
switch (e.touches.length) {
case 2:
pinchmove(e);
break;
case 1:
panmove(e)
break;
}
}
function touchend(e) {
touches = {};
var touch = e.touches[ 0 ];
if (touch) { // Pass through for panning after pinching
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
}
function panstart(e) {
var touch = e.touches[ 0 ];
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
function panmove(e) {
var touch = e.touches[ 0 ];
var dx = touch.clientX - mouse.x;
var dy = touch.clientY - mouse.y;
zui.translateSurface(dx, dy);
mouse.set(touch.clientX, touch.clientY);
}
function pinchstart(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
distance = Math.sqrt(dx * dx + dy * dy);
mouse.x = dx / 2 + a.clientX;
mouse.y = dy / 2 + a.clientY;
}
function pinchmove(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
var d = Math.sqrt(dx * dx + dy * dy);
var delta = d - distance;
zui.zoomBy(delta / 250, mouse.x, mouse.y);
distance = d;
}
}
.. in order to make it work this way :
Zoom : on wheel, no change.
Dragging : only if mousedown in space between shapes.
Shape click : execute a function.
I tried many things base on answers found here and doc, but nothing work at all, an some pieces of code could be seen as a damn blasphemy.
I don't ask for a complete working code (if someone can provide it I will study it gladly), but any explanations or hints will be greatly appreciate.
I am making a 2d multiplayer sword game using Node JS and Socket io.
I have the backend working but in the front end I cant get the sword to be attached on the player.
I want it to be like
Sword is attached to player
But the problem is when i move mouse the sword changes and goes outside of the player.
Sword is not attached to player. So If you are wondering how my game works its basically the Smiley(player) always is pointing at the mouse (yes it may be upside down) and you can move player with arrow keys and use the sword to kill other players.
Here is my code that draws the player and sword.
function drawImageLookat(img, x, y, lookx, looky){
ctx.setTransform(1, 0, 0, 1, x, y); // set scale and origin
ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}
const drawPlayer = (player) => {
const img = new Image()
const img2 = new Image()
img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}
};
socket.on('state', (gameState) => {
ctx.clearRect(0,0, 1200, 700)
for (let player in gameState.players) {
drawPlayer(gameState.players[player])
}
})
Basically this "state" is emitted 60 times a second with the gamestate. In the gamestate we have players object which contains stuff like LookX(Mouse X position),LookY(Mouse Y position), and X and Y of player. This code below is in server side.
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
//More stuff below here which is skipped
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
Also in client side, here is how I am getting mouse position
//mouse stuf
var pos = 0;
document.addEventListener("mousemove", (e) => {
pos = getMousePos(e)
})
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//send 2 server
setInterval(() => {
socket.emit('mouseAngle', pos);
socket.emit('playerMovement', playerMovement);
}, 1000 / 60);
So basically All I want is that when I spin the mouse, the Player should always be pointing at the mouse and the Sword is attached to the side of the Player and spins with it....
Here is my full code if you need it...
//SERVER.JS
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)
var htmlPath = path.join(__dirname, 'client');
app.use(express.static(htmlPath));
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
socket.on('playerMovement', (playerMovement) => {
const player = gameState.players[socket.id]
const canvasWidth = 1200
const canvasHeight = 700
if (playerMovement.left && player.x > 0) {
player.x -= 4
}
if (playerMovement.right && player.x < canvasWidth - player.width) {
player.x += 4
}
if (playerMovement.up && player.y > 0) {
player.y -= 4
}
if (playerMovement.down && player.y < canvasHeight - player.height) {
player.y += 4
}
})
socket.on("mouseAngle", (pos) => {
const player = gameState.players[socket.id]
player.lookx = pos.x;
player.looky = pos.y;
})
socket.on("disconnect", () => {
delete gameState.players[socket.id]
})
})
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
http.listen(3000, () => {
console.log('listening on *:3000');
});
//Client/Index.html
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)
var htmlPath = path.join(__dirname, 'client');
app.use(express.static(htmlPath));
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
socket.on('playerMovement', (playerMovement) => {
const player = gameState.players[socket.id]
const canvasWidth = 1200
const canvasHeight = 700
if (playerMovement.left && player.x > 0) {
player.x -= 4
}
if (playerMovement.right && player.x < canvasWidth - player.width) {
player.x += 4
}
if (playerMovement.up && player.y > 0) {
player.y -= 4
}
if (playerMovement.down && player.y < canvasHeight - player.height) {
player.y += 4
}
})
socket.on("mouseAngle", (pos) => {
const player = gameState.players[socket.id]
player.lookx = pos.x;
player.looky = pos.y;
})
socket.on("disconnect", () => {
delete gameState.players[socket.id]
})
})
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
http.listen(3000, () => {
console.log('listening on *:3000');
});
//Client/Assets/Js/script.js
var socket = io();
var canvas = document.getElementById("game");
const ctx = canvas.getContext("2d")
document.getElementById("game").width =1200;
document.getElementById("game").height = 700;
socket.emit('newPlayer');
//mouse stuff
function drawImageLookat(img, x, y, lookx, looky){
ctx.setTransform(1, 0, 0, 1, x, y); // set scale and origin
ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}
const drawPlayer = (player) => {
const img = new Image()
const img2 = new Image()
img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}
};
socket.on('state', (gameState) => {
ctx.clearRect(0,0, 1200, 700)
for (let player in gameState.players) {
drawPlayer(gameState.players[player])
}
})
//Client/assets/js/Controller.js
//v1
var canvas = document.getElementById("game")
const playerMovement = {
up: false,
down: false,
left: false,
right: false
};
const keyDownHandler = (e) => {
if (e.keyCode == 39 || e.keyCode == 68) {
playerMovement.right = true;
} else if (e.keyCode == 37 || e.keyCode == 65) {
playerMovement.left = true;
} else if (e.keyCode == 38 || e.keyCode == 87) {
playerMovement.up = true;
} else if (e.keyCode == 40 || e.keyCode == 83) {
playerMovement.down = true;
}
};
const keyUpHandler = (e) => {
if (e.keyCode == 39 || e.keyCode == 68) {
playerMovement.right = false;
} else if (e.keyCode == 37 || e.keyCode == 65) {
playerMovement.left = false;
} else if (e.keyCode == 38 || e.keyCode == 87) {
playerMovement.up = false;
} else if (e.keyCode == 40 || e.keyCode == 83) {
playerMovement.down = false;
}
};
document.addEventListener('keydown', keyDownHandler, false);
document.addEventListener('keyup', keyUpHandler, false);
//mouse stuf
var pos = 0;
document.addEventListener("mousemove", (e) => {
pos = getMousePos(e)
})
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//send 2 server
setInterval(() => {
socket.emit('mouseAngle', pos);
socket.emit('playerMovement', playerMovement);
}, 1000 / 60);
If you could help me thanks!
There are many ways this can be done. The standard method is to use a second transform and multiply it with the current context transform.
2D Matrix multiplication
The 2d API has two functions to enter a matrix to change the current transform.
ctx.setTransform Replaces the current transform with a new one
ctx.transform Multiplies the current transformation with the new one setting he current transform to the resulting matrix.
This lets you attach image elements in a hierarchical transformation structure. (Tree like structure)
ctx.setTransform is used to set the root transform and you use ctx.transform to attach a child that inherits the previous transform as well has getting the secondary transform.
Say you have an image that you transform as you do with
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(Math.atan2(looky - y, lookx - x));
ctx.drawImage(img,-img.width / 2, -img.height / 2);
The origin is at the center of the image
To attach another image say for example the second image is centered on the bottom right corner of the existing image, and will rotate (scale, translate, skew, mirror, etc) with the existing image.
Just create a second transform that is relative to the current transform (previous image) and use ctx.transform to do the matrix multiplication, then render the second image
ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2); // bottom right of prev image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
Save and restore the transform.
If you need to get the parent transform you will need to save and restore the 2D API state
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(Math.atan2(looky - y, lookx - x));
ctx.drawImage(img,-img.width / 2, -img.height / 2);
ctx.save();
// draw second image
ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2);
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.restore(); // get the parent transform
// draw 3rd image at top right
ctx.transform(1, 0, 0, 1, img.width / 2, -img.height / 2);
ctx.drawImage(img3, -img3.width / 2, -img3.height / 2);
The is ok for simple rendering but can be slow or inconvenient for more complex rendering.
DOMMatrix
Rather than use the built in transforms you can use DOMMatrix as it provides functions to do all the standard matrix math.
For example the previous 3 images can be done as
const ang = Math.atan2(looky - y, lookx - x);
const ax = Math.cos(ang);
const ay = Math.sin(ang);
const parentMatrix = [ax, ay, -ay, ax, x, y];
const child1Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, img.height / 2]);
const child2Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, -img.height / 2]);
// draw first image with root transform
ctx.setTransform(...parentMatrix);
ctx.drawImage(img,-img.width / 2, -img.height / 2);
// draw second image at bottom right
const mat1 = new DOMMatrix(...parentMatrix);
matrix.multiplySelf(child1Matrix);
ctx.setTransform(mat1.a, mat1.b, mat1.c, mat1.d, mat1.e, mat1.f);
ctx.drawImage(img1,-img1.width / 2, -img1.height / 2);
// draw third image at top right
const mat2 = new DOMMatrix(...parentMatrix);
matrix.multiplySelf(child2Matrix);
ctx.setTransform(mat2.a, mat2.b, mat2.c, mat2.d, mat2.e, mat2.f);
ctx.drawImage(img2,-img2.width / 2, -img2.height / 2);
I want to detect a click event on a three.js sprite here's the code:
function bindEvents(state) {
let intersected;
function onDocumentMouseDown(event) {
event.preventDefault();
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
const mouseY = - (event.clientY / window.innerHeight) * 2 + 1;
const camera = state.threeD.elements.camera;
const raycaster = state.threeD.raycaster;
raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera);
var intersects = raycaster.intersectObjects(state.clickables?state.clickables:[]);
if (intersects.length > 0) {
if (intersected) intersected.material.map = intersected.currentMap;
intersected = intersects[0].object;
intersected.currentMap = intersected.material.map;
intersected.material.map = selectCityTexture(128, '#cccccc', 'cccccc');
}
}
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('touchstart', onDocumentMouseDown, false);
}
This almost works, the mouse position is offset by some amount. Probably some calculation error, I have no idea how the above code works please help.
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
const mouseY = - (event.clientY / window.innerHeight) * 2 + 1;
these are changed with
const mouseX = (event.clientX / canvas.width) * 2 - 1;
const mouseY = - (event.clientY / canvas.height) * 2 + 1;
When I drag the mouse left or right, i'd like drag the scene, don't rotate camera.
I tried
camera.position.x = mouseX;
camera.position.y = mouseY;
but scene rotated
I tried change position in scene — scene rotated.
How to drag the scene?
You can try using (after you define your camera)
controls = new THREE.RollControls(camera);
controls.movementSpeed = 10;
controls.lookSpeed = 1;
controls.rollSpeed = 0;
controls.autoForward = false;
after including this in your html:
<script type="text/javascript" src="three.js/examples/js/controls/RollControls.js"></script>
In addition you would have to change your onWindowResize event to add
controls.handleResize();
and your render() function to add
controls.update(clock.getDelta());
and your init() function to add
clock = new THREE.Clock();
here is a file i got at github that might work
THREE.DragControls = function(_camera, _objects, _domElement) {
if (_objects instanceof THREE.Scene) {
_objects = _objects.children;
}
var _projector = new THREE.Projector();
var _mouse = new THREE.Vector3(),
_offset = new THREE.Vector3();
var _selected;
_domElement.addEventListener('mousemove', onDocumentMouseMove, false);
_domElement.addEventListener('mousedown', onDocumentMouseDown, false);
_domElement.addEventListener('mouseup', onDocumentMouseUp, false);
function onDocumentMouseMove(event) {
event.preventDefault();
_mouse.x = (event.clientX / _domElement.width) * 2 - 1;
_mouse.y = -(event.clientY / _domElement.height) * 2 + 1;
var ray = _projector.pickingRay(_mouse, _camera);
if (_selected) {
var targetPos = ray.direction.clone().multiplyScalar(_selected.distance).addSelf(ray.origin);
_selected.object.position.copy(targetPos.subSelf(_offset));
return;
}
var intersects = ray.intersectObjects(_objects);
if (intersects.length > 0) {
_domElement.style.cursor = 'pointer';
} else {
_domElement.style.cursor = 'auto';
}
}
function onDocumentMouseDown(event) {
event.preventDefault();
_mouse.x = (event.clientX / _domElement.width) * 2 - 1;
_mouse.y = -(event.clientY / _domElement.height) * 2 + 1;
var ray = _projector.pickingRay(_mouse, _camera);
var intersects = ray.intersectObjects(_objects);
if (intersects.length > 0) {
_selected = intersects[0];
_offset.copy(_selected.point).subSelf(_selected.object.position);
_domElement.style.cursor = 'move';
}
}
function onDocumentMouseUp(event) {
event.preventDefault();
if (_selected) {
_selected = null;
}
_domElement.style.cursor = 'auto';
}
}