在上一篇文章,我们成功实现了一个会转的转盘,接下来继续实现关于飞刀的部分。
飞刀类
飞刀的行为主要有两个:展示飞刀,飞刀飞向转盘。
和之前的转盘类一样,我们直接将飞刀类继承THREE.Group:
// 忽略文件名的命名,因为我的场景它是一支笔
// /game/Pen.js
class Pen extends THREE.Group {
// canvas对象
canvas
constructor(canvas) {
this.canvas = canvas;
this.drawPen(canvas)
}
// 生成的笔网格体
pen = null;
drawPen(canvas) {
// 贴图
let texture = new THREE.TextureLoader().load(canvas, '/UV_Grid_Sm.jpg');
let material = new THREE.MeshBasicMaterial({ map: texture});
// 因为是平面,不需要3d,直接生成一个Plane就ok
let geometry = new THREE.PlaneGeometry(20, 60);
let pen = new THREE.Mesh(geometry, material);
this.pen = pen;
// 设置name
pen.name = 'pen';
// 在group中添加pen网格
this.add(pen);
// this.matrixAutoUpdate = true;
this.position.set(0, -400, -1);
}
}
在MainGame中创建Pen并添加到mainObj中
// /game/index.js
const Pen = require('./Pen');
...
init(canvas) {
...
this.addPen();
...
}
// 保存当前的飞刀
pen = null;
addPen() {
this.pen = new Pen(this.canvas, this);
this.mainObj.add(this.pen);
}
...
飞刀
要令刀飞起来,方法跟让转盘转起来大同小异,但是我们需要有一个状态控制刀是否在飞行状态还是静止状态。在Pen类中,我们加上一个move的属性,默认为false。提供一个flyPen方法,用于检测刀是否需要飞行并位移
// /game/Pen.js
class Pen {
...
move = false;
flyPen() {
if (this.move) {
// y轴自增15个单位
this.position.y += 15;
}
}
...
}
回到GameMain,我们需要在render里调用flyPen的方法。
// /game/index.js
class GameMain {
render() {
this.pen && this.pen.flyPen()
}
// 暴露一个方法在page中可以调用
makePenFly() {
this.pen && this.pen.move = true;
}
}
我们在page中加一个按钮并调用GameMain的makePenFly方法,就可以让刀飞起来。
刀飞起来了,但是却刹不住车飞出屏幕了哈哈哈哈哈。那怎么样让他在适当的时候刹车挺住,做到插在转盘的效果呢?答案是做物体的碰撞检测。
在Three.js中,碰撞的检测要用到Raycaster。
在官方文档的介绍中,Raycaster是用于检测鼠标经过的物体
This class is designed to assist with raycasting. Raycasting is used for mouse picking (working out what objects in the 3d space the mouse is over) amongst other things.
原理很简单,Raycaster提供了setFromCamera方法,只需要传入鼠标的位置二维坐标和Camera对象,就会从Camera发出一条射线,如果有物体和这根射线相交,在调用intersectObject()时,会返回传入的检测范围(scene或者其他的具体object)内和射线相交的物体对象。
虽然这里说的是鼠标,但是其实物体的碰撞也应该是一样的,在我们生成的物体上构建一或多条射线,在物体移动的时候不断对某个区域执行intersectObject(),直到我们拿到有相交的对象位置。
原理就是这样,下面是实现:
我们在创建小刀的时候调用的是PlaneGeometry,生成了一个平面几何体,转盘也是一个平面圆,所以如果我们是按照一般的做法(从物体的原点出发,向每个顶点都发出一根射线来构成碰撞检测的射线)在平面几何体中是不可能检测到碰撞的,因为射线和其他的几何体永远都是平行的(除非几何体在x或者y轴发生了旋转)。所以对于平面射线原点,我们要把它当作一个正方体来处理,就是我们给这个平面一个假想的高度。
底部的实线是我们的实际平面,虚线是我们碰撞检测的是构成的正方体。
我们在Pen中添加一个碰撞检测的方法crashTest,先拿到当前飞刀的位置
let originPoint = this.position.clone()
这个坐标是位于当前飞刀的正中心位置,这时候我们要给它一个z坐标轴上的“高度”
// 这里的值还是我随意给的,没做详细的计算
originPoint.z = 100
接下来是构建射线,这里因为和camera无关,所以我们要调用Raycaster的set()方法,有两个参数,原点、标准化方向向量。
我们先从平面的四个顶点开始构建,顶点信息存储在Object.geometry.vertices
for (let posIndex = 0; posIndex < this.pen.geometry.vertices.length; posIndex++) {
// 原顶点坐标
let localVertex = this.pen.geometry.vertices[posIndex].clone();
// 对顶点坐标作变换,获取变换后的真实坐标
let globalVertex = localVertex.applyMatrix4(this.pen.matrix);
// 刀对象的位置,因为这里的顶点位置都是在刀对象上的,所以计算方向向量要用到刀在group中的相对位置
let pen_pos = this.pen.position.clone();
// 给 "高度"
pen_pos.z = 100;
// 坐标相减获取方向向量
let directionVector = globalVertex.sub(pen_pos);
// 设置射线
ray.set(originPoint, directionVector.clone().normalize())
// 获取相交的对象,返回是一个数组
let result = ray.intersectObjects(object.children, true)
// console.log(result);
if (result.length > 0) {
// 相交就停止飞行
this.move = false;
break;
}
}
这里偷了个小懒,只生成了四根射线,大家有兴趣的可以探究下还有什么比较好的射线生成方式,我的想法是,毕竟是一个2d场面,我们其实所有射线都从顶点射向z轴的两个方向就可以了,后面再试试
完整代码
crashTest(object) {
let originPoint = this.position.clone();
originPoint.z = 100;
// console.log(object);
// return;
for (let posIndex = 0; posIndex < this.pen.geometry.vertices.length; posIndex++) {
// 原顶点坐标
let localVertex = this.pen.geometry.vertices[posIndex].clone();
// 对顶点坐标作变换,获取变换后的真实坐标
let globalVertex = localVertex.applyMatrix4(this.pen.matrix);
// 刀对象的位置,因为这里的顶点位置都是在刀对象上的,所以计算方向向量要用到刀在group中的相对位置
let pen_pos = this.pen.position.clone();
// 给 "高度"
pen_pos.z = 100;
// 坐标相减获取方向向量
let directionVector = globalVertex.sub(pen_pos);
// 设置射线
ray.set(originPoint, directionVector.clone().normalize())
// 获取相交的对象,返回是一个数组
let result = ray.intersectObjects(object.children, true)
// console.log(result);
if (result.length > 0) {
this.move = false;
break;
}
}
}
我们在flyPen中调用碰撞检测,因为我们要检测的是飞盘对象的内容,所以在render调用flyPen是传入this.turnTable。在crashTest中传入这个object
flyPen(object) {
if (this.move) {
this.position.y += 15;
// 检测碰撞
this.crashTest(object);
}
}
我们加入log,看下碰撞的结果
在输出了一些的空数组之后,终于输出了一个非空数组,并且飞刀也停下来了!
等等,好像有点奇怪。飞刀插到转盘了,停是停了。但。。。。。。你这转盘还是自己在转啊?。
没事,别急,下一篇继续继续
github地址:戳这里