上回继续,今天继续捣腾动画,上一节咱们让汽车的轮子动了起来,回顾一下核心代码:

//轮子转动
const wheelAnimation = (scene, wheels) => {
//定义一个动画,每秒30帧,绕y轴转动
const animWheel = new BABYLON.Animation("wheelAnimation", "rotation.y",
30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE); //动画关键帧
const wheelKeys = []; //起始帧
wheelKeys.push({
frame: 0,
value: 0
}); //截止帧(即:第30帧,转到360度)
wheelKeys.push({
frame: 30,
value: 2 * Math.PI
}); //设置关键帧
animWheel.setKeys(wheelKeys); let result = []; for (let i = 0; i < wheels.length; i++) {
//将wheel与动画关联
wheels[i].animations = [];
wheels[i].animations.push(animWheel); //开始动画,最后的true表示循环播放
result.push(scene.beginAnimation(wheels[i], 0, 30, true));
} return result;
}

可能有朋友要问了,这里返回的result有啥用?先卖个关子,后面再讲。

模仿这段代码,略做修改,我们就能让汽车在屏幕上动起来:

//car运动
const carAnimation = (scene, car) => {
const animCar = new BABYLON.Animation("carAnimation", "position.x",
30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE); //动画关键帧
const carKeys = []; //起始帧
carKeys.push({
frame: 0,
value: -1
}); carKeys.push({
frame: 30,
value: -0.9
}); //截止帧(即:第120帧,走到x=8)
carKeys.push({
frame: 120,
value: 0.9
}); carKeys.push({
frame: 150,
value: 1
}); //设置关键帧
animCar.setKeys(carKeys); car.animations = [];
car.animations.push(animCar); //开始动画
return scene.beginAnimation(car, 0, 150, true); }

一、如何控制动画暂停/播放?

接下来说说scene.beginAnimation 返回值有啥用?根据官方文档描述,这个返回对象Animatable提供了几个有用的方法:

  • pause()
  • restart()
  • stop()
  • reset()

利用这些方法,接下来加点鼠标互动:

  • ALT+鼠标单击,暂停/继续播放 汽车运动;
  • SHIFT+鼠标单击,暂停/继续播放 车轮转动
//声明2个变量,方便控制动画
let wheelAnimatables, carAnimatable; const createScene = () => {
const scene = new BABYLON.Scene(engine); ... wheelAnimatables = wheelAnimation(scene, wheels);
carAnimatable = carAnimation(scene, car); ... return scene;
} ...
window.addEventListener("click", (e) => {
if (e.altKey) {
if (carAnimatable._paused) {
carAnimatable.restart();
}
else {
carAnimatable.pause();
}
} if (e.shiftKey) {
let first = wheelAnimatables[0];
if (first._paused) {
wheelAnimatables.forEach(element => {
element.restart();
});
}
else {
wheelAnimatables.forEach(element => {
element.pause();
});
}
}
});

在线地址:https://yjmyzz.github.io/babylon_js_study/day06/01.html

为了方便后面复用这个小汽车,我们把它在playground上导出为babylon文件(参考前文提到的做法),核心代码如下:

const createScene = () => {
const scene = new BABYLON.Scene(engine); const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 2, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0)); const car = buildCar();
const wheels = buildWheels(car);
car.rotation.x = -Math.PI / 2;
wheelAnimation(scene, wheels); return scene;
} const buildCar = () => {
//base
const outline = [
new BABYLON.Vector3(-0.3, 0, -0.1),
new BABYLON.Vector3(0.2, 0, -0.1),
] //curved front
for (let i = 0; i < 20; i++) {
outline.push(new BABYLON.Vector3(0.2 * Math.cos(i * Math.PI / 40), 0, 0.2 * Math.sin(i * Math.PI / 40) - 0.1));
} //top
outline.push(new BABYLON.Vector3(0, 0, 0.1));
outline.push(new BABYLON.Vector3(-0.3, 0, 0.1)); //car face UVs
const faceUV = [];
faceUV[0] = new BABYLON.Vector4(0, 0.5, 0.38, 1);
faceUV[1] = new BABYLON.Vector4(0, 0, 1, 0.5);
faceUV[2] = new BABYLON.Vector4(0.38, 1, 0, 0.5); //car material
const carMat = new BABYLON.StandardMaterial("carMat");
carMat.diffuseTexture = new BABYLON.Texture("https://yjmyzz.github.io/babylon_js_study/assets/img/car.png"); //back formed automatically
const car = BABYLON.MeshBuilder.ExtrudePolygon("car", { shape: outline, depth: 0.2, faceUV: faceUV, wrap: true });
car.material = carMat; return car;
} const buildWheels = (car) => {
//wheel face UVs
const wheelUV = [];
wheelUV[0] = new BABYLON.Vector4(0, 0, 1, 1);
wheelUV[1] = new BABYLON.Vector4(0, 0.5, 0, 0.5);
wheelUV[2] = new BABYLON.Vector4(0, 0, 1, 1); //car material
const wheelMat = new BABYLON.StandardMaterial("wheelMat");
wheelMat.diffuseTexture = new BABYLON.Texture("https://yjmyzz.github.io/babylon_js_study/assets/img/wheel.png"); const wheelRB = BABYLON.MeshBuilder.CreateCylinder("wheelRB", { diameter: 0.125, height: 0.05, faceUV: wheelUV })
wheelRB.material = wheelMat; wheelRB.parent = car;
wheelRB.position.z = -0.1;
wheelRB.position.x = -0.2;
wheelRB.position.y = 0.035; const wheelRF = wheelRB.clone("wheelRF");
wheelRF.position.x = 0.1; const wheelLB = wheelRB.clone("wheelLB");
wheelLB.position.y = -0.2 - 0.035; const wheelLF = wheelRF.clone("wheelLF");
wheelLF.position.y = -0.2 - 0.035; const wheels = [];
wheels.push(wheelRB);
wheels.push(wheelRF);
wheels.push(wheelLB);
wheels.push(wheelLF); return wheels;
} //轮子转动
const wheelAnimation = (scene, wheels) => {
//定义一个动画,每秒30帧,绕y轴转动
const animWheel = new BABYLON.Animation("wheelAnimation", "rotation.y",
30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE); //动画关键帧
const wheelKeys = []; //起始帧
wheelKeys.push({
frame: 0,
value: 0
}); //截止帧(即:第30帧,转到360度)
wheelKeys.push({
frame: 30,
value: 2 * Math.PI
}); //设置关键帧
animWheel.setKeys(wheelKeys); for (let i = 0; i < wheels.length; i++) {
//将wheel与动画关联
wheels[i].animations = [];
wheels[i].animations.push(animWheel); //开始动画,最后的true表示循环播放
scene.beginAnimation(wheels[i], 0, 30, true);
}
}

结合先前画的房屋,可以让小汽车在房屋之间动起来:

在线地址:https://yjmyzz.github.io/babylon_js_study/day06/02.html (加载的资源比较多,网页首次打开可能比较慢)

二、如何实现复杂的运动轨迹?

2.1 理解movePOV

babylon.js提供了movePOV(rightSpeed, upSpeed, forwardSpeed)方法,可以让对象 朝右(x轴负方向)、朝上(z轴正方向)、朝前(z轴负方向) 运动。

下面分别演示一下,基本代码如下:

const createScene = () => {
const scene = new BABYLON.Scene(engine); const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 15, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0)); BABYLON.SceneLoader.ImportMeshAsync("him", "../assets/glb/", "dude.babylon").then((result) => {
var dude = result.meshes[0];
dude.scaling = new BABYLON.Vector3(0.02, 0.02, 0.02);
scene.beginAnimation(result.skeletons[0], 0, 100, true, 1.0); //向前运动
const rightSpeed = 0, upSpeed = 0, forwardSpeed = 0.01;
let distance = 0; //控制下一帧的行为
scene.onBeforeRenderObservable.add(() => {
//让人物动起来
dude.movePOV(rightSpeed, upSpeed, forwardSpeed);
distance += 0.002;
if (distance > 1) {
//走的距离>1地,复位
distance = 0;
dude.position = BABYLON.Vector3.Zero();
} });
}); showAxis(4, scene);
return scene;
}

向前(z轴负方向):

注:此处的向前,是从人物自身的视角来看的,从观察者(即电脑前的我们)角度来看,即为向我们走来。

向右(x轴负方向):

//向右运动
const rightSpeed = 0.01, upSpeed = 0, forwardSpeed = 0.00;

注:由于人物动画走路朝向的设计原因,仅设置rightSpeed有值,会让人横向移动(类似螃蟹),有点奇怪(马上会讲到rotate方法,可以解决这个问题)

向上(Y轴正方向):

//向上运动
const rightSpeed = 0, upSpeed = 0.01, forwardSpeed = 0.00;

2.2 理解rotate

前进过程中如何转向呢?回想下开汽车时,我们用方向盘来转向,babylon.js中自然也有类似方法,即:rotate方法

BABYLON.SceneLoader.ImportMeshAsync("him", "../assets/glb/", "dude.babylon").then((result) => {
var dude = result.meshes[0];
dude.scaling = new BABYLON.Vector3(0.02, 0.02, 0.02);
scene.beginAnimation(result.skeletons[0], 0, 100, true, 1.0); //向前运动
const rightSpeed = 0, upSpeed = 0, forwardSpeed = 0.01; let distance = 0, turnFlag = false; //控制下一帧的行为
scene.onBeforeRenderObservable.add(() => {
//让人物动起来
dude.movePOV(rightSpeed, upSpeed, forwardSpeed);
distance += 0.002; //走到0.5距离时,右转90度
if (!turnFlag && Math.abs(distance - 0.5) <= 0.000001) {
dude.rotate(BABYLON.Axis.Y, Math.PI / 2, BABYLON.Space.LOCAL);
turnFlag = true;
} if (distance > 1) {
//走的距离>1地,复位
distance = 0;
dude.position = BABYLON.Vector3.Zero();
dude.rotation = BABYLON.Vector3.Zero();
turnFlag = false;
} });
});

在线地址 :https://yjmyzz.github.io/babylon_js_study/day06/03.html

3 生成复杂运动轨迹

结合movePOV 以及rotate,就能实现相对较为复杂的运动轨迹

const createScene = () => {
const scene = new BABYLON.Scene(engine); const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 15, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0)); //画1个正方形(辅助观察人物运动路径)
const squarePoints = [new BABYLON.Vector3(-2, 0, 2),
new BABYLON.Vector3(2, 0, 2),
new BABYLON.Vector3(2, 0, -2),
new BABYLON.Vector3(-2, 0, -2),
new BABYLON.Vector3(-2, 0, 2)];
const square = BABYLON.MeshBuilder.CreateLines("square", { points: squarePoints }); //走距离dist后,转方向trun(角度)
const trunPoint = function (turn, dist) {
this.turn = turn;
this.dist = dist;
} //运动轨迹(关键转向点)
//注:因本例中的人物原型太大,加载后缩小处理了,所以这里的距离也要回应缩小(即:*0.2)
const track = [
new trunPoint(Math.PI / 2, 4 * 0.2),
new trunPoint(Math.PI / 2, 8 * 0.2),
new trunPoint(Math.PI / 2, 12 * 0.2),
new trunPoint(Math.PI / 2, 16 * 0.2)
] BABYLON.SceneLoader.ImportMeshAsync("him", "../assets/glb/", "dude.babylon").then((result) => {
var dude = result.meshes[0];
dude.scaling = new BABYLON.Vector3(0.02, 0.02, 0.02);
dude.position = new BABYLON.Vector3(2, 0, 2);
scene.beginAnimation(result.skeletons[0], 0, 100, true, 1.0); //向前运动
const rightSpeed = 0.00, upSpeed = 0.00, forwardSpeed = 0.01; //distance记录走过的距离,p为track中转向点的下标(即:第几个转向点)
let distance = 0, p = 0; //控制下一帧的行为
scene.onBeforeRenderObservable.add(() => {
//让人物动起来
dude.movePOV(rightSpeed, upSpeed, forwardSpeed);
distance += 0.002;
//判断关键转向点
console.log(distance);
if (distance > track[p].dist) {
dude.rotate(BABYLON.Axis.Y, track[p].turn, BABYLON.Space.LOCAL);
p += 1;
p %= track.length; if (p === 0) {
//走到最开始的位置时,复位
distance = 0;
dude.position = new BABYLON.Vector3(2, 0, 2);
dude.rotation = BABYLON.Vector3.Zero();
}
}
});
}); showAxis(4, scene);
return scene;
}

在线地址:https://yjmyzz.github.io/babylon_js_study/day06/04.html

4、碰撞检测 

每个mesh对象在babylon.js中实际占据着一块矩形立体空间

当2个对象碰撞到时,只要这2个立体空间有重叠即为发生了碰撞,对应的方法为 intersectsMesh,下面给1个简单的演示:

BABYLON.SceneLoader.ImportMeshAsync("", "../assets/glb/", "car.babylon").then((result) => {
var car1 = scene.getMeshByName("car");
car1.position = new BABYLON.Vector3(-6, 0, 0);
car1.scaling = new BABYLON.Vector3(4, 4, 4); const wheelRB = scene.getMeshByName("wheelRB");
const wheelRF = scene.getMeshByName("wheelRF");
const wheelLB = scene.getMeshByName("wheelLB");
const wheelLF = scene.getMeshByName("wheelLF");
let wheels = [wheelRB, wheelRF, wheelLB, wheelLF];
let wheelAnimatables = [];
for (let i = 0; i < wheels.length; i++) {
wheelAnimatables.push(scene.beginAnimation(wheels[i], 0, 30, true));
} const animCar = new BABYLON.Animation("carAnimation", "position.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
const carKeys = [];
carKeys.push({
frame: 0,
value: -6
});
carKeys.push({
frame: 120,
value: 6
}); animCar.setKeys(carKeys); car1.animations = [];
car1.animations.push(animCar); let carAnimable = scene.beginAnimation(car1, 0, 120, true); //复制出第2辆车,挡在路上
var car2 = car1.clone("car2");
car2.position = new BABYLON.Vector3(2, 0, 0.4);
car2.rotation.y = Math.PI;
car2.scaling = car1.scaling; //注:第2辆车刚复制出来的瞬间,位置与第1辆车就重合了,会导致一出场就碰了,因此要借助ready把首次碰排除掉
let ready = false; //控制下一帧的行为
scene.onBeforeRenderObservable.add(() => {
//发生碰撞
if (car1.intersectsMesh(car2)) {
if (!ready) {
ready = true;
return;
} if (ready){
//停止动画
carAnimable.stop();
//模拟第1辆车,被撞飞
car1.rotate(BABYLON.Axis.Y, Math.PI*0.4, BABYLON.Space.LOCAL);
car1.position.x -= 0.2;
car1.position.y += 0.5;
return; } } });
});

在线地址 : https://yjmyzz.github.io/babylon_js_study/day06/05.html

参考文档:

https://doc.babylonjs.com/features/introductionToFeatures/chap3/walkpath

https://doc.babylonjs.com/features/introductionToFeatures/chap4/mesh_intersect

babylon.js 学习笔记(6)的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. Vue.js学习笔记(2)vue-router

    vue中vue-router的使用:

  3. JS 学习笔记--9---变量-作用域-内存相关

    JS 中变量和其它语言中变量最大的区别就是,JS 是松散型语言,决定了它只是在某一个特定时间保存某一特定的值的一个名字而已.由于在定义变量的时候不需要显示规定必须保存某种类型的值,故变量的值以及保存的 ...

  4. WebGL three.js学习笔记 使用粒子系统模拟时空隧道(虫洞)

    WebGL three.js学习笔记 使用粒子系统模拟时空隧道 本例的运行结果如图: 时空隧道demo演示 Demo地址:https://nsytsqdtn.github.io/demo/sprite ...

  5. WebGL three.js学习笔记 法向量网格材质MeshNormalMaterial的介绍和创建360度全景天空盒的方法

    WebGL学习----Three.js学习笔记(5) 点击查看demo演示 Demo地址:https://nsytsqdtn.github.io/demo/360/360 简单网格材质 MeshNor ...

  6. WebGL three.js学习笔记 创建three.js代码的基本框架

    WebGL学习----Three.js学习笔记(1) webgl介绍 WebGL是一种3D绘图协议,它把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的 ...

  7. vue.js 学习笔记3——TypeScript

    目录 vue.js 学习笔记3--TypeScript 工具 基础类型 数组 元组 枚举 字面量 接口 类类型 类类型要素 函数 函数参数 this对象和类型 重载 迭代器 Symbol.iterat ...

  8. 2019-4-29 js学习笔记

    js学习笔记一:js数据类型   1:基本数据类型       number类型(整数,小数)      String类型          boolean类型        NaN类型其实是一个nu ...

  9. 一点感悟:《Node.js学习笔记》star数突破1000+

    写作背景 笔者前年开始撰写的<Node.js学习笔记> github star 数突破了1000,算是个里程碑吧. 从第一次提交(2016.11.03)到现在,1年半过去了.突然有些感慨, ...

  10. JS学习笔记5_DOM

    1.DOM节点的常用属性(所有节点都支持) nodeType:元素1,属性2,文本3 nodeName:元素标签名的大写形式 nodeValue:元素节点为null,文本节点为文本内容,属性节点为属性 ...

随机推荐

  1. 打造企业级AI文案助手:GPT-J+Flask全栈开发实战

    一.智能文案革命的序幕:为什么需要AI文案助手? 在数字化营销时代,内容生产效率成为企业核心竞争力.据统计,营销人员平均每天需要撰写3.2篇文案,而传统人工创作存在三大痛点: 效率瓶颈:创意构思到成文 ...

  2. 从零开始的PHP原生反序列化漏洞

    1.写在前面 OK 兄弟们,这几天一直在面试,发现很多 HR 喜欢问反序列化相关的内容,今天咱们就从最简单的 PHP 原生反序列化入手,带大家入门反序列化 2.PHP 序列化 在 PHP 中,有反序列 ...

  3. OpenDeepWiki一分钟即可让您的仓库自动生成精美文档

    引言 还在为编写详尽的项目文档而头疼吗?是否曾经面对一个陌生的代码仓库,却不知从何下手理解其结构和逻辑?在软件开发的世界里,高质量文档就像一张详细的地图,引导开发者穿越复杂代码的迷宫.然而,编写这样的 ...

  4. 鸿蒙NEXT开发实战教程:仿抖音短视频

    今天的实战教程是简单模仿一下抖音短视频,主要是首页部分的内容,先看效果图:   下面为大家讲解这个项目的详细教程. tabbar Tabbar的难点在于中间有个发布按钮,思路是我们可以在tabbar里 ...

  5. Webkit 实现页面滚动条美化

    当页面或者某个容器布局内容超出过后, 就会有滚动条, 但默认的有点丑, 经常需要自己来美化一下, 这里做个笔记吧. /* 美化全局的滚动条 */ ::-webkit-scrollbar { width ...

  6. AI 技术发展简史

    AI 智能体开发指南 AI技术发展简史 一.AI的定义与核心目标 人工智能(Artificial Intelligence,AI)自诞生以来,一直是计算机科学和软件工程领域的重要研究方向.随着计算能力 ...

  7. trim()与String index out of range: 0

    当使用trim()处理字符串后,发现 String index out of range: 0报错? 而且觉得代码无懈可击?一时想不出来什么时候会为空? 注意特殊情况处理的字符元素都是空格,eg:&q ...

  8. P7185 [CRCI2008-2009] CIJEVI

    hack&&虐题解爽! 私题里有 \(3\) 组 hack. 还得是中模拟. 从 M 或者 Z 遍历整个管道,若遇到断点.断点有两种情况,若与 M 或者 Z 不直接接触,枚举附近的一个 ...

  9. 把数据库表的信息添加到list集合里面

    把数据库表里面的信息添加到集合里面并且打印出来: 数据库表的内容:  java代码逻辑处理: 1 public static void main(String[] args) { 2 3 Connec ...

  10. 阿里微服务解决方案-Alibaba Cloud之集成Nacos(服务注册与发现)(三)

    一.集成 Nacos(服务注册与发现) 1.1 下载 Nacos Nacos下载地址 1.2 下载后解压到本地 1.3 启动 Nacos 启动成功界面 输入 http://127.0.0.1:8848 ...