一.前言
我们在玩一些通过键盘和鼠标控制模型移动的游戏,例如赛车类的或者人物之类(比如吃鸡吧),没有注意到模型运动的时候有一些特征。这些都是比较简单逻辑没有涉及到物理引擎,而实际开发中会用到物理引擎,比如刚体运动,限制,软体啥等,车子的运动也是通过施加力的作用让它加速或者旋转,这里就不详细展开说了。顺便说一下babylon.js的用到的HK物理引擎真的太棒了,但是遇到了一点小bug,希望官方快点修复
一、当我们往前移动的时候或者只有角速度的时候,汽车的速度是由0慢慢加速然后打到一个速度最大值后速度就不再增加;当我们停止前进,车子就会慢慢减速直到停止;
二、当汽车在在左右旋转的时候,如果车子没有移动(指的是前进),车子是没有办法旋转的,也就是绕着车子整体的Y轴(上方向)旋转,而且汽车在拐弯的时候,它旋转的角速度和线路度是呈正相关的;
三、车轮的转速和旋转的方向和当前车子速度也有关系,我们只需给车轮增加一个绕着车轮本地坐标的Z轴方向一个旋转动画即可
四、当车子前进速度很快,却想让快速停下来可以按下后退来实现刹车效果,比如前进速度很快时,可以按下后退实现刹车;当后退速度很快可以按下前进来实现反向刹车
好了,分析完毕我们开始吧
1.对于场景必要的比如,渲染引擎、相机、灯光、模型加载啥的这些基本都是必须的就不写了
补充说明:我们需要两台相机,一个是第一人称的,另一个是和车子绑定的
// 车轮动画
this.tireAnimationGroup = new AnimationGroup('tireGroupAnimation')
this.tireAnimation = initTireAnimation()
// 键盘事件
var inputMap: any = {};
// 初始值
var carSpeed = 0;
const acceleration = 0.002
var carRotationSpeed = 0.02;
const maxSpeed = 0.8
const braker = (keydown: boolean) => {
// 阻力作用下停车
if (keydown === false && carSpeed > 0) {
carSpeed = Math.max(0, carSpeed - acceleration * 1.5)
} else if (keydown === false && carSpeed < 0) {
carSpeed = Math.min(0, carSpeed + acceleration * 1.5)
}
}
scene.actionManager = new ActionManager(scene);
scene.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnKeyDownTrigger, (evt: any) => {
inputMap[evt.sourceEvent.key.toLowerCase()] = evt.sourceEvent.type == "keydown";
}));
scene.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnKeyUpTrigger, function (evt: any) {
inputMap[evt.sourceEvent.key.toLowerCase()] = evt.sourceEvent.type == "keydown";
}));
braker 这个函数是比较关键的,当我们车加速到一定的速度,然后放开键盘,或者是键盘只按下了A或者D也就是方向,此时车子需要在负的加速度下慢慢减速,直到车子速度为0.
这里没有给车子别的加速度,比如不同的挡(手动挡),加速度应该是不一样的,你们也可以加上其他的按键加上不同的加速度,实现换挡的效果
2.在每一帧里调用,在三维场景模型的位置改变其实就是坐标的改变
补充:当车子移动的时候,顺便把第一人称的相机位置也更新一下,下车的时候,第一人称相机也就是停车后的位置了
scene.onBeforeRenderObservable.add(() => {
var keydown = false;
// 前进,给车动力
if (inputMap["s"]) {
carSpeed = Math.min(maxSpeed, carSpeed + acceleration * 2)
keydown = true;
}
// 反向给力
if (inputMap["w"]) {
carSpeed = Math.max(-maxSpeed, carSpeed - acceleration)
keydown = true;
}
// 向左拐
if (inputMap["a"]) {
this.car.rotate(Vector3.Up(), carRotationSpeed * carSpeed * 2);
if (!inputMap["w"] && !inputMap["s"]) {
braker(false)
}
keydown = true;
}
// 向右拐
if (inputMap["d"]) {
this.car.rotate(Vector3.Up(), -carRotationSpeed * carSpeed * 2);
if (!inputMap["w"] && !inputMap["s"]) {
braker(false)
}
keydown = true;
}
// 阻力作用下停车
braker(keydown)
// 速度改变影响车子的位移,car.forward是模型的朝向,carSpeed具有大小,这样就
//得到一个具有大小和方向的速度
this.car.moveWithCollisions(this.car.forward.scale(carSpeed));
// 在车子开动的时候,顺便把第一人称的相机位置也更新一下
if (scene.activeCamera instanceof ArcRotateCamera) {
const carPosition = this.car.position.clone()
carPosition.y += 2
fcamera.position = carPosition
}
// 车轮转起来,就是让车轮绕着其本地坐标的X轴旋转,根据速度改变speedRatio
this.tireAnimationGroup.play(true)
this.tireAnimationGroup.speedRatio = -carSpeed * 14
});
总结:在文中只是用到一个核心变量carSpeed ,因为车子的运动,正负决定前进和后退,再结合car.forward也就是模型的方向就可以得到一个具有大小和方向的速度,代码的作用在注释都写的比较明白了