思路
查看官方例子,使用PointerLockControls可以实现该功能,官方参考地址。
然后可以将模型加载进来同步修改位置和观察方向就可以了
具体代码如下
<!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>
<script type="module">
import * as THREE from '../build/three.module.js';
import { PointerLockControls } from './jsm/controls/PointerLockControls.js';
import { GLTFLoader } from './jsm/loaders/GLTFLoader.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;
//根据navigation属性,开始计算毫秒数,通过两次时间相减可以计算某个操作的间隔时间
let prevTime = performance.now();
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
const vertex = new THREE.Vector3();
const color = new THREE.Color();
let person=null,mixer,stateList={},currentAction,previousAction,lastkey
let clock = new THREE.Clock();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(1,200,1);
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 () {
camera.position.set(1,10,1);
camera.lookAt(new THREE.Vector3(1,1,1));
controls.lock();
} );
controls.addEventListener( 'lock', function () {
//camera.position.y = 1;
instructions.style.display = 'none';
blocker.style.display = 'none';
} );
controls.addEventListener( 'unlock', function () {
blocker.style.display = 'block';
instructions.style.display = '';
} );
controls.addEventListener( 'change', function (evt) {
//console.log('change',evt)
let dir1=new THREE.Vector3()
controls.getDirection(dir1)
console.log('change getDirection',dir1)
console.log('change position',controls.getObject().position)
} );
scene.add( controls.getObject() );
const onKeyDown = function ( event ) {
if (lastkey !== event.code) {
lastkey = event.code;
fadeToAction('Walking', 0.2);
}
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 ) {
lastkey = null;
fadeToAction('Standing', 0.2);
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 );
}
// 添加辅助
var axesHelper = new THREE.AxesHelper(30);
scene.add(axesHelper);
// 添加人物
const gltfloader=new GLTFLoader()
let modelConfig={
url:'models/gltf/RobotExpressive/RobotExpressive.glb',
scale:1,
position:[0,0,0],
openAnimate:true,
}
gltfloader.load( modelConfig.url, ( gltf )=> {
person=gltf.scene
if(modelConfig.position){
gltf.scene.position.set(modelConfig.position[0], modelConfig.position[1], modelConfig.position[2])
}
if(modelConfig.scale){
gltf.scene.scale.set(modelConfig.scale, modelConfig.scale, modelConfig.scale)
}
if(modelConfig.rotateX){
gltf.scene.rotateX(modelConfig.rotateX)
}
if(modelConfig.rotateY){
gltf.scene.rotateX(modelConfig.rotateY)
}
if(modelConfig.rotateZ){
gltf.scene.rotateX(modelConfig.rotateZ)
}
mixer = new THREE.AnimationMixer(person);
stateList.Walking = mixer.clipAction(gltf.animations[10]);
stateList.Standing = mixer.clipAction(gltf.animations[7]);
// 设置下面两项主要是站着的时候,别抖了
stateList.Standing.clampWhenFinished = true;
stateList.Standing.loop = THREE.LoopOnce;
currentAction = stateList.Standing;
currentAction.play();
scene.add( gltf.scene );
//person=gltf.scene
},null,(error)=>{
console.error(error)
} );
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;
}
if(person){
// 同步移动人物位置
let personPosition =camera.position
person.position.set(personPosition.x,personPosition.y-8,personPosition.z-2)
// 同步修改人物方向
let dir1=new THREE.Vector3()
// todo 注意此处获得的是方向向量,需要添加该处的position才能获得目标位置
controls.getDirection(dir1)
let targetPt=person.position.clone()
// 获取目标位置
targetPt.add(dir1)
person.lookAt(targetPt);
}
}
prevTime = time;
roleAction()
renderer.render( scene, camera );
}
function roleAction() {
let dt = clock.getDelta();
if (mixer) mixer.update(dt);
//handleRoleAction();
}
function fadeToAction(name, duration) {
previousAction = currentAction;
currentAction = stateList[name];
if (previousAction !== currentAction) {
previousAction.fadeOut(duration);
}
if (currentAction) {
currentAction
.reset()
.setEffectiveTimeScale(1)
.setEffectiveWeight(1)
.fadeIn(duration)
.play();
}
}
</script>
</body>
</html>
最终效果
效果
如有不足之处欢迎来指正,交流QQ 1826356125