I've been trying to make a good movement script for a few months now but the one I have now isn't very good. (I'm still a beginner).
This is my code:
import * as THREE from 'three'
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // Import the GLTFLoader
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.set(0, 0, 0)
const cameraDirection = new THREE.Vector3();
window.onload = function() {
renderer.domElement.requestPointerLock();
renderer.domElement.onclick = function() {
renderer.domElement.requestPointerLock();
}
};
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
})
renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
window.addEventListener('resize', () => {
const width = window.innerWidth
const height = window.innerHeight
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.setSize(width, height)
})
const skyboxPaths = [
'assets/textures/skymap/Daylight Box_Right.png', // Positive X
'assets/textures/skymap/Daylight Box_Left.png', // Negative X
'assets/textures/skymap/Daylight Box_Top.png', // Positive Y
'assets/textures/skymap/Daylight Box_Bottom.png', // Negative Y
'assets/textures/skymap/Daylight Box_Front.png', // Positive Z
'assets/textures/skymap/Daylight Box_Back.png', // Negative Z
];
const cubeTextureLoader = new THREE.CubeTextureLoader();
const skyboxTexture = cubeTextureLoader.load(skyboxPaths);
scene.background = skyboxTexture;
const controls = new PointerLockControls(camera, renderer.domElement)
controls.sensitivity = 0.1
const world = new CANNON.World();
world.gravity.set(0, -5, 0);
// Define the vertex shader
var vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// Define the fragment shader
var fragmentShader = `
varying vec2 vUv;
float noise(vec2 pos) {
return fract(sin(dot(pos, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
float n = noise(vUv * 10.0);
vec3 color = mix(vec3(0.2, 0.1, 0.0), vec3(0.6, 0.4, 0.2), n);
float brightness = 0.5 + noise(vUv * 30.0) * 0.5;
color *= brightness;
gl_FragColor = vec4(color, 1.0);
}
`;
const textureloader = new THREE.TextureLoader();
// Load the texture
const texture = textureloader.load('assets/textures/materials/stone.png');
const greenMaterial = new THREE.MeshBasicMaterial({color: new THREE.Color(0x00ff00)})
const stoneMaterial = new THREE.MeshBasicMaterial({
map: texture
})
// Create Shader Material
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
zoom: { value: 1.0 },
offset: { value: new THREE.Vector2() }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
const floorGeometry = new THREE.BoxGeometry(5, 0.5, 5)
const floorMesh = new THREE.Mesh(floorGeometry, stoneMaterial)
floorMesh.position.y = -1.75
floorMesh.castShadow = true
scene.add(floorMesh)
const floorShape = new CANNON.Box(new CANNON.Vec3(2.5, 0.25, 2.5))
const floorBody = new CANNON.Body({ mass: 0 })
floorBody.addShape(floorShape)
floorBody.position.x = floorMesh.position.x
floorBody.position.y = floorMesh.position.y
floorBody.position.z = floorMesh.position.z
world.addBody(floorBody)
const plat1Geometry = new THREE.BoxGeometry(1, 0.5, 1)
const plat1Mesh = new THREE.Mesh(plat1Geometry, greenMaterial)
plat1Mesh.position.z = -5
plat1Mesh.position.y = -1.75
plat1Mesh.castShadow = true
scene.add(plat1Mesh)
const plat1Shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 0.5))
const plat1Body = new CANNON.Body({ mass: 0 })
plat1Body.addShape(plat1Shape)
plat1Body.position.x = plat1Mesh.position.x
plat1Body.position.y = plat1Mesh.position.y
plat1Body.position.z = plat1Mesh.position.z
world.addBody(plat1Body)
const plat2Geometry = new THREE.BoxGeometry(1, 0.5, 1)
const plat2Mesh = new THREE.Mesh(plat2Geometry, greenMaterial)
plat2Mesh.position.z = -8
plat2Mesh.position.y = -1.75
plat2Mesh.castShadow = true
scene.add(plat2Mesh)
const plat2Shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 0.5))
const plat2Body = new CANNON.Body({ mass: 0 })
plat2Body.addShape(plat2Shape)
plat2Body.position.x = plat2Mesh.position.x
plat2Body.position.y = plat2Mesh.position.y
plat2Body.position.z = plat2Mesh.position.z
world.addBody(plat2Body)
const plat3Geometry = new THREE.BoxGeometry(1, 0.5, 1)
const plat3Mesh = new THREE.Mesh(plat3Geometry, greenMaterial)
plat3Mesh.position.z = -11
plat3Mesh.position.y = -1.75
plat3Mesh.castShadow = true
scene.add(plat3Mesh)
const plat3Shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 0.5))
const plat3Body = new CANNON.Body({ mass: 0 })
plat3Body.addShape(plat3Shape)
plat3Body.position.x = plat3Mesh.position.x
plat3Body.position.y = plat3Mesh.position.y
plat3Body.position.z = plat3Mesh.position.z
world.addBody(plat3Body)
const plat4Geometry = new THREE.BoxGeometry(1, 0.5, 12)
const plat4Mesh = new THREE.Mesh(plat4Geometry, greenMaterial)
plat4Mesh.position.z = -20
plat4Mesh.position.y = -1.75
plat4Mesh.castShadow = true
scene.add(plat4Mesh)
const plat4Shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 6))
const plat4Body = new CANNON.Body({ mass: 0 })
plat4Body.addShape(plat4Shape)
plat4Body.position.x = plat4Mesh.position.x
plat4Body.position.y = plat4Mesh.position.y
plat4Body.position.z = plat4Mesh.position.z
world.addBody(plat4Body)
const plat5Geometry = new THREE.BoxGeometry(5, 0.5, 5)
const plat5Mesh = new THREE.Mesh(plat5Geometry, greenMaterial)
plat5Mesh.position.z = -30
plat5Mesh.position.y = -1.75
plat5Mesh.castShadow = true
scene.add(plat5Mesh)
const plat5Shape = new CANNON.Box(new CANNON.Vec3(2.5, 0.25, 2.5))
const plat5Body = new CANNON.Body({ mass: 0 })
plat5Body.addShape(plat5Shape)
plat5Body.position.x = plat5Mesh.position.x
plat5Body.position.y = plat5Mesh.position.y
plat5Body.position.z = plat5Mesh.position.z
world.addBody(plat5Body)
const wall1Geometry = new THREE.BoxGeometry(1, 3, 4)
const wall1Mesh = new THREE.Mesh(wall1Geometry, greenMaterial)
wall1Mesh.position.z = -14
wall1Mesh.position.x = 1.5
wall1Mesh.castShadow = true
scene.add(wall1Mesh)
const wall1Shape = new CANNON.Box(new CANNON.Vec3(0.5, 1.5, 2.5))
const wall1Body = new CANNON.Body({ mass: 0 })
wall1Body.addShape(wall1Shape)
wall1Body.position.x = wall1Mesh.position.x
wall1Body.position.y = wall1Mesh.position.y
wall1Body.position.z = wall1Mesh.position.z
world.addBody(wall1Body)
const wall2Geometry = new THREE.BoxGeometry(1, 3, 4)
const wall2Mesh = new THREE.Mesh(wall2Geometry, greenMaterial)
wall2Mesh.position.z = -20
wall2Mesh.position.x = -1.5
wall2Mesh.castShadow = true
scene.add(wall2Mesh)
const wall2Shape = new CANNON.Box(new CANNON.Vec3(0.5, 1.5, 2.5))
const wall2Body = new CANNON.Body({ mass: 0 })
wall2Body.addShape(wall2Shape)
wall2Body.position.x = wall2Mesh.position.x
wall2Body.position.y = wall2Mesh.position.y
wall2Body.position.z = wall2Mesh.position.z
world.addBody(wall2Body)
const buttonGeometry = new THREE.BoxGeometry(0.1, 1, 0.1)
const buttonMesh = new THREE.Mesh()
buttonMesh.position.z = -30
buttonMesh.position.y = -1
buttonMesh.castShadow = true
scene.add(buttonMesh)
const buttonShape = new CANNON.Box(new CANNON.Vec3(0.05, 0.5, 0.05))
const buttonBody = new CANNON.Body({ mass: 0 })
buttonBody.addShape(buttonShape)
buttonBody.position.x = buttonMesh.position.x
buttonBody.position.y = buttonMesh.position.y
buttonBody.position.z = buttonMesh.position.z
world.addBody(buttonBody)
const playerGeometry = new THREE.SphereGeometry(0.25)
const playerMesh = new THREE.Mesh()
playerMesh.position.x = 0
playerMesh.position.y = -1
playerMesh.castShadow = true
scene.add(playerMesh)
const playerShape = new CANNON.Sphere(0.25)
const playerBody = new CANNON.Body({ mass: 1, restitution: 0 })
playerBody.addShape(playerShape)
playerBody.position.x = playerMesh.position.x
playerBody.position.y = playerMesh.position.y
playerBody.position.z = playerMesh.position.z
world.addBody(playerBody)
const loader = new GLTFLoader();
loader.load('assets/models/button.glb', (gltf) => {
const levelModel = gltf.scene;
const scaleFactor = 0.05; // Adjust this value as needed
levelModel.scale.set(scaleFactor, scaleFactor, scaleFactor);
levelModel.position.set(0, -1.4, -30);
scene.add(levelModel);
});
const clock = new THREE.Clock()
let delta
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.y = 3
light.position.z = 1
light.castShadow = true
scene.add(light)
scene.add(new THREE.AmbientLight(0xffffff, 0.5))
camera.position.z = 5
const keys = {
a: {
pressed: false
},
d: {
pressed: false
},
s: {
pressed: false
},
w: {
pressed: false
},
space: {
pressed: false
},
e: {
pressed: false
}
}
window.addEventListener('keydown', (event) => {
switch (event.code) {
case 'KeyA':
keys.a.pressed = true
break
case 'KeyD':
keys.d.pressed = true
break
case 'KeyS':
keys.s.pressed = true
break
case 'KeyW':
keys.w.pressed = true
break
case 'Space':
keys.space.pressed = true
break
case 'KeyE':
keys.e.pressed = true
break
}
})
window.addEventListener('keyup', (event) => {
switch (event.code) {
case 'KeyA':
keys.a.pressed = false
break
case 'KeyD':
keys.d.pressed = false
break
case 'KeyS':
keys.s.pressed = false
break
case 'KeyW':
keys.w.pressed = false
break
case 'Space':
keys.space.pressed = false
break
case 'KeyE':
keys.e.pressed = false
break
}
})
let frames = 0
var canJump = false;
var canWallRun = false;
var contactNormal = new CANNON.Vec3(); // Normal in the contact, pointing *out* of whatever the player touched
var upAxis = new CANNON.Vec3(0,1,0);
var posAxis = new CANNON.Vec3(1,0,1);
var negAxis = new CANNON.Vec3(-1,0,-1);
playerBody.addEventListener("collide",function(e){
var contact = e.contact;
if(contact.bi.id == playerBody.id)
contact.ni.negate(contactNormal);
else
contactNormal.copy(contact.ni);
if(contactNormal.dot(upAxis) > 0.5)
canJump = true;
if(contactNormal.dot(posAxis) > 0.5)
canWallRun = true;
if(contactNormal.dot(negAxis) > 0.5)
canWallRun = true;
if(contactNormal.dot(posAxis) > 0.5 | contactNormal.dot(negAxis) > 0.5){
}
else{
canWallRun = false;
}
});
let bobbingclock = new THREE.Clock();
let bobbingSpeed = 10; // Adjust to your liking
let bobbingAmount = 0.025;
let footsteps = new Audio('assets/SFX/Footsteps.wav');
footsteps.loop = true;
let walked = false;
function animate() {
const animationId = requestAnimationFrame(animate)
// render the main scene
renderer.render( scene, camera );
delta = Math.min(clock.getDelta(), 0.1)
world.step(delta);
let time = bobbingclock.getElapsedTime();
let bobbing = Math.sin(time * bobbingSpeed) * bobbingAmount;
if(canJump == true & (keys.w.pressed | keys.a.pressed | keys.s.pressed | keys.d.pressed)){
camera.position.set(playerMesh.position.x, playerMesh.position.y += bobbing + 0.1, playerMesh.position.z)
}
else{
camera.position.set(playerMesh.position.x, playerMesh.position.y + 0.1, playerMesh.position.z)
}
camera.getWorldDirection(cameraDirection);
// Copy coordinates from Cannon to Three.js
playerMesh.position.set(playerBody.position.x, playerBody.position.y, playerBody.position.z)
playerMesh.quaternion.set(
playerBody.quaternion.x,
playerBody.quaternion.y,
playerBody.quaternion.z,
playerBody.quaternion.w
)
var slideForce = 2; // Adjust this value to control the sliding force
const force = new CANNON.Vec3();
if(nomenu == true){
playerBody.velocity.set(0, playerBody.velocity.y, 0)
// Create a new direction vector that doesn't change when you look up or down
var horizontalDirection = new THREE.Vector3();
horizontalDirection.copy(cameraDirection);
horizontalDirection.y = 0;
horizontalDirection.normalize();
if (keys.d.pressed & !keys.a.pressed & !keys.s.pressed & !keys.w.pressed) {
playerBody.velocity.x -= horizontalDirection.z * slideForce;
playerBody.velocity.z += horizontalDirection.x * slideForce;
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.a.pressed & !keys.d.pressed & !keys.s.pressed & !keys.w.pressed) {
playerBody.velocity.x += horizontalDirection.z * slideForce;
playerBody.velocity.z -= horizontalDirection.x * slideForce;
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.s.pressed & !keys.a.pressed & !keys.d.pressed & !keys.w.pressed) {
playerBody.velocity.x -= horizontalDirection.x * slideForce;
playerBody.velocity.z -= horizontalDirection.z * slideForce;
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.w.pressed & !keys.a.pressed & !keys.s.pressed & !keys.d.pressed) {
playerBody.velocity.x += horizontalDirection.x * slideForce;
playerBody.velocity.z += horizontalDirection.z * slideForce;
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.w.pressed & keys.d.pressed) {
playerBody.velocity.x += horizontalDirection.x * (slideForce * 0.7);
playerBody.velocity.z += horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.x -= horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.z += horizontalDirection.x * (slideForce * 0.7);
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.w.pressed & keys.a.pressed) {
playerBody.velocity.x += horizontalDirection.x * (slideForce * 0.7);
playerBody.velocity.z += horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.x += horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.z -= horizontalDirection.x * (slideForce * 0.7);
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.a.pressed & keys.s.pressed) {
playerBody.velocity.x += horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.z -= horizontalDirection.x * (slideForce * 0.7);
playerBody.velocity.x -= horizontalDirection.x * (slideForce * 0.7);
playerBody.velocity.z -= horizontalDirection.z * (slideForce * 0.7);
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.s.pressed & keys.d.pressed) {
playerBody.velocity.x -= horizontalDirection.x * (slideForce * 0.7);
playerBody.velocity.z -= horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.x -= horizontalDirection.z * (slideForce * 0.7);
playerBody.velocity.z += horizontalDirection.x * (slideForce * 0.7);
if(walked == false){
footsteps.play()
walked = true
}
}
if (keys.space.pressed) {
if (canJump == true){
force.y += 3;
canJump = false
}
}
if(!keys.w.pressed & !keys.a.pressed & !keys.s.pressed & !keys.d.pressed){
footsteps.pause()
footsteps.currentTime = 0;
walked = false
}
if(canJump == false){
footsteps.pause()
footsteps.currentTime = 0;
walked = false
}
if (keys.e.pressed) {
button()
}
}
if(canWallRun == true){
world.gravity.set(0, -1, 0)
slideForce = 5
}
if(canWallRun == false){
world.gravity.set(0, -5, 0)
slideForce = 2
}
function button(){
if(playerBody.position.x > 0 - 0.3){
if(playerBody.position.x < 0 + 0.3){
if(playerBody.position.z < -30 + 0.3){
if(playerBody.position.z > -30 - 0.3){
playerBody.position.set(playerBody.position.x, 1000, playerBody.position.z)
}
}
}
}
}
playerBody.applyImpulse(force, playerBody.position);
if(playerBody.position.y <= -20){
playerBody.position.x = 0
playerBody.position.y = -1.2
playerBody.position.z = 0
playerBody.velocity.set(0, 0, 0)
}
frames++
}
animate()
Html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn around you idiot</title>
<link rel="icon" type="gif" href="assets/icon/logo.png">
<link rel="stylesheet" href="Turn around you idiot.css">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/cannon.min.js"></script>
<style>
body {
margin: 0;
background: white;
}
</style>
<img hidden id="img" src="assets/icon/cursor.png" style="width: 0.35vw;height: 0.35vw;position:absolute;left:50%;top:50%;">
<p hidden id="actionbar" style="position: absolute;left:50%;top: 90%;">You died!</p>
<script>
imgPositionx = window.innerWidth/2 - 0.125
imgPositiony = window.innerHeight/2 - 0.125
document.getElementById('img').style.left = imgPositionx
document.getElementById('img').style.top = imgPositiony
actionbarPositionx = window.innerWidth/2 - 100
actionbarPositiony = window.innerHeight/2 - 0.125
document.getElementById('actionbar').style.left = actionbarPositionx
document.getElementById('actionbar').style.top = actionbarPositiony
</script>
<div id="menu" style="width: 100%;height: 100%;background: none;outline: none;border: none;position: absolute;">
<h2 style="text-align: center;font-size: 5vw;user-select: none;">Turn around you Idiot!</h2>
<button id="newgame" type="button" style="height: 5vw;width: 20vw;font-size: x-large;background: none;position: absolute;top: 15vw;left: 20%;font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;outline: none;border: none;user-select: none;" onclick="newgame()">New Game</button>
<button id="joingame" type="button" style="height: 5vw;width: 20vw;font-size: x-large;background: none;position: absolute;top: 20vw;left: 20%;font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;outline: none;border: none;user-select: none;" onclick="joingame()">Join Game</button>
<button id="options" type="button" style="height: 5vw;width: 20vw;font-size: x-large;background: none;position: absolute;top: 25vw;left: 20%;font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;outline: none;border: none;user-select: none;" onclick="options()">Options</button>
<button id="quit" type="button" style="height: 5vw;width: 20vw;font-size: x-large;background: none;position: absolute;top: 30vw;left: 20%;font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;outline: none;border: none;user-select: none;" onclick="quit()">Quit</button>
</div>
<dialog hidden id="joinmenu" style="width: 80vw;height: 80vh;background: white;outline: none;border: none;position: absolute;top: 5vh;border-radius: 5vw;">
<p style="font-size: 10vw;text-align: center;font-family: monospace;user-select: none;">Join Code:</p>
<input placeholder="IE: 0000" style="font-family: 'Courier New', Courier, monospace;height: 10vw;width: 35vw;position: absolute;left: 25vw;border-radius: 1vw;font-size: 5vw;outline: none;">
<button id="mainmenu" type="button" style="height: 5vw;width: 20vw;font-size: 3vw;background: none;position: absolute;top: 40vw;left: 3%;font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;outline: none;border: none;user-select: none;user-select: none;"onclick="mainmenu()">Back</button>
</dialog>
<script>
let nomenu;
function newgame(){
nomenu = true
document.getElementById('menu').style.display = "none"
document.getElementById('img').style.display = "block"
}
function joingame(){
document.getElementById('joinmenu').style.display = "block"
}
function options(){
}
function quit(){
}
function mainmenu(){
document.getElementById("joinmenu").hidden = "true"
document.getElementById('menu').style.display = "block"
}
</script>
<script
async
src="https://unpkg.com/[email protected]/dist/es-module-shims.js"
></script>
<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" src="Turn around you idiot.js"></script>
</body>
</html>
I tried searching for some online, but apparently no-one has made a parkour/fps movement script in cannon.js before, and the examples that I found online didn't really help that much.