摄像机、网格碰撞和重力
你玩过第一人称射击游戏(FPS)吗?在本教程中,我们将模拟FPS的摄影机移动:摄影机位于地板上,与地面碰撞,并可能与场景中的任何对象碰撞。
如何实现?
为了实现这一功能,我们必须执行3个简单步骤:
1.定义并应用重力
首先要做的是定义重力矢量,定义重力。
Babylon.js的Scene类具有重力属性,可以应用于您之前在代码中定义的任何相机。这将沿指定的方向和速度移动摄影机(Vector3对象),除非摄影机的椭球体(参见下面的第2步)在checkCollisions设置为true的情况下与该方向上的另一个网格(如地面网格)发生碰撞。
Scene设置重力属性
scene.gravity = new BABYLON.Vector3(0, -0.15, 0);
摄像机开启重力属性
camera.applyGravity = true;
在现实世界中,重力是一种向下施加的力,即沿Y轴的负方向施加的力。在地球上,这一力大约为9.81m/s²。下落物体在下落时会加速,因此需要1秒才能完全达到这一速度,然后在2秒后速度达到19.62m/s,在3秒后达到29.43m/s... 在大气层中,风阻力将最终与这一力相匹配,自由落体速度也会停止增加(达到“终极速度”)。
然而,Babylon.js遵循了一个简单得多的引力模型 - scene.gravity表示恒定的速度,而不是加速度,它是以单位/帧而不是米/秒来测量的。渲染每个帧时,应用该重力的摄影机将沿每个轴移动矢量值(通常x和z设置为0,但您可以在任何方向上使用“重力”!),直到检测到碰撞。
虽然Babylon.js单元距离(unit)没有直接对应物理世界的东西,但在默认的相机视场下,1个单元距离=1米的近似值是一个相当标准的假设。因此,如果想要模拟地球重力,你需要对每秒渲染的帧数做出一些假设,并计算一个合适的矢量:
const assumedFramesPerSecond = 60;
const earthGravity = -9.81;
scene.gravity = new BABYLON.Vector3(0, earthGravity / assumedFramesPerSecond, 0);
由于这是每帧计算一次,所以相机实际上并没有“移动”,而是沿着重力矢量的方向进行微小的“跳跃”。如果您依靠碰撞检测来确定相机(或者更确切地说,是为此目的附着在相机上的网格)是否“进入”或“退出”了其他网格(例如,“地面”层下的一个平面,用于感测下落的角色并重置游戏),这一点可能很重要。根据您选择的重力、起始高程以及“触发”网格的位置和高度,相机可能会直接跳过触发网格,而不会与之“相交”。因此请务必检查Math类,以确保添加到起始高程的场景重力的至少一个倍数会与触发网格相交。
如果你需要更准确地表示引力(或其他力),你可以使用Babylon集成的物理引擎,或者添加自定义的物理引擎。
警告:
如在一对象上添加physicsimpostor(自定义的外部物理引擎)并同时启用碰撞(collision),可能会导致意外行为。
2. 定义椭圆球体
下一个重要步骤是定义相机周围的椭圆球体。这个椭圆球体代表了我们玩家的尺寸:当对象网格与这个椭圆球体接触时,会引发碰撞事件,防止我们的相机离这个对象网格太近:
Babylon.js相机上的椭圆球属性默认为尺寸大小为(0.5、1、0.5)。更改这些值会使您变高、变大、变小、变瘦。在下面的示例中,我们将使相机的椭圆球体比默认的更胖:
//Set the ellipsoid around the camera (e.g. your player's size)
camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
请注意,摄像机的椭圆球体应该是偏移的,以便是视点(眼睛)始终位于椭球体的顶部。
可以通过更新椭球体偏移特性来控制此行为。
计算方法如下:
finalPosition = position - vec3(0, ellipsoid.y, 0) + ellipsoidOffset
3. 应用碰撞
我们的最后一步是声明我们对感知场景中的碰撞感兴趣:
scene.collisionsEnabled = true;
camera.checkCollisions = true;
以及要声明哪些网格可能与我们的相机发生碰撞(box和ground):
ground.checkCollisions = true;
box.checkCollisions = true;
非常简单,以下是案例代码:
4.物体与物体碰撞
你可以通过对物体网格(Mesh)的ellipsoid属性和moveWithCollisions(速度)函数的使用来达到相同的效果。此函数将尝试根据给定的速度移动Mesh,并检查当前模型与激活了checkCollisions的所有模型之间是否产生了碰撞。
也可以使用mesh.ellipsoidOffse来移动网格上的椭球体(默认情况下,椭球体以网格为中心)。
代码如下:
const speedCharacter = 8;
const gravity = 0.15;
const character = Your mesh;
character.ellipsoid = new BABYLON.Vector3(0.5, 1.0, 0.5);
character.ellipsoidOffset = new BABYLON.Vector3(0, 1.0, 0);
const forwards = new BABYLON.Vector3(parseFloat(Math.sin(character.rotation.y)) / speedCharacter, gravity, parseFloat(Math.cos(character.rotation.y)) / speedCharacter);
forwards.negate();
character.moveWithCollisions(forwards);
// or
const backwards = new BABYLON.Vector3(parseFloat(Math.sin(character.rotation.y)) / speedCharacter, -gravity, parseFloat(Math.cos(character.rotation.y)) / speedCharacter);
character.moveWithCollisions(backwards);
案例如下:
弧形旋转摄像机(Arc Rotate Camera)的碰撞
ArcRotateCamera也可以检查碰撞,但碰撞发生时,该相机不会移动。
如要激活碰撞,只需将camera.checkCollisions设置为true。还可以定义碰撞半径:
camera.collisionRadius = new BABYLON.Vector3(0.5, 0.5, 0.5);
其他类型的碰撞
太好了,现在你可以开发一款真正的FPS游戏了!但也许你想知道一个网格何时与另一个网格碰撞?如果你对此感兴趣,你可以在这里学习:网格碰撞。