You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
8.0 KiB
345 lines
8.0 KiB
2 years ago
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<title>three.js - pointerlock controls</title>
|
||
|
<meta charset="utf-8">
|
||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||
|
<link type="text/css" rel="stylesheet" href="main.css">
|
||
|
<style>
|
||
|
#blocker {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
background-color: rgba(0,0,0,0.5);
|
||
|
}
|
||
|
|
||
|
#instructions {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
|
||
|
text-align: center;
|
||
|
font-size: 14px;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div id="blocker">
|
||
|
<div id="instructions">
|
||
|
<p style="font-size:36px">
|
||
|
Click to play
|
||
|
</p>
|
||
|
<p>
|
||
|
Move: WASD<br/>
|
||
|
Jump: SPACE<br/>
|
||
|
Look: MOUSE
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- Import maps polyfill -->
|
||
|
<!-- Remove this when import maps will be widely supported -->
|
||
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
||
|
|
||
|
<script type="importmap">
|
||
|
{
|
||
|
"imports": {
|
||
|
"three": "../build/three.module.js",
|
||
|
"three/addons/": "./jsm/"
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<script type="module">
|
||
|
|
||
|
import * as THREE from 'three';
|
||
|
|
||
|
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
|
||
|
|
||
|
let camera, scene, renderer, controls;
|
||
|
|
||
|
const objects = [];
|
||
|
|
||
|
let raycaster;
|
||
|
|
||
|
let moveForward = false;
|
||
|
let moveBackward = false;
|
||
|
let moveLeft = false;
|
||
|
let moveRight = false;
|
||
|
let canJump = false;
|
||
|
|
||
|
let prevTime = performance.now();
|
||
|
const velocity = new THREE.Vector3();
|
||
|
const direction = new THREE.Vector3();
|
||
|
const vertex = new THREE.Vector3();
|
||
|
const color = new THREE.Color();
|
||
|
|
||
|
init();
|
||
|
animate();
|
||
|
|
||
|
function init() {
|
||
|
|
||
|
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
|
||
|
camera.position.y = 10;
|
||
|
|
||
|
scene = new THREE.Scene();
|
||
|
scene.background = new THREE.Color( 0xffffff );
|
||
|
scene.fog = new THREE.Fog( 0xffffff, 0, 750 );
|
||
|
|
||
|
const light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 );
|
||
|
light.position.set( 0.5, 1, 0.75 );
|
||
|
scene.add( light );
|
||
|
|
||
|
controls = new PointerLockControls( camera, document.body );
|
||
|
|
||
|
const blocker = document.getElementById( 'blocker' );
|
||
|
const instructions = document.getElementById( 'instructions' );
|
||
|
|
||
|
instructions.addEventListener( 'click', function () {
|
||
|
|
||
|
controls.lock();
|
||
|
|
||
|
} );
|
||
|
|
||
|
controls.addEventListener( 'lock', function () {
|
||
|
|
||
|
instructions.style.display = 'none';
|
||
|
blocker.style.display = 'none';
|
||
|
|
||
|
} );
|
||
|
|
||
|
controls.addEventListener( 'unlock', function () {
|
||
|
|
||
|
blocker.style.display = 'block';
|
||
|
instructions.style.display = '';
|
||
|
|
||
|
} );
|
||
|
|
||
|
scene.add( controls.getObject() );
|
||
|
|
||
|
const onKeyDown = function ( event ) {
|
||
|
|
||
|
switch ( event.code ) {
|
||
|
|
||
|
case 'ArrowUp':
|
||
|
case 'KeyW':
|
||
|
moveForward = true;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowLeft':
|
||
|
case 'KeyA':
|
||
|
moveLeft = true;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowDown':
|
||
|
case 'KeyS':
|
||
|
moveBackward = true;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowRight':
|
||
|
case 'KeyD':
|
||
|
moveRight = true;
|
||
|
break;
|
||
|
|
||
|
case 'Space':
|
||
|
if ( canJump === true ) velocity.y += 350;
|
||
|
canJump = false;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
const onKeyUp = function ( event ) {
|
||
|
|
||
|
switch ( event.code ) {
|
||
|
|
||
|
case 'ArrowUp':
|
||
|
case 'KeyW':
|
||
|
moveForward = false;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowLeft':
|
||
|
case 'KeyA':
|
||
|
moveLeft = false;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowDown':
|
||
|
case 'KeyS':
|
||
|
moveBackward = false;
|
||
|
break;
|
||
|
|
||
|
case 'ArrowRight':
|
||
|
case 'KeyD':
|
||
|
moveRight = false;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
document.addEventListener( 'keydown', onKeyDown );
|
||
|
document.addEventListener( 'keyup', onKeyUp );
|
||
|
|
||
|
raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
|
||
|
|
||
|
// floor
|
||
|
|
||
|
let floorGeometry = new THREE.PlaneGeometry( 2000, 2000, 100, 100 );
|
||
|
floorGeometry.rotateX( - Math.PI / 2 );
|
||
|
|
||
|
// vertex displacement
|
||
|
|
||
|
let position = floorGeometry.attributes.position;
|
||
|
|
||
|
for ( let i = 0, l = position.count; i < l; i ++ ) {
|
||
|
|
||
|
vertex.fromBufferAttribute( position, i );
|
||
|
|
||
|
vertex.x += Math.random() * 20 - 10;
|
||
|
vertex.y += Math.random() * 2;
|
||
|
vertex.z += Math.random() * 20 - 10;
|
||
|
|
||
|
position.setXYZ( i, vertex.x, vertex.y, vertex.z );
|
||
|
|
||
|
}
|
||
|
|
||
|
floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
|
||
|
|
||
|
position = floorGeometry.attributes.position;
|
||
|
const colorsFloor = [];
|
||
|
|
||
|
for ( let i = 0, l = position.count; i < l; i ++ ) {
|
||
|
|
||
|
color.setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
|
||
|
colorsFloor.push( color.r, color.g, color.b );
|
||
|
|
||
|
}
|
||
|
|
||
|
floorGeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colorsFloor, 3 ) );
|
||
|
|
||
|
const floorMaterial = new THREE.MeshBasicMaterial( { vertexColors: true } );
|
||
|
|
||
|
const floor = new THREE.Mesh( floorGeometry, floorMaterial );
|
||
|
scene.add( floor );
|
||
|
|
||
|
// objects
|
||
|
|
||
|
const boxGeometry = new THREE.BoxGeometry( 20, 20, 20 ).toNonIndexed();
|
||
|
|
||
|
position = boxGeometry.attributes.position;
|
||
|
const colorsBox = [];
|
||
|
|
||
|
for ( let i = 0, l = position.count; i < l; i ++ ) {
|
||
|
|
||
|
color.setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
|
||
|
colorsBox.push( color.r, color.g, color.b );
|
||
|
|
||
|
}
|
||
|
|
||
|
boxGeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colorsBox, 3 ) );
|
||
|
|
||
|
for ( let i = 0; i < 500; i ++ ) {
|
||
|
|
||
|
const boxMaterial = new THREE.MeshPhongMaterial( { specular: 0xffffff, flatShading: true, vertexColors: true } );
|
||
|
boxMaterial.color.setHSL( Math.random() * 0.2 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
|
||
|
|
||
|
const box = new THREE.Mesh( boxGeometry, boxMaterial );
|
||
|
box.position.x = Math.floor( Math.random() * 20 - 10 ) * 20;
|
||
|
box.position.y = Math.floor( Math.random() * 20 ) * 20 + 10;
|
||
|
box.position.z = Math.floor( Math.random() * 20 - 10 ) * 20;
|
||
|
|
||
|
scene.add( box );
|
||
|
objects.push( box );
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
|
||
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
||
|
renderer.setPixelRatio( window.devicePixelRatio );
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
document.body.appendChild( renderer.domElement );
|
||
|
|
||
|
//
|
||
|
|
||
|
window.addEventListener( 'resize', onWindowResize );
|
||
|
|
||
|
}
|
||
|
|
||
|
function onWindowResize() {
|
||
|
|
||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
camera.updateProjectionMatrix();
|
||
|
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
|
||
|
}
|
||
|
|
||
|
function animate() {
|
||
|
|
||
|
requestAnimationFrame( animate );
|
||
|
|
||
|
const time = performance.now();
|
||
|
|
||
|
if ( controls.isLocked === true ) {
|
||
|
|
||
|
raycaster.ray.origin.copy( controls.getObject().position );
|
||
|
raycaster.ray.origin.y -= 10;
|
||
|
|
||
|
const intersections = raycaster.intersectObjects( objects, false );
|
||
|
|
||
|
const onObject = intersections.length > 0;
|
||
|
|
||
|
const delta = ( time - prevTime ) / 1000;
|
||
|
|
||
|
velocity.x -= velocity.x * 10.0 * delta;
|
||
|
velocity.z -= velocity.z * 10.0 * delta;
|
||
|
|
||
|
velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
|
||
|
|
||
|
direction.z = Number( moveForward ) - Number( moveBackward );
|
||
|
direction.x = Number( moveRight ) - Number( moveLeft );
|
||
|
direction.normalize(); // this ensures consistent movements in all directions
|
||
|
|
||
|
if ( moveForward || moveBackward ) velocity.z -= direction.z * 400.0 * delta;
|
||
|
if ( moveLeft || moveRight ) velocity.x -= direction.x * 400.0 * delta;
|
||
|
|
||
|
if ( onObject === true ) {
|
||
|
|
||
|
velocity.y = Math.max( 0, velocity.y );
|
||
|
canJump = true;
|
||
|
|
||
|
}
|
||
|
|
||
|
controls.moveRight( - velocity.x * delta );
|
||
|
controls.moveForward( - velocity.z * delta );
|
||
|
|
||
|
controls.getObject().position.y += ( velocity.y * delta ); // new behavior
|
||
|
|
||
|
if ( controls.getObject().position.y < 10 ) {
|
||
|
|
||
|
velocity.y = 0;
|
||
|
controls.getObject().position.y = 10;
|
||
|
|
||
|
canJump = true;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
prevTime = time;
|
||
|
|
||
|
renderer.render( scene, camera );
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|