900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > Threejs实现键盘控制人物行走跳动/Capsule碰撞体(胶囊体)/碰撞检测(Octree八叉树)/游

Threejs实现键盘控制人物行走跳动/Capsule碰撞体(胶囊体)/碰撞检测(Octree八叉树)/游

时间:2019-01-11 14:43:31

相关推荐

Threejs实现键盘控制人物行走跳动/Capsule碰撞体(胶囊体)/碰撞检测(Octree八叉树)/游

个人主页:左本Web3D,更多案例预览请点击==》在线案例

个人简介:专注Web3D使用ThreeJS实现3D效果技巧和学习案例

💕 💕积跬步以至千里,致敬每个爱学习的你。喜欢的话请三连,有问题请私信或者加微信

1,功能介绍

Threejs实现键盘控制人物行走和跳动、使用八叉树Octree和胶囊体Capsule进行碰撞检测、人物头顶显示名称标签、镜头跟随人物移动并且镜头围绕人物旋转,类似游戏中第三人称。如下效果图

2,功能实现

Octree是一种基于八叉树的数据结构,使用Octree进行碰撞检测可以提高性能,特别是在场景中存在大量物体的情况下。

Capsule(胶囊体)是一种3D几何体,它由两个球体和一个圆柱体组成。通常被用作游戏和虚拟现实应用中的角色或物体的碰撞体积。

创建一个名为worldOctree的Octree对象,并将gltf.scene中的所有网格节点添加到Octree中。

Octree是一种空间划分数据结构,可用于更有效地进行3D碰撞检测。Octree通过将3D空间分解成八个相等的子空间,然后递归地将每个子空间再分解成八个子空间,以此类推。在这个过程中,Octree会将所有网格对象分配到合适的节点中。这样做的好处是,在进行碰撞检测时,可以避免对整个场景中的所有网格进行遍历,而只需遍历与当前视野和碰撞体相关的那些网格。这样,可以大大减少碰撞检测的计算量,提高性能。

const worldOctree = new Octree();worldOctree.fromGraphNode(gltf.scene);

创建Capsule.js实例来表示一个胶囊体,然后将该实例添加到Octree中,以便进行空间分区和碰撞检测。例如:

const playerCollider = new Capsule(position1, position2, radius);const result = worldOctree.capsuleIntersect(playerCollider);

其中,position1和position2是线段的两个端点的位置,radius是球的半径。创建Capsule.js实例后,可以使用该实例来创建一个包含几何体信息的playerCollider对象,并使用worldOctree.capsuleIntersect()方法将该对象添加到Octree中。

const result = worldOctree.capsuleIntersect(playerCollider);

它首先调用了worldOctree中的capsuleIntersect方法,将玩家的碰撞体playerCollider作为参数传入,得到碰撞检测的结果result。如果result存在,说明发生了碰撞,需要将玩家位置进行调整

function playerCollisions() {const result = worldOctree.capsuleIntersect(playerCollider);playerOnFloor = false;if (result) {playerOnFloor = result.normal.y > 0;if (!playerOnFloor) {playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));}playerCollider.translate(result.normal.multiplyScalar(result.depth));}}

这里使用了result.normal.multiplyScalar(result.depth)的方法,其中result.normal表示碰撞的法向量,result.depth表示碰撞的深度。将这两个值相乘,可以得到需要进行调整的距离向量,将其作为参数传入playerCollidertranslate方法中,就可以将玩家位置进行调整,避免穿模现象的发生。

3,源代码

<script type="importmap">{"imports": {"three": "./libs/jsm/three.module.js","three/addons/": "./libs/jsm/"}}</script><script type="module">import * as THREE from 'three';import {OrbitControls} from './libs/jsm/OrbitControls.js';import {GLTFLoader} from './libs/jsm/GLTFLoader.js';import {Octree} from './libs/jsm/math/Octree.js';import {Capsule} from './libs/jsm/math/Capsule.js';import {GUI} from './libs/jsm/lil-gui.module.min.js';import Stats from './libs/jsm/stats.module.js';const clock = new THREE.Clock();const scene = new THREE.Scene();scene.background = new THREE.Color(0x88ccee);scene.fog = new THREE.Fog(0x88ccee, 0, 50);const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);camera.rotation.order = 'YXZ';camera.position.set(0, 0, 0);const fillLight1 = new THREE.HemisphereLight(0x4488bb, 0x002244, 2.5);fillLight1.position.set(2, 1, 1);// scene.add(fillLight1);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(-5, 25, -1);directionalLight.castShadow = true;directionalLight.shadow.camera.near = 0.01;directionalLight.shadow.camera.far = 500;directionalLight.shadow.camera.right = 30;directionalLight.shadow.camera.left = -30;directionalLight.shadow.camera.top = 30;directionalLight.shadow.camera.bottom = -30;directionalLight.shadow.mapSize.width = 1024;directionalLight.shadow.mapSize.height = 1024;directionalLight.shadow.radius = 4;directionalLight.shadow.bias = -0.00006;scene.add(directionalLight);const container = document.getElementById('container');const renderer = new THREE.WebGLRenderer({antialias: true});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.VSMShadowMap;renderer.outputEncoding = THREE.sRGBEncoding;renderer.toneMapping = THREE.ACESFilmicToneMapping;container.appendChild(renderer.domElement);var oControls = new OrbitControls(camera, container);oControls.update();const stats = new Stats();stats.domElement.style.position = 'absolute';stats.domElement.style.top = '0px';container.appendChild(stats.domElement);scene.add(new THREE.AmbientLight(0xe0ffff, 1))// scene.add(new THREE.HemisphereLight("#ffffff", "#6b6b6b", 2));const geometry = new THREE.CapsuleGeometry(0.35, 0.7, 4, 8);const material = new THREE.MeshBasicMaterial({color: 0x00ff00,wireframe: true});const capsule = new THREE.Mesh(geometry, material);scene.add(capsule);const GRAVITY = 30;const NUM_SPHERES = 100;const SPHERE_RADIUS = 0.2;const STEPS_PER_FRAME = 5;const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5);const sphereMaterial = new THREE.MeshLambertMaterial({color: 0xbbbb44});let peopleObj, activeAction, peopleAnimations, tween, mixer, peopleBox;let mixerArr = [];const spheres = [];let sphereIdx = 0;const worldOctree = new Octree();const playerCollider = new Capsule(new THREE.Vector3(0, 0.35, 0), new THREE.Vector3(0, 1, 0), 0.26);const playerVelocity = new THREE.Vector3();const playerDirection = new THREE.Vector3();let playerOnFloor = false;let mouseTime = 0;const keyStates = {};const vector1 = new THREE.Vector3();const vector2 = new THREE.Vector3();const vector3 = new THREE.Vector3();document.addEventListener('keydown', (event) => {keyStates[event.code] = true;});document.addEventListener('keyup', (event) => {keyStates[event.code] = false;});window.addEventListener('resize', onWindowResize);function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);}function playerCollisions() {const result = worldOctree.capsuleIntersect(playerCollider);playerOnFloor = false;if (result) {playerOnFloor = result.normal.y > 0;if (!playerOnFloor) {playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));}playerCollider.translate(result.normal.multiplyScalar(result.depth));}}function updatePlayer(deltaTime) {let damping = Math.exp(-4 * deltaTime) - 1;if (!playerOnFloor) {playerVelocity.y -= GRAVITY * deltaTime;// small air resistancedamping *= 0.1;}playerVelocity.addScaledVector(playerVelocity, damping);const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);playerCollider.translate(deltaPosition);playerCollisions();let resPos = playerCollider.end;capsule.position.copy(resPos);if (peopleObj) {peopleObj.position.copy(resPos.clone().setY(resPos.y - 0.7));let pos = peopleObj.position.clone();pos = pos.setY(pos.y + 0.7)oControls.target = pos;oControls.update();} else {oControls.object.position.set(0, 1, -2)oControls.update(true); // 初始化}}function getForwardVector() {capsule.getWorldDirection(playerDirection);playerDirection.y = 0;playerDirection.normalize();return playerDirection;}function getSideVector() {capsule.getWorldDirection(playerDirection);playerDirection.y = 0;playerDirection.normalize();playerDirection.cross(capsule.up);return playerDirection;}function controls(deltaTime) {// console.log(camera.rotation)capsule.rotation.y = camera.rotation.y;// gives a bit of air controlconst speedDelta = deltaTime * (playerOnFloor ? 25 : 8);if (keyStates['KeyW']) {playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta));}if (keyStates['KeyS']) {playerVelocity.add(getForwardVector().multiplyScalar(speedDelta));}if (keyStates['KeyA']) {playerVelocity.add(getSideVector().multiplyScalar(speedDelta));}if (keyStates['KeyD']) {playerVelocity.add(getSideVector().multiplyScalar(-speedDelta));}if (playerOnFloor) {if (keyStates['Space']) {playerVelocity.y = 8;}}}// 创建文字精灵var getTextCanvas = function(text) {let option = {fontFamily: 'Arial',fontSize: 30,fontWeight: 'bold',color: '#ffffff',actualFontSize: 0.08,},canvas, context, textWidth, texture, materialObj, spriteObj;canvas = document.createElement('canvas');context = canvas.getContext('2d');// 先设置字体大小后获取文本宽度context.font = option.fontWeight + ' ' + option.fontSize + 'px ' + option.fontFamily;textWidth = context.measureText(text).width;canvas.width = textWidth;canvas.height = option.fontSize;context.textAlign = "center";context.textBaseline = "middle";context.fillStyle = option.color;context.font = option.fontWeight + ' ' + option.fontSize + 'px ' + option.fontFamily;context.fillText(text, textWidth / 2, option.fontSize / 1.8);texture = new THREE.CanvasTexture(canvas);materialObj = new THREE.SpriteMaterial({map: texture});spriteObj = new THREE.Sprite(materialObj);spriteObj.scale.set(textWidth / option.fontSize * option.actualFontSize, option.actualFontSize, option.actualFontSize);return spriteObj;}const loader = new GLTFLoader().setPath('assets/models/');loader.load('collision-world.glb', (gltf) => {let obj = gltf.scene;obj.remove(obj.getObjectByName("polySurface150"));scene.add(obj);worldOctree.fromGraphNode(obj);gltf.scene.traverse(child => {if (child.isMesh) {child.castShadow = true;child.receiveShadow = true;if (child.material.map) {child.material.map.anisotropy = 4;}}});animate();});let gloader = new GLTFLoader()gloader.load("assets/models/man/people.glb", result => {peopleObj = result.scene;peopleObj.scale.set(0.7, 0.7, 0.7);peopleAnimations = result.animations;// 主人物名字let spriteText = getTextCanvas("左本Web3D");spriteText.position.set(0, 1.95, 0);peopleObj.add(spriteText);const bbox = new THREE.Box3().setFromObject(peopleObj);// 获取包围盒的中心点const center = new THREE.Vector3();bbox.getCenter(center);// 将物体移动到中心点peopleObj.position.sub(center);// 组合对象添加到场景中scene.add(peopleObj);mixer = new THREE.AnimationMixer(peopleObj);mixerArr.push(mixer)activeAction = mixer.clipAction(peopleAnimations[1]);activeAction.play();})function teleportPlayerIfOob() {if (camera.position.y <= -25) {playerCollider.start.set(0, 0.35, 0);playerCollider.end.set(0, 1, 0);playerCollider.radius = 0.35;camera.position.copy(playerCollider.end);camera.rotation.set(0, 0, 0);}}function animate() {const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;// we look for collisions in substeps to mitigate the risk of// an object traversing another too quickly for detection.for (let i = 0; i < mixerArr.length; i++) {mixerArr[i].update(clock.getDelta());}for (let i = 0; i < STEPS_PER_FRAME; i++) {controls(deltaTime);updatePlayer(deltaTime);// updateSpheres(deltaTime);// teleportPlayerIfOob();}renderer.render(scene, camera);stats.update();requestAnimationFrame(animate);}</script>

Threejs实现键盘控制人物行走跳动/Capsule碰撞体(胶囊体)/碰撞检测(Octree八叉树)/游戏第三人称/镜头跟随人物移动

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。