Reputation: 59
I am making a 3d Three Js game, but I have run into a problem with my camera controls. When I look the starting direction that you look, the camera controls are all right. But if I look backward, the up and down camera controls inverse, and if I look to one of the sides, then those controls make the whole thing go sideways. I think the problem is like an old time joystick, where if you stand on the side of the claw machine or whatever it is, than forward would not be forward, it would be to the side. Here is my code so far: (Sorry, mouse lock doesn't work with stack overflow, use something like https://www.w3schools.com/html/tryit.asp?filename=tryhtml_default)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First Person Game</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
let scene, camera, renderer;
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
let velocity = new THREE.Vector3();
let direction = new THREE.Vector3();
let canJump = false;
let prevTime = performance.now();
let cubes = [], cubeVelocities = [];
let heldCube = null;
const speed = 150.0;
const jumpVelocity = 75.0;
const gravity = 9.8 * 50.0;
const pickUpDistance = 2.0;
const originalCubeScale = 1;
const heldCubeScale = 0.5;
const friction = 0.1; // Friction coefficient
let pitch = 0, yaw = 0;
init();
animate();
function init() {
// Scene and camera setup
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 1.6;
// Renderer setup
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Ground
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
// Tangible cubes
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
for (let i = 0; i < 3; i++) {
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(Math.random() * 30 - 15, 1.6, Math.random() * 30 - 15);
cubes.push(cube);
cubeVelocities.push(new THREE.Vector3());
scene.add(cube);
}
// Event listeners for movement
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// Lock the mouse
document.body.addEventListener('click', () => {
document.body.requestPointerLock();
});
document.addEventListener('mousemove', onMouseMove);
window.addEventListener('resize', onWindowResize);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onKeyDown(event) {
switch (event.code) {
case 'KeyW': moveForward = true; break;
case 'KeyS': moveBackward = true; break;
case 'KeyA': moveLeft = true; break;
case 'KeyD': moveRight = true; break;
case 'Space':
if (canJump) {
velocity.y = jumpVelocity;
canJump = false;
}
break;
case 'KeyF':
pickOrThrowCube();
break;
}
}
function onKeyUp(event) {
switch (event.code) {
case 'KeyW': moveForward = false; break;
case 'KeyS': moveBackward = false; break;
case 'KeyA': moveLeft = false; break;
case 'KeyD': moveRight = false; break;
}
}
function onMouseMove(event) {
if (document.pointerLockElement) {
const sensitivity = 0.002;
yaw -= event.movementX * sensitivity;
pitch -= event.movementY * sensitivity;
pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch));
camera.rotation.y = yaw;
camera.rotation.x = pitch;
camera.rotation.z = 0; // Prevent rolling sideways
}
}
function pickOrThrowCube() {
if (heldCube) {
// Throw the held cube
const throwVelocity = new THREE.Vector3();
camera.getWorldDirection(throwVelocity);
throwVelocity.multiplyScalar(200); // Throw strength
heldCube.position.add(throwVelocity.multiplyScalar(0.02));
cubeVelocities[cubes.indexOf(heldCube)].copy(throwVelocity);
heldCube.scale.set(originalCubeScale, originalCubeScale, originalCubeScale); // Reset cube scale
heldCube = null;
} else {
// Pick up a cube if close enough and looking at it
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
const intersects = raycaster.intersectObjects(cubes);
if (intersects.length > 0) {
const intersect = intersects[0];
if (intersect.distance < pickUpDistance) {
heldCube = intersect.object;
cubeVelocities[cubes.indexOf(heldCube)].set(0, 0, 0); // Stop the cube's movement
heldCube.scale.set(heldCubeScale, heldCubeScale, heldCubeScale); // Make the cube smaller
heldCube.position.copy(camera.position).add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1.5));
}
}
}
}
function animate() {
requestAnimationFrame(animate);
// Update time
const time = performance.now();
const delta = (time - prevTime) / 1000;
// Movement logic
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize();
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= gravity * delta;
if (moveForward || moveBackward || moveLeft || moveRight) {
const frontDirection = new THREE.Vector3();
camera.getWorldDirection(frontDirection);
const rightDirection = new THREE.Vector3();
rightDirection.crossVectors(camera.up, frontDirection).normalize();
frontDirection.multiplyScalar(direction.z * speed * delta);
rightDirection.multiplyScalar(direction.x * speed * delta);
velocity.add(frontDirection).add(rightDirection);
}
camera.position.addScaledVector(velocity, delta);
// Collision detection with barriers
camera.position.x = Math.max(-24, Math.min(24, camera.position.x));
camera.position.z = Math.max(-24, Math.min(24, camera.position.z));
if (camera.position.y < 1.6) {
velocity.y = 0;
camera.position.y = 1.6;
canJump = true;
}
// Update held cube position
if (heldCube) {
heldCube.position.copy(camera.position).add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1.5));
}
// Cube collision logic
for (let i = 0; i < cubes.length; i++) {
if (cubes[i] !== heldCube) {
// Apply friction
cubeVelocities[i].x *= (1 - friction);
cubeVelocities[i].z *= (1 - friction);
// Apply gravity to cubes
cubeVelocities[i].y -= gravity * delta;
cubes[i].position.addScaledVector(cubeVelocities[i], delta);
// Cube collision with ground: stop bouncing
if (cubes[i].position.y < 0.5) {
cubes[i].position.y = 0.5;
cubeVelocities[i].y = 0; // Stop upward velocity
}
// Simple cube interaction logic for pushing
for (let j = 0; j < cubes.length; j++) {
if (i !== j) {
const distance = cubes[i].position.distanceTo(cubes[j].position);
if (distance < 1.5) { // If cubes are close enough
const collisionDirection = new THREE.Vector3().subVectors(cubes[i].position, cubes[j].position).normalize();
const relativeVelocity = new THREE.Vector3().subVectors(cubeVelocities[i], cubeVelocities[j]);
if (relativeVelocity.dot(collisionDirection) < 0) { // Only push if moving toward each other
const pushAmount = 0.02; // Adjust to control push force
cubeVelocities[i].add(collisionDirection.clone().multiplyScalar(pushAmount));
cubeVelocities[j].sub(collisionDirection.clone().multiplyScalar(pushAmount));
}
}
}
}
}
}
renderer.render(scene, camera);
prevTime = time;
}
</script>
</body>
</html>
Upvotes: 0
Views: 57
Reputation: 376
you need to add
camera.rotation.order = 'YXZ' // default 'XYZ'
in your init()
function
reason: rotation matrix multiplication order
you can see detalised info here: https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
and also here: https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions
also if you add this:
const axesHelper = new THREE.AxesHelper( 15 )
//axesHelper.renderOrder = 1;
axesHelper.material.depthTest = false;
scene.add( axesHelper )
in init()
it will show world axes (and will be always on top of all objects) i needed that while cheking your code so maybe you also will need it in future
Upvotes: 1