I recently attempted to create a rotation system for a First Person Shooter game using THREE.JS.
However, I encountered a strange issue where the camera would pause momentarily before returning, especially at certain rotations. When checking the rotation value with console.log(this.rotation.y), it was approximately 1/-1 and sometimes switched directions unexpectedly.
Below is the code snippet from my game.js file:
// Importing necessary libraries
import * as engine from './three/src/Three.js'
// Importing loaders
import {GLTFLoader} from './three/examples/jsm/loaders/GLTFLoader.js'
import {OBJLoader} from './three/examples/jsm/loaders/OBJLoader.js'
import {MTLLoader} from './three/examples/jsm/loaders/MTLLoader.js'
// Importing Player class
import {Player} from './src/player.js'
// Creating a new player instance
const player = new Player(0.1)
// Setting up pause variable
var isPaused = false;
// Creating camera
const camera = new engine.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
player.position.z = 3; // Changing camera position
// Creating scene
const scene = new engine.Scene();
scene.background = new engine.Color("skyblue"); // Setting background color
// Creating renderer
const renderer = new engine.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate); // Animation loop function
renderer.domElement.style.width = "100%";
renderer.domElement.style.height = "100%";
renderer.domElement.className = "main";
renderer.domElement.id = "main";
// Adding ambient light to the scene
const ambientLight = new engine.AmbientLight(0xFFFFFF);
ambientLight.intensity = 4;
scene.add(ambientLight);
// Game logic
// Camera interaction
renderer.domElement.addEventListener("click", () => {
renderer.domElement.requestPointerLock();
});
renderer.domElement.addEventListener("mousemove", player.handleMouseMove);
document.addEventListener("pointerlockchange", () => {
isPaused = document.pointerLockElement !== renderer.domElement;
});
setInterval(() => {
if (isPaused) {
player.needToHandleMouseMove = false;
player.needToHandleKeyboard = false;
document.getElementById("pauseMenu").style.display = "";
} else {
player.needToHandleMouseMove = true;
player.needToHandleKeyboard = true;
document.getElementById("pauseMenu").style.display = "none";
}
}, 20);
isPaused = true;
// Movement controls
document.addEventListener("keydown", player.handleKeyDown);
document.addEventListener("keyup", player.handleKeyUp);
document.addEventListener('wheel', (event) => { event.preventDefault(); }, {passive: false});
document.body.appendChild(renderer.domElement); // Appending renderer element to the body
// Load assets
const glftLoader = new GLTFLoader();
const objLoader = new OBJLoader();
const mtlLoader = new MTLLoader();
glftLoader.load("./res/TestMap.gltf", (glftScene) => {
var obj = glftScene.scene.children[0];
obj.position.set(0, 0, 0);
scene.add(obj);
});
function animate(time) {
player.checkForMovement();
camera.rotation.copy(player.rotation);
camera.position.copy(player.position);
renderer.render(scene, camera);
}
// HTML interactions
document.getElementById("play").addEventListener("click", () => { isPaused = !isPaused; renderer.domElement.requestPointerLock(); });
document.getElementById("menuquit").addEventListener("click", () => { window.location.href = "../../menus/index/index.html"; });
document.getElementById("osquit").addEventListener("click", () => { require("electron").ipcRenderer.sendSync("closed", "close"); });
Additionally, here is my Player.js file:
import * as THREE from '../three/src/Three.js'
class Player {
constructor(moveSpeed, keys = { W: false, A: false, S: false, D: false, ' ': false, SHIFT: false }) {
this.moveSpeed = moveSpeed;
this.keys = keys;
this.position = new THREE.Vector3();
this.rotation = new THREE.Quaternion();
this.needToHandleMouseMove = false;
this.needToHandleKeyboard = false;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
}
handleKeyDown(event) {
if (this.needToHandleKeyboard) {
const key = event.key.toUpperCase();
console.log("Key: " + key)
if (this.keys.hasOwnProperty(key)) {
this.keys[key] = true;
if (key === ' ') {
event.preventDefault();
}
}
}
}
handleKeyUp(event) {
if (this.needToHandleKeyboard) {
const key = event.key.toUpperCase();
console.log("KeyUp: " + key)
if (this.keys.hasOwnProperty(key)) {
this.keys[key] = false;
}
}
}
handleMouseMove(event) {
if (this.needToHandleMouseMove) {
let movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
let quaternionY = new THREE.Quaternion();
quaternionY.setFromAxisAngle(new THREE.Vector3(0, 1, 0), -movementX * 0.002);
this.rotation.multiply(quaternionY);
}
}
checkForMovement() {
let moveSpeedX = this.moveSpeed * Math.sin(this.rotation.y);
let moveSpeedZ = this.moveSpeed * Math.cos(this.rotation.y);
if (this.keys.W) {
this.position.x -= moveSpeedX;
this.position.z -= moveSpeedZ;
}
if (this.keys.S) {
this.position.x += moveSpeedX;
this.position.z += moveSpeedZ;
}
if (this.keys.D) {
this.position.x += moveSpeedZ;
this.position.z -= moveSpeedX;
}
if (this.keys.A) {
this.position.x -= moveSpeedZ;
this.position.z += moveSpeedX;
}
if (this.keys[' ']) this.position.y += this.moveSpeed;
if (this.keys.SHIFT) this.position.y -= this.moveSpeed;
}
}
export { Player };