I have made a game, and everything works, except when I move the camera with my mouse, everything duplicates in my view. I see a quickly alternating version alternating between my original camera view and my new camera view.
I made this code, and I have tried to make the camera update and change it instead of duplicating it, but it doesn't work. Here is my broken code so far:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Platformer</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>
// Create a scene
var scene = new THREE.Scene();
// Create a camera with increased FOV
var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
// Create a renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Create a large platform
var platformGeometry = new THREE.BoxGeometry(20, 1, 20);
var platformMaterial = new THREE.MeshBasicMaterial({ color: 0xaaaaaa });
var platform = new THREE.Mesh(platformGeometry, platformMaterial);
platform.position.y = -1;
scene.add(platform);
// Create a player
var playerGeometry = new THREE.BoxGeometry(1, 1, 1);
var playerMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var player = new THREE.Mesh(playerGeometry, playerMaterial);
scene.add(player);
// Create additional cubes
var cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
var cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
for (var i = 0; i < 5; i++) {
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(Math.random() * 18 - 9, 0.5, Math.random() * 18 - 9);
scene.add(cube);
}
// Create an AI that runs around randomly
var aiGeometry = new THREE.BoxGeometry(1, 1, 1);
var aiMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var ai = new THREE.Mesh(aiGeometry, aiMaterial);
ai.position.set(0, 0.5, -5); // Initial position
scene.add(ai);
// AI movement variables
var aiSpeed = 0.05;
var aiDirection = new THREE.Vector3(Math.random() * 2 - 1, 0, Math.random() * 2 - 1).normalize();
var aiJumpSpeed = 0.2;
var aiVelocityY = 0;
var aiOnGround = true;
// Player controls
var playerSpeed = 0.1;
var jumpSpeed = 0.2;
var velocityY = 0;
var onGround = true;
var keys = {
w: false,
a: false,
s: false,
d: false,
space: false
};
window.addEventListener('keydown', function(event) {
if (keys.hasOwnProperty(event.key)) keys[event.key] = true;
});
window.addEventListener('keyup', function(event) {
if (keys.hasOwnProperty(event.key)) keys[event.key] = false;
});
// Mouse controls
var mouse = {
isDragging: false,
previousX: 0,
previousY: 0,
deltaX: 0,
deltaY: 0
};
document.addEventListener('mousedown', function(event) {
mouse.isDragging = true;
mouse.previousX = event.clientX;
mouse.previousY = event.clientY;
});
document.addEventListener('mouseup', function() {
mouse.isDragging = false;
});
document.addEventListener('mousemove', function(event) {
if (mouse.isDragging) {
mouse.deltaX = event.clientX - mouse.previousX;
mouse.deltaY = event.clientY - mouse.previousY;
camera.rotation.y -= mouse.deltaX * 0.002;
camera.rotation.x -= mouse.deltaY * 0.002;
camera.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, camera.rotation.x)); // Prevent flipping
mouse.previousX = event.clientX;
mouse.previousY = event.clientY;
}
});
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Player movement
if (keys.w) player.position.z -= playerSpeed;
if (keys.a) player.position.x -= playerSpeed;
if (keys.s) player.position.z += playerSpeed;
if (keys.d) player.position.x += playerSpeed;
// Prevent jumping off the edge
player.position.x = Math.max(-9.5, Math.min(9.5, player.position.x));
player.position.z = Math.max(-9.5, Math.min(9.5, player.position.z));
// Player jumping
if (keys.space && onGround) {
velocityY = jumpSpeed;
onGround = false;
}
player.position.y += velocityY;
velocityY -= 0.01; // Gravity effect
if (player.position.y <= 0) {
player.position.y = 0;
onGround = true;
}
// AI random movement and jumping
ai.position.add(aiDirection.clone().multiplyScalar(aiSpeed));
if (Math.random() < 0.05 && aiOnGround) { // Increased jump frequency
aiVelocityY = aiJumpSpeed;
aiOnGround = false;
}
ai.position.y += aiVelocityY;
aiVelocityY -= 0.01; // Gravity effect
if (ai.position.y <= 0) {
ai.position.y = 0;
aiOnGround = true;
}
// Avoid getting stuck with AI
var distance = player.position.distanceTo(ai.position);
if (distance < 1) {
var pushDirection = new THREE.Vector
Vector3().subVectors(player.position, ai.position).normalize();
player.position.add(pushDirection.multiplyScalar(0.1));
}
// Position camera
camera.position.set(player.position.x, player.position.y + 1.5, player.position.z);
camera.lookAt(player.position.x + Math.sin(camera.rotation.y), player.position.y + 1.5, player.position.z - Math.cos(camera.rotation.y));
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
The problem is a conflict or duplication between the manually updated camera rotation and the view calculated by lookAt(). You directly modify camera.rotation.x and camera.rotation.y based on mouse movement. After updating the player's position, you change the camera position using camera.position.set and then call camera.lookAt. Hence the jerking/shaking. Instead of directly modifying the camera rotation via camera.rotation.x and camera.rotation.y in the mouse event, you can separate the control variables (yaw and pitch) from the camera's internal rotation. Using lookAt() based on yaw and pitch, you eliminate the oscillation between two different rotation updates.
This should helpful as well:
https://sbcode.net/threejs/follow-cam/
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
90,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const platformGeometry = new THREE.BoxGeometry(20, 1, 20);
const platformMaterial = new THREE.MeshBasicMaterial({
color: 0xaaaaaa
});
const platform = new THREE.Mesh(platformGeometry, platformMaterial);
platform.position.y = -1;
scene.add(platform);
const playerGeometry = new THREE.BoxGeometry(1, 1, 1);
const playerMaterial = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
const player = new THREE.Mesh(playerGeometry, playerMaterial);
scene.add(player);
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
});
for (let i = 0; i < 5; i++) {
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(Math.random() * 18 - 9, 0.5, Math.random() * 18 - 9);
scene.add(cube);
}
const aiGeometry = new THREE.BoxGeometry(1, 1, 1);
const aiMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
});
const ai = new THREE.Mesh(aiGeometry, aiMaterial);
ai.position.set(0, 0.5, -5);
scene.add(ai);
const playerSpeed = 0.1;
let velocityY = 0;
let onGround = true;
const keys = {
w: false,
a: false,
s: false,
d: false,
space: false
};
window.addEventListener("keydown", (event) => {
if (keys.hasOwnProperty(event.key)) keys[event.key] = true;
});
window.addEventListener("keyup", (event) => {
if (keys.hasOwnProperty(event.key)) keys[event.key] = false;
});
let yaw = 0;
let pitch = 0;
const mouse = {
isDragging: false,
previousX: 0,
previousY: 0,
};
document.addEventListener("mousedown", (event) => {
mouse.isDragging = true;
mouse.previousX = event.clientX;
mouse.previousY = event.clientY;
});
document.addEventListener("mouseup", () => {
mouse.isDragging = false;
});
document.addEventListener("mousemove", (event) => {
if (mouse.isDragging) {
const deltaX = event.clientX - mouse.previousX;
const deltaY = event.clientY - mouse.previousY;
yaw -= deltaX * 0.002;
pitch -= deltaY * 0.002;
pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch));
mouse.previousX = event.clientX;
mouse.previousY = event.clientY;
}
});
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
function animate() {
requestAnimationFrame(animate);
if (keys.w) player.position.z -= playerSpeed;
if (keys.a) player.position.x -= playerSpeed;
if (keys.s) player.position.z += playerSpeed;
if (keys.d) player.position.x += playerSpeed;
player.position.x = Math.max(-9.5, Math.min(9.5, player.position.x));
player.position.z = Math.max(-9.5, Math.min(9.5, player.position.z));
if (keys.space && onGround) {
velocityY = 0.2;
onGround = false;
}
player.position.y += velocityY;
velocityY -= 0.01;
if (player.position.y <= 0) {
player.position.y = 0;
onGround = true;
}
camera.position.set(player.position.x, player.position.y + 1.5, player.position.z);
const target = new THREE.Vector3();
target.x = player.position.x + Math.sin(yaw) * Math.cos(pitch);
target.y = player.position.y + 1.5 + Math.sin(pitch);
target.z = player.position.z - Math.cos(yaw) * Math.cos(pitch);
camera.lookAt(target);
renderer.render(scene, camera);
}
animate();
</script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With