本文主要是介绍简单的基于threejs和BVH第一人称视角和第三人称视角控制器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
渲染框架是基于THREE和BVH结构做重力和墙体碰单检测。本来用的是three自带的octree结构做碰撞发现性能不太好
核心代码:
import * as THREE from 'three'
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
import { MeshBVH, MeshBVHHelper, StaticGeometryGenerator } from 'three-mesh-bvh';
import CameraControls from 'src/renderers/camera';
import { OrbitControls } from 'src/renderers/controls/OrbitControls'
import { Renderer } from 'src/renderers/Renderer';
class InputControls{pressKeys=new Set()releaseKeys=new Set()constructor() {this.mountEvents()}mountEvents(){window.addEventListener('keydown',this.handleKey)window.addEventListener('keyup',this.handleKey)}unmountEvents(){window.removeEventListener('keydown',this.handleKey)window.removeEventListener('keyup',this.handleKey)}isPressedKey(key:string){return this.pressKeys.has(key)}isReleaseKey(key:string){if(this.pressKeys.has(key)&&!this.releaseKeys.has(key)){this.releaseKeys.add(key)return true}return false}handleKey=(e:KeyboardEvent)=>{const type=e.typeconst key=e.key.toLowerCase()if(type==='keydown'){if(!this.pressKeys.has(key)){this.pressKeys.add(key)}}else{if(this.pressKeys.has(key)){this.releaseKeys.delete(key)this.pressKeys.delete(key)}}}
}export class CharacterPersonCamera{keys=new Set()player:THREE.Meshcollider?:THREE.MeshcolliderBox2:THREE.Box2=new THREE.Box2()colliderBox:THREE.Box3=new THREE.Box3()input:InputControlsspeed=100speedRatio=1 // 速率gravity=298 // 重力速度enableGravity=false // 是否启用重力_enableFirstPerson=false// 是否启用第一视角// 当前速度和位移playerVelocity=new THREE.Vector3()// 累积移动accumulateMovement=new THREE.Vector3()deltaPosition=new THREE.Vector3()tempPlayerPosition=new THREE.Vector2()tempVector=new THREE.Vector3()tempVector2=new THREE.Vector3()tempDirection=new THREE.Vector3()tempBox=new THREE.Box3()tempSegment=new THREE.Line3()tempMat=new THREE.Matrix4()playerIsOnGround=false // 是否在地面enable=true // 是否启用cameraControls?:CameraControlsorbitControls?:OrbitControlsupVector = new THREE.Vector3( 0, 1, 0 );colliderBoxDistance=Infinityconstructor(public context:Renderer) {this.input=new InputControls()this.player=new THREE.Mesh(new RoundedBoxGeometry(0.5,1,0.5,10,1),new THREE.MeshBasicMaterial({color:0xff0000}))// this.player=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),generateCubeFaceTexture(512,512))this.player.userData={capsuleInfo:{radius: 0.5,segment: new THREE.Line3( new THREE.Vector3(), new THREE.Vector3( 0,0, 0.0 ) )}}this.player.position.setFromMatrixPosition(this.camera.matrixWorld)// this.root.add(this.player)}get renderer(){return this.context.renderer}get root(){return this.context.scene}get camera(){return this.context.camera}get finalSpeed(){return this.speed*this.speedRatio}get playerDirection(){return this.player.quaternion}get isAllowFalling(){this.tempPlayerPosition.set(this.player.position.x,this.player.position.z)// 是否可以下落,并且当前视角位置在碰撞检测体的z轴平面上.return this.enableGravity&&this.colliderBox2.containsPoint(this.tempPlayerPosition)}get minDropY(){return this.colliderBox.min.y}set enableFirstPerson(v){if(v!==this._enableFirstPerson){this._enableFirstPerson=v;if(!v&&this.orbitControls){this.camera.position.sub( this.orbitControls.target).normalize().multiplyScalar( 10 ).add( this.orbitControls.target); }else if(!v&&this.cameraControls){this.cameraControls.getTarget(this.tempVector)this.camera.position.sub(this.cameraControls.getTarget(this.tempVector) ).normalize().multiplyScalar( 10 ).add(this.cameraControls.getTarget(this.tempVector)); }}}get enableFirstPerson(){return this._enableFirstPerson}setupOrbitControls(){this.orbitControls=new OrbitControls(this.camera,this.renderer.domElement)this.initControlsMaxLimit()// this.orbitControls.enableDamping=true// this.orbitControls.enablePan=true// this.orbitControls.enableZoom=true// this.orbitControls.rotateSpeed=1// this.orbitControls.minAzimuthAngle=-Math.PI// this.orbitControls.maxAzimuthAngle=Math.PI}setColliderModel(colliderModel:THREE.Object3D){const staticGenerator = new StaticGeometryGenerator( colliderModel );staticGenerator.attributes = [ 'position' ];const mergedGeometry = staticGenerator.generate();mergedGeometry.boundsTree = new MeshBVH( mergedGeometry );this.collider = new THREE.Mesh( mergedGeometry );mergedGeometry.boundsTree.getBoundingBox(this.colliderBox)this.colliderBox2.min.set(this.colliderBox.min.x,this.colliderBox.min.z)this.colliderBox2.max.set(this.colliderBox.max.x,this.colliderBox.max.z)this.colliderBoxDistance=this.colliderBox.getSize(this.tempVector).length()*1.5// const visualizer = new MeshBVHHelper(this.collider,1000 );//this.root.add( visualizer );}updateControls(delta:number){const finalSpeed=this.finalSpeed*deltaif(this.orbitControls){const angle = this.orbitControls.getAzimuthalAngle();const tempVector=this.tempVectorconst upVector=this.upVectorif(this.input.isPressedKey('w')){tempVector.set( 0, 0, - 1 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('s')){tempVector.set( 0, 0, 1 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('a')){tempVector.set( -1, 0, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('d')){tempVector.set( 1, 0, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('q')){tempVector.set( 0, 1, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('e')){tempVector.set( 0, -1, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}}else{if(this.input.isPressedKey('w')){this.tempVector.set(0,0,1).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('s')){this.tempVector.set(0,0,1).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('a')){this.tempVector.set(1,0,0).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('d')){this.tempVector.set(1,0,0).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('q')){this.tempVector.set(0,1,0).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('e')){this.tempVector.set(0,1,0).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}}}updatePlayer(delta:number){// 增加阻尼const damping=0.9if (this.enableGravity&&this.isAllowFalling&&!this.playerIsOnGround) {this.playerVelocity.y -= delta * this.gravity;}this.playerVelocity.multiplyScalar(damping)this.deltaPosition.copy(this.playerVelocity).multiplyScalar(delta)this.accumulateMovement.add(this.deltaPosition)// 应用移动this.player.position.add(this.deltaPosition)// 如果重力模式,就应用物理碰撞if(this.enableGravity){this.updateCollider(delta)}if(this.orbitControls){// this.camera.translateZ(2)this.camera.position.sub(this.orbitControls.target);this.orbitControls.target.copy(this.player.position);this.camera.position.add(this.player.position);}else if(this.cameraControls){this.cameraControls.getTarget(this.tempVector,true)this.camera.position.sub(this.tempVector);this.cameraControls.setTarget(this.player.position.x,this.player.position.y,this.player.position.z,false);this.camera.position.add(this.player.position);}else{this.camera.position.copy(this.player.position)this.camera.translateZ(2)}}box3Helper?:THREE.Box3HelpervisibleBox3Helper(box:THREE.Box3){if(!this.box3Helper){this.box3Helper=new THREE.Box3Helper(box,0xff0000)this.root.add(this.box3Helper)}else{this.box3Helper.box.copy(box)}}updateCollider(delta:number){const collider=this.collider!;const player=this.playerconst boundsTree=collider.geometry.boundsTree as MeshBVHconst tempBox=this.tempBoxconst tempSegment=this.tempSegmentconst tempMat=this.tempMatconst tempVector=this.tempVectorconst tempVector2=this.tempVector2;const playerVelocity=this.playerVelocityplayer.updateMatrixWorld();// 根据碰撞调整玩家位置const capsuleInfo = player.userData.capsuleInfo;tempBox.makeEmpty();tempMat.copy( collider.matrixWorld ).invert();tempSegment.copy( capsuleInfo.segment );//获取胶囊在碰撞器局部空间中的位置tempSegment.start.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );tempSegment.end.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );// 获取胶囊的轴对齐边界框tempBox.expandByPoint( tempSegment.start );tempBox.expandByPoint( tempSegment.end );tempBox.min.addScalar( - capsuleInfo.radius );tempBox.max.addScalar( capsuleInfo.radius );// this.visibleBox3Helper(tempBox)boundsTree.shapecast( {intersectsBounds: box => box.intersectsBox( tempBox ),intersectsTriangle: tri => {// 检查三角形是否与胶囊相交并调整// 胶囊位置(如果是)。const triPoint = tempVector;const capsulePoint =tempVector2;const distance = tri.closestPointToSegment( tempSegment, triPoint, capsulePoint );if ( distance < capsuleInfo.radius ) {const depth = capsuleInfo.radius - distance;const direction = capsulePoint.sub( triPoint ).normalize();tempSegment.start.addScaledVector( direction, depth );tempSegment.end.addScaledVector( direction, depth );}}} );// 检查后得到胶囊碰撞器在世界空间中的调整位置// 三角形碰撞并移动它。 CapsuleInfo.segment.start 假设为// 玩家模型的起源。const newPosition = tempVector;newPosition.copy( tempSegment.start ).applyMatrix4( collider.matrixWorld );// 检查碰撞体移动了多少const deltaVector = tempVector2;deltaVector.subVectors( newPosition, player.position );// 如果玩家主要是垂直调整的,我们假设它位于我们应该考虑地面的地方this.playerIsOnGround = deltaVector.y > Math.abs( delta * playerVelocity.y * 0.25 );const offset = Math.max( 0.0, deltaVector.length() - 1e-5 );deltaVector.normalize().multiplyScalar( offset );// 调整位置 player.position.add( deltaVector );if ( !this.playerIsOnGround ) {// console.log('this.playerIsOnGround',deltaVector)deltaVector.normalize();playerVelocity.addScaledVector( deltaVector, - deltaVector.dot( playerVelocity ) );} else {playerVelocity.set( 0, 0, 0 );}// 如果玩家跌落到水平线以下太远,则将其位置重置为开始位置if ( player.position.y < this.minDropY ) {this.resetPlayerPosition();}}resetPlayerPosition(){this.playerVelocity.y=0this.player.position.y=this.minDropY}initControlsMaxLimit(){const controls=this.orbitControls||this.cameraControlsif(controls){if(this.enableFirstPerson){controls.maxPolarAngle = Math.PI;controls.minDistance = 1e-4;controls.maxDistance = 1e-4;}else{controls.maxPolarAngle = Math.PI / 2;controls.minDistance = 1;controls.maxDistance = this.colliderBoxDistance}}}onUpdate(delta:number){if(!this.enable){return}this.player.quaternion.copy(this.camera.quaternion)// this.player.quaternion.x=0// this.player.quaternion.z=0// this.player.quaternion.normalize()let controls:any;if(this.orbitControls){controls=this.orbitControls}else if(this.cameraControls){controls=this.cameraControls as any}this.initControlsMaxLimit()const MAX_STEP=5;for(let i=0;i<MAX_STEP;i++){const d=delta/MAX_STEP;this.updateControls(d)this.updatePlayer(d)}if(controls){controls.update(delta)}}dispose(){if(this.orbitControls){this.orbitControls.dispose()}this.input.unmountEvents()}
}
这篇关于简单的基于threejs和BVH第一人称视角和第三人称视角控制器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!