14. 光投射-用于射击游戏、选中多个物体

本教程翻译自官方教程Raycasts,各位也可以进入本站镜像站点查看

本文目录

  1. 光投射
    1. 我该怎么做?
    2. 检测与射线接触的第一个物体
    3. 判定函数
    4. 多选
    5. Debugging
    6. 下一步
  2. 延伸阅读
    1. 基础教程Basic-难度L1
    2. 外部资源
  3. 贡献者

光投射Raycasts

    光线rays类似太阳光,在3D场景中,用于物体或点之间的碰撞或相交检测。

    在之前的教程中,我们使用了光投射来以鼠标选中场景中的物体(在3D场景中,一束光线从相机发射到鼠标点击的位置),也使用了它的scene.pick(scene.pointerX, scene.pointerY)功能。

    上述的使用都是黑盒,我们不知道自己使用到了光投射。但是现在我们可以直接使用它,从任何点或任何位置发出光线。例如在第三人称射击游戏中,子弹和障碍物之间发生碰撞。

    可通过以下几个步骤来应用光投射:

  1. 首先我们必须先创建一束光线。
  2. 使用scene.pickWithRay()方法把光线发射出去,并选中一个物体。
  3. 从scene.pickWithRay()方法返回的picking信息中,做进一步的处理。

检测与射线接触的第一个物体

    首先让我们先看一个小例子

(以下描述将引用此示例。所引用的行均来自最新的示例链接)
1-raycast01

    在本章的playgrounds中,我们假设男主角是位于场景中心的一个红色立方体,它会持续的向前方发射死亡光线,敌人(其他立方体)会被光线击中身亡。

    在不需要点击的情况下,我们只需移动鼠标,就可以用 三角函数mousemovef(第34行) 来转动我们的男主角(红色盒子)。死亡射线在创建时需要几个参数: 发射原点origin,方向direction和长度length。代码如下

 var ray = new BABYLON.Ray(origin, direction, length);

    首先,我们设置 box.isPickable属性为false(第18行),避免光线穿过自己的体内。因为光线是从男主角的中心点射出的,如果不设置,会把自己杀死。

    最重要的是计算出一个好的方向向量(第59行)

function vecToLocal(vector, mesh){
	var m = mesh.getWorldMatrix();
	var v = BABYLON.Vector3.TransformCoordinates(vector, m);
	return v;
}

var forward = new BABYLON.Vector3(0,0,1);
forward = vecToLocal(forward, box);

var direction = forward.subtract(origin);
direction = BABYLON.Vector3.Normalize(direction);

    我们需要一个向量,它相对于男主角立方体的空间和方向来说,是正向的。然后,为了得到方向direction,需要从立方体(原点)的位置减去它。函数vecToLocal设计用于通过将向量乘以物体矩阵,并从物体的角度来改变位置。

各位看代码也许理解得更加深刻些,mesh.getWorldMatrix()是得到物体的变换矩阵,BABYLON.Vector3.TransformCoordinates可以应用变换矩阵到一个新的向量上,就好比让一个新的点做同样的位移、缩放、旋转等操作。Vector3.subtract()表示向量相减。BABYLON.Vector3.Normalize表示归一化向量,保持向量方向,把x,y,z归一化到1以内。

    然后,我们使用以上计算给定的参数创建死亡激光(光线rays),长度为100,例如(第67行):

var ray = new BABYLON.Ray(origin, direction, length);

    最后,假如死亡激光(光线rays)击中的了敌人(其他立方体),则我们会得到撞击点hit。

var hit = scene.pickWithRay(ray);

    假如一个敌人被击中了,我们将得到picking信息,例如物体的名称、点的位置等。在例子中,我们会把增大被击中敌人的体积,因为这样效果更明显、更有趣!


    其实我们也没必要强制将box.isPickable设置为false,假如需要检查男主角立方体是否与其他射线相交,比如敌人射出的子弹是否也击中的男猪脚,设置成false就成了无敌状态。 在这个情况下,可以在立方体范围外设置光线的原点origin、并给一个稍远的方向direction和所需的长度length(第57行):

Playground 光投射示例


判定函数

    这是一个筛选器,用于选择需要的物体:
Playground 光投射选择需要的物体示例

2-raycast02

    如上例所示,我们加入了一个判定函数(第54行)

function predicate(mesh){
  if (mesh == box2 || mesh == box){
    return false;
  }
  return true;
}

    然后把这个函数作为参数加入到pickWithRay方法中:

scene.pickWithRay(ray, predicate);

    可以看到再加入判定函数的情况下,其实isPickable属性加不加都无所谓。在这里我们也过滤掉了box2,只允许box3和box4能够响应光线的照射。

    结果,当我们同时照射到box2和box4时,虽然box2先被照射到,但是由于被判定函数排除了,所以我们可以看到box4的体积在正大。就好像box2被穿透了一样,对光线没有任何反应。


    pickWithRay方法还有一个可选参数,即:是否支持快速检测(默认为false),当设置为true的时候,函数将返回与光线相交的第一个物体(按物体在场景中数组的顺序,scene.meshes),而不是距光线起点最近的物体,避免了复杂的计算,但是结果不太精确。


三角判定

    从Babylon.js v4.0开始,支持定义一个对于物体面facet的判定函数,它将使用每个面的3个顶点和即将出现的光线ray来调用判定函数:

//p0, p1, p2表示facet面三角的坐标,ray表示光线
scene.pick(scene.pointerX, scene.pointerY, null, false, null, (p0, p1, p2, ray) => {
    var p0p1 = p0.subtract(p1);
    var p2p1 = p2.subtract(p1);
    var normal = BABYLON.Vector3.Cross(p0p1, p2p1);
    return (BABYLON.Vector3.Dot(ray.direction, normal) < 0);
});

    在以下例子中,将过滤掉所有不面向相机的facet面三角,facet相当于组成物体的三角形面
Playground 三角判定的应用

多选

    如果不希望光线在遇到第一个障碍物时查找,我们可以使用scene.multickwithray来选择多个物体:

Playground 选择多个物体示例

3-raycast02

    光线选中的结果将会一个数组(第69行)。

    我们可以遍历数组来更改撞击到的所有物体的状态,在上例中,可以看到2个蓝色立方体正在变大,就像强大的子弹同时打中的它们俩。


    还有一种方法来实现上述的效果,可以直接使用Ray类。首先改变光线ray的坐标空间:

Ray.Transform(ray, matrix) → Ray

    然后使用Ray类的intersectsMesh函数,这个和在碰撞检测学习到的物体mesh的intersectsMesh函数类似:

Ray.intersectsMesh(mesh, fastCheck) → PickingInfo

Debugging

    一束光线ray的开始原点和方向,这个比较抽象,为了帮助debug,能够使用RayHelper类。

    可以使用一个静态函数来创建和显示一束光线:

BABYLON.RayHelper.CreateAndShow(ray, scene, new BABYLON.Color3(1, 1, 0.1));

    或者也可以使用更详细的版本:

var rayHelper = new BABYLON.RayHelper(ray);
rayHelper.show(scene);

    RayHelper辅助类也可以连接到物体以跟踪其方向:

var localMeshDirection = new BABYLON.Vector3(0, 0, -1);
var localMeshOrigin = new BABYLON.Vector3(0, 0, -.4);
var length = 3;
rayHelper.attachToMesh(box, localMeshDirection, localMeshOrigin, length);

Playground RayHelper辅助类应用示例

下一步

    在3D场景中加入一些2D动画形状,这将会让你的场景更加出彩,请阅读下一章15. 精灵图-让2D动图显示在3D场景中

延伸阅读

基础教程Basic-难度L1

物体概述

外部资源

Forum Pick with Ray
Forum World Local Ray
Forum Gobal to Local

贡献者

翻译/校对: Cow&Sheep
Playground: 天才
技术支持: Dushusir github

参与贡献? 联系alexads@foxmail.com