Build a complete browser-based first-person 3D environment using the latest stable version of Three.js. The user must be able to move freely through the environment using the WASD keys for directional movement and the mouse for camera rotation through pointer lock controls. The implementation must not include any overlays, popups, or interactive prompts such as “Click to Begin.” Implement a camera soldier system that maintains a human-scale eye height, smooth acceleration, head stability, and accurate step height relative to the ground. Include a visible textured ground plane that spans a large enough area for continuous movement. Place one static 3D object within the scene positioned as a navigation obstacle. Include a complete lighting setup using a combination of directional light and hemispheric light to achieve visible illumination across all geometry. Implement smooth movement physics that remain consistent under variable frame rates by using delta-time-based integration. Ensure collision handling between the player controller and static geometry so that the user cannot pass through solid objects. Maintain correct visual proportions and perspective under all window resizing events by recalculating aspect ratio and updating the camera projection matrix dynamically. Use current JavaScript and Three.js best practices with explicit import statements from the most recent stable module URLs. All variable names must follow consistent camelCase naming. All functions must include concise, technically accurate code comments describing their purpose and behavior. Use strict typing where possible, avoid global variables, and ensure deterministic behavior of the main animation loop driven by requestAnimationFrame. The program must execute without console warnings, syntax errors, or deprecated API usage.
Browse files- index.html +143 -200
index.html
CHANGED
|
@@ -1,146 +1,70 @@
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>WanderLust 3D Explorer</title>
|
| 7 |
-
<script src="https://
|
| 8 |
-
<script src="https://
|
| 9 |
-
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
|
| 10 |
<style>
|
| 11 |
body {
|
| 12 |
margin: 0;
|
| 13 |
overflow: hidden;
|
| 14 |
-
font-family: 'Inter', sans-serif;
|
| 15 |
-
}
|
| 16 |
-
#info {
|
| 17 |
-
position: absolute;
|
| 18 |
-
top: 10px;
|
| 19 |
-
width: 100%;
|
| 20 |
-
text-align: center;
|
| 21 |
-
color: white;
|
| 22 |
-
z-index: 100;
|
| 23 |
-
pointer-events: none;
|
| 24 |
-
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
| 25 |
-
}
|
| 26 |
-
#blocker {
|
| 27 |
-
position: absolute;
|
| 28 |
-
width: 100%;
|
| 29 |
-
height: 100%;
|
| 30 |
-
background-color: rgba(0,0,0,0.7);
|
| 31 |
-
display: flex;
|
| 32 |
-
flex-direction: column;
|
| 33 |
-
justify-content: center;
|
| 34 |
-
align-items: center;
|
| 35 |
-
z-index: 200;
|
| 36 |
-
}
|
| 37 |
-
#instructions {
|
| 38 |
-
color: white;
|
| 39 |
-
max-width: 600px;
|
| 40 |
-
text-align: center;
|
| 41 |
-
padding: 20px;
|
| 42 |
-
background-color: rgba(0,0,0,0.5);
|
| 43 |
-
border-radius: 10px;
|
| 44 |
}
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
margin-top: 20px;
|
| 48 |
-
background: linear-gradient(135deg, #6e8efb, #a777e3);
|
| 49 |
-
color: white;
|
| 50 |
-
border: none;
|
| 51 |
-
border-radius: 25px;
|
| 52 |
-
font-size: 16px;
|
| 53 |
-
cursor: pointer;
|
| 54 |
-
transition: all 0.3s ease;
|
| 55 |
-
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 56 |
-
}
|
| 57 |
-
#startButton:hover {
|
| 58 |
-
transform: translateY(-2px);
|
| 59 |
-
box-shadow: 0 6px 8px rgba(0,0,0,0.15);
|
| 60 |
}
|
| 61 |
</style>
|
| 62 |
</head>
|
| 63 |
-
<body
|
| 64 |
-
<div id="info" class="text-lg">
|
| 65 |
-
WanderLust 3D Explorer | WASD to move | Mouse to look | ESC to exit
|
| 66 |
-
</div>
|
| 67 |
-
<div id="blocker">
|
| 68 |
-
<div id="instructions" class="text-xl">
|
| 69 |
-
<h1 class="text-3xl font-bold mb-4">Welcome to WanderLust</h1>
|
| 70 |
-
<p class="mb-6">Explore this immersive 3D environment freely.</p>
|
| 71 |
-
<p class="text-sm opacity-75">Click the button below to enter the experience. Use WASD keys to move and mouse to look around.</p>
|
| 72 |
-
<button id="startButton">BEGIN EXPLORATION</button>
|
| 73 |
-
</div>
|
| 74 |
-
</div>
|
| 75 |
<script>
|
|
|
|
|
|
|
| 76 |
// Scene setup
|
| 77 |
const scene = new THREE.Scene();
|
| 78 |
-
scene.background = new THREE.Color(0x87CEEB);
|
| 79 |
|
| 80 |
-
// Camera
|
| 81 |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
| 82 |
-
camera.position.
|
| 83 |
|
| 84 |
-
// Renderer
|
| 85 |
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
| 86 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 87 |
renderer.shadowMap.enabled = true;
|
|
|
|
| 88 |
document.body.appendChild(renderer.domElement);
|
| 89 |
|
| 90 |
-
//
|
| 91 |
const controls = new THREE.PointerLockControls(camera, document.body);
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
//
|
| 99 |
const velocity = new THREE.Vector3();
|
| 100 |
const direction = new THREE.Vector3();
|
| 101 |
-
let prevTime =
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
});
|
| 113 |
-
|
| 114 |
-
document.addEventListener('keyup', (event) => {
|
| 115 |
-
switch (event.code) {
|
| 116 |
-
case 'KeyW': moveForward = false; break;
|
| 117 |
-
case 'KeyA': moveLeft = false; break;
|
| 118 |
-
case 'KeyS': moveBackward = false; break;
|
| 119 |
-
case 'KeyD': moveRight = false; break;
|
| 120 |
-
}
|
| 121 |
-
});
|
| 122 |
-
|
| 123 |
-
// UI Elements
|
| 124 |
-
const blocker = document.getElementById('blocker');
|
| 125 |
-
const instructions = document.getElementById('instructions');
|
| 126 |
-
const startButton = document.getElementById('startButton');
|
| 127 |
-
|
| 128 |
-
startButton.addEventListener('click', () => {
|
| 129 |
-
controls.lock();
|
| 130 |
-
});
|
| 131 |
-
|
| 132 |
-
controls.addEventListener('lock', () => {
|
| 133 |
-
blocker.style.display = 'none';
|
| 134 |
-
});
|
| 135 |
-
|
| 136 |
-
controls.addEventListener('unlock', () => {
|
| 137 |
-
blocker.style.display = 'flex';
|
| 138 |
-
});
|
| 139 |
-
|
| 140 |
-
// Create ground
|
| 141 |
const groundGeometry = new THREE.PlaneGeometry(100, 100);
|
| 142 |
const groundMaterial = new THREE.MeshStandardMaterial({
|
| 143 |
-
|
| 144 |
roughness: 0.8,
|
| 145 |
metalness: 0.2
|
| 146 |
});
|
|
@@ -149,40 +73,21 @@
|
|
| 149 |
ground.receiveShadow = true;
|
| 150 |
scene.add(ground);
|
| 151 |
|
| 152 |
-
//
|
| 153 |
-
const
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
cube.position.z = Math.random() * 40 - 20;
|
| 168 |
-
scene.add(cube);
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
// Add a larger central structure
|
| 172 |
-
const towerGeometry = new THREE.CylinderGeometry(3, 2, 8, 8);
|
| 173 |
-
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0x9933ff });
|
| 174 |
-
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
|
| 175 |
-
tower.position.y = 4;
|
| 176 |
-
tower.castShadow = true;
|
| 177 |
-
scene.add(tower);
|
| 178 |
-
};
|
| 179 |
-
|
| 180 |
-
// Lighting
|
| 181 |
-
const setupLighting = () => {
|
| 182 |
-
// Ambient light
|
| 183 |
-
const ambientLight = new THREE.AmbientLight(0x404040);
|
| 184 |
-
scene.add(ambientLight);
|
| 185 |
-
|
| 186 |
// Directional light (sun)
|
| 187 |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
| 188 |
directionalLight.position.set(10, 20, 10);
|
|
@@ -191,86 +96,124 @@
|
|
| 191 |
directionalLight.shadow.mapSize.height = 2048;
|
| 192 |
directionalLight.shadow.camera.near = 0.5;
|
| 193 |
directionalLight.shadow.camera.far = 500;
|
| 194 |
-
directionalLight.shadow.camera.left = -50;
|
| 195 |
-
directionalLight.shadow.camera.right = 50;
|
| 196 |
-
directionalLight.shadow.camera.top = 50;
|
| 197 |
-
directionalLight.shadow.camera.bottom = -50;
|
| 198 |
scene.add(directionalLight);
|
| 199 |
|
| 200 |
-
// Hemisphere light for
|
| 201 |
const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5f0b, 0.3);
|
| 202 |
scene.add(hemisphereLight);
|
| 203 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
});
|
| 212 |
-
const skybox = new THREE.Mesh(skyGeometry, skyMaterial);
|
| 213 |
-
scene.add(skybox);
|
| 214 |
-
};
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
|
|
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
requestAnimationFrame(animate);
|
| 232 |
|
| 233 |
-
|
| 234 |
-
const
|
| 235 |
-
prevTime =
|
| 236 |
|
| 237 |
-
//
|
| 238 |
-
|
| 239 |
-
const moveSpeed = speed * delta;
|
| 240 |
|
| 241 |
-
//
|
| 242 |
-
direction.z = Number(
|
| 243 |
-
direction.x = Number(
|
| 244 |
direction.normalize();
|
| 245 |
|
| 246 |
-
// Apply movement
|
| 247 |
-
if (
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
// Apply gravity
|
| 251 |
-
velocity.y -=
|
| 252 |
-
|
| 253 |
-
//
|
| 254 |
-
controls.
|
| 255 |
-
controls.
|
| 256 |
-
controls.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
-
//
|
| 259 |
-
if (controls.getObject().position.y <
|
| 260 |
velocity.y = 0;
|
| 261 |
-
controls.getObject().position.y =
|
| 262 |
-
canJump = true;
|
| 263 |
} else {
|
| 264 |
-
canJump = false;
|
| 265 |
}
|
| 266 |
|
| 267 |
// Apply damping (friction)
|
| 268 |
-
velocity.x *=
|
| 269 |
-
velocity.z *=
|
| 270 |
|
| 271 |
renderer.render(scene, camera);
|
| 272 |
-
}
|
| 273 |
|
|
|
|
| 274 |
animate();
|
| 275 |
</script>
|
| 276 |
</body>
|
|
|
|
| 1 |
+
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="en">
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
<title>WanderLust 3D Explorer</title>
|
| 8 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r154/three.min.js"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/three@0.154.0/examples/js/controls/PointerLockControls.js"></script>
|
|
|
|
| 10 |
<style>
|
| 11 |
body {
|
| 12 |
margin: 0;
|
| 13 |
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
+
canvas {
|
| 16 |
+
display: block;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
}
|
| 18 |
</style>
|
| 19 |
</head>
|
| 20 |
+
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
<script>
|
| 22 |
+
'use strict';
|
| 23 |
+
|
| 24 |
// Scene setup
|
| 25 |
const scene = new THREE.Scene();
|
| 26 |
+
scene.background = new THREE.Color(0x87CEEB);
|
| 27 |
|
| 28 |
+
// Camera setup with human-scale eye height (1.6m)
|
| 29 |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
| 30 |
+
camera.position.set(0, 1.6, 0);
|
| 31 |
|
| 32 |
+
// Renderer with antialiasing and shadows
|
| 33 |
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
| 34 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 35 |
renderer.shadowMap.enabled = true;
|
| 36 |
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
| 37 |
document.body.appendChild(renderer.domElement);
|
| 38 |
|
| 39 |
+
// PointerLock controls for first-person movement
|
| 40 |
const controls = new THREE.PointerLockControls(camera, document.body);
|
| 41 |
+
|
| 42 |
+
// Movement state variables
|
| 43 |
+
const movementState = {
|
| 44 |
+
forward: false,
|
| 45 |
+
backward: false,
|
| 46 |
+
left: false,
|
| 47 |
+
right: false,
|
| 48 |
+
canJump: false
|
| 49 |
+
};
|
| 50 |
|
| 51 |
+
// Physics variables
|
| 52 |
const velocity = new THREE.Vector3();
|
| 53 |
const direction = new THREE.Vector3();
|
| 54 |
+
let prevTime = 0;
|
| 55 |
+
const playerHeight = 1.6;
|
| 56 |
+
const movementSpeed = 5.0;
|
| 57 |
+
const gravity = 9.8;
|
| 58 |
+
const dampingFactor = 5.0;
|
| 59 |
+
|
| 60 |
+
// Ground plane with texture
|
| 61 |
+
const groundTexture = new THREE.TextureLoader().load('http://static.photos/nature/1024x576/1');
|
| 62 |
+
groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
|
| 63 |
+
groundTexture.repeat.set(10, 10);
|
| 64 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
const groundGeometry = new THREE.PlaneGeometry(100, 100);
|
| 66 |
const groundMaterial = new THREE.MeshStandardMaterial({
|
| 67 |
+
map: groundTexture,
|
| 68 |
roughness: 0.8,
|
| 69 |
metalness: 0.2
|
| 70 |
});
|
|
|
|
| 73 |
ground.receiveShadow = true;
|
| 74 |
scene.add(ground);
|
| 75 |
|
| 76 |
+
// Single obstacle (large cube)
|
| 77 |
+
const obstacleGeometry = new THREE.BoxGeometry(4, 4, 4);
|
| 78 |
+
const obstacleMaterial = new THREE.MeshStandardMaterial({
|
| 79 |
+
color: 0x9933ff,
|
| 80 |
+
roughness: 0.7,
|
| 81 |
+
metalness: 0.3
|
| 82 |
+
});
|
| 83 |
+
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
|
| 84 |
+
obstacle.position.set(10, 2, 0);
|
| 85 |
+
obstacle.castShadow = true;
|
| 86 |
+
obstacle.receiveShadow = true;
|
| 87 |
+
scene.add(obstacle);
|
| 88 |
+
|
| 89 |
+
// Lighting setup
|
| 90 |
+
function setupLighting() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
// Directional light (sun)
|
| 92 |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
| 93 |
directionalLight.position.set(10, 20, 10);
|
|
|
|
| 96 |
directionalLight.shadow.mapSize.height = 2048;
|
| 97 |
directionalLight.shadow.camera.near = 0.5;
|
| 98 |
directionalLight.shadow.camera.far = 500;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
scene.add(directionalLight);
|
| 100 |
|
| 101 |
+
// Hemisphere light for ambient outdoor lighting
|
| 102 |
const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5f0b, 0.3);
|
| 103 |
scene.add(hemisphereLight);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Collision detection between player and obstacles
|
| 107 |
+
function checkCollisions(position) {
|
| 108 |
+
const playerBoundingBox = new THREE.Box3().setFromCenterAndSize(
|
| 109 |
+
new THREE.Vector3(position.x, position.y, position.z),
|
| 110 |
+
new THREE.Vector3(0.5, playerHeight, 0.5)
|
| 111 |
+
);
|
| 112 |
|
| 113 |
+
const obstacleBoundingBox = new THREE.Box3().setFromObject(obstacle);
|
| 114 |
+
return playerBoundingBox.intersectsBox(obstacleBoundingBox);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// Input event handlers
|
| 118 |
+
function setupEventListeners() {
|
| 119 |
+
document.addEventListener('keydown', (event) => {
|
| 120 |
+
switch (event.code) {
|
| 121 |
+
case 'KeyW': movementState.forward = true; break;
|
| 122 |
+
case 'KeyA': movementState.left = true; break;
|
| 123 |
+
case 'KeyS': movementState.backward = true; break;
|
| 124 |
+
case 'KeyD': movementState.right = true; break;
|
| 125 |
+
}
|
| 126 |
});
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
document.addEventListener('keyup', (event) => {
|
| 129 |
+
switch (event.code) {
|
| 130 |
+
case 'KeyW': movementState.forward = false; break;
|
| 131 |
+
case 'KeyA': movementState.left = false; break;
|
| 132 |
+
case 'KeyS': movementState.backward = false; break;
|
| 133 |
+
case 'KeyD': movementState.right = false; break;
|
| 134 |
+
}
|
| 135 |
+
});
|
| 136 |
|
| 137 |
+
// Auto-enable pointer lock on click
|
| 138 |
+
document.addEventListener('click', () => {
|
| 139 |
+
if (!controls.isLocked) {
|
| 140 |
+
controls.lock();
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
|
| 144 |
+
// Window resize handler
|
| 145 |
+
window.addEventListener('resize', () => {
|
| 146 |
+
camera.aspect = window.innerWidth / window.innerHeight;
|
| 147 |
+
camera.updateProjectionMatrix();
|
| 148 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 149 |
+
});
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
// Initialize the scene
|
| 153 |
+
function init() {
|
| 154 |
+
setupLighting();
|
| 155 |
+
setupEventListeners();
|
| 156 |
+
|
| 157 |
+
// Start with pointer lock
|
| 158 |
+
controls.lock();
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// Animation loop with delta-time based physics
|
| 162 |
+
function animate(currentTime = 0) {
|
| 163 |
requestAnimationFrame(animate);
|
| 164 |
|
| 165 |
+
// Calculate delta time in seconds
|
| 166 |
+
const deltaTime = (currentTime - prevTime) / 1000;
|
| 167 |
+
prevTime = currentTime;
|
| 168 |
|
| 169 |
+
// Skip physics on first frame or when delta is too large
|
| 170 |
+
if (deltaTime > 0.1) return;
|
|
|
|
| 171 |
|
| 172 |
+
// Movement direction based on input
|
| 173 |
+
direction.z = Number(movementState.forward) - Number(movementState.backward);
|
| 174 |
+
direction.x = Number(movementState.right) - Number(movementState.left);
|
| 175 |
direction.normalize();
|
| 176 |
|
| 177 |
+
// Apply movement with speed scaled by delta time
|
| 178 |
+
if (movementState.forward || movementState.backward) {
|
| 179 |
+
velocity.z -= direction.z * movementSpeed * deltaTime;
|
| 180 |
+
}
|
| 181 |
+
if (movementState.left || movementState.right) {
|
| 182 |
+
velocity.x -= direction.x * movementSpeed * deltaTime;
|
| 183 |
+
}
|
| 184 |
|
| 185 |
// Apply gravity
|
| 186 |
+
velocity.y -= gravity * deltaTime;
|
| 187 |
+
|
| 188 |
+
// Calculate new position
|
| 189 |
+
const oldPosition = controls.getObject().position.clone();
|
| 190 |
+
controls.moveRight(-velocity.x * deltaTime);
|
| 191 |
+
controls.moveForward(-velocity.z * deltaTime);
|
| 192 |
+
controls.getObject().position.y += velocity.y * deltaTime;
|
| 193 |
+
|
| 194 |
+
// Check for collisions
|
| 195 |
+
if (checkCollisions(controls.getObject().position)) {
|
| 196 |
+
controls.getObject().position.copy(oldPosition);
|
| 197 |
+
velocity.set(0, 0, 0);
|
| 198 |
+
}
|
| 199 |
|
| 200 |
+
// Ground collision and jumping
|
| 201 |
+
if (controls.getObject().position.y < playerHeight) {
|
| 202 |
velocity.y = 0;
|
| 203 |
+
controls.getObject().position.y = playerHeight;
|
| 204 |
+
movementState.canJump = true;
|
| 205 |
} else {
|
| 206 |
+
movementState.canJump = false;
|
| 207 |
}
|
| 208 |
|
| 209 |
// Apply damping (friction)
|
| 210 |
+
velocity.x *= Math.max(0, 1 - dampingFactor * deltaTime);
|
| 211 |
+
velocity.z *= Math.max(0, 1 - dampingFactor * deltaTime);
|
| 212 |
|
| 213 |
renderer.render(scene, camera);
|
| 214 |
+
}
|
| 215 |
|
| 216 |
+
init();
|
| 217 |
animate();
|
| 218 |
</script>
|
| 219 |
</body>
|