图片
开发棋类游戏,需要实现鼠标控制棋子的功能,使得游戏能够将鼠标在屏幕上的位置准确地映射到棋盘上的对应落子点。
如果开发的是2D游戏的话,实现就比较简单,鼠标屏幕坐标和棋盘都是二维平面。就像下图围棋所示,可以根据xy坐标表示鼠标的位置,同时围棋落子点的坐标也可以计算得出xy坐标,这样很轻松地就能实现鼠标位置和围棋落子点坐标的映射和距离比较。
图片
如果开发的是3D游戏,这个问题就不那么好处理了,增加一个维度意味着复杂度的增加。玩家可以通过变换摄像机从不同角度观察场景中的物体,这样也会导致物体在观察空间下的坐标发生变化,那么在3D场景中怎么样才能从二维屏幕中的鼠标位置得出鼠标点中哪个物体以及点中物体的具体坐标呢?对于围棋游戏来说就是怎么判断鼠标点中位置最接近围棋盘中具体哪个落子位置呢?
先来梳理一下思路,围棋棋盘放置到3D空间中的位置是固定的,也就是说围棋棋盘每个交叉点的xyz坐标可以认为是已知的。那么如果能得到鼠标屏幕位置对应3D空间中棋盘的坐标,那么判定鼠标在棋盘落子点的哪个位置就自然不是问题了。
下图是计算机图形学中的视锥体模型,描述的是观察者(摄像机)在视角下可见的空间范围,前后两个平面分别是近平面和远平面,这里可以近似认为近平面就是计算机屏幕,如果从观察者到屏幕鼠标做一条射线,通过计算射线和围棋平面的交点就可以得出鼠标位置点击到的棋盘坐标,这样就可以拿射线交点坐标和围棋棋盘坐标一一比较得出更接近哪个落子点了。
图片
关于射线和平面的求交算法这里不做讲解,如果有兴趣请查阅图形学相关的资料进行查阅。像Babylon.js这样的3D框架已经实现了创建射线、求射线和网格交点这些算法,这里介绍如何使用Babylon.js的提供的API来实时跟踪鼠标位置,并获取观察者到鼠标射线与场景中物体的交点坐标。
图片
主要用到的API是scene.pick方法,scene是空间场景Scene类的实例。关于pick方法及参数说明如下:
pick(x, y, predicate?, fastCheck?, camera?, trianglePredicate?) 方法用于从屏幕上的指定位置(x, y)拾取对象。以下是该方法参数的详细解释:
x, y:这两个参数指定了屏幕上的坐标,用于确定拾取操作的起点。这些坐标通常是鼠标事件中获取的,表示用户点击或触摸屏幕的位置。
predicate?(可选):这是一个过滤函数,允许你自定义拾取逻辑。它接受一个AbstractMesh对象作为参数,并返回一个布尔值。如果返回true,则该网格会被考虑在内进行拾取;如果返回false,则该网格会被忽略。这个参数可以用来排除某些对象,或者只拾取特定类型的网格。例如,你可以设置一个predicate来忽略不可见的网格或者只拾取特定标签的网格。
fastCheck?(可选):这是一个布尔值参数,用于优化拾取性能。如果设置为true,Babylon.js将跳过一些检查,从而加快拾取速度,但可能会降低拾取的准确性。默认值为false,意味着执行完整的拾取检查。
camera?(可选):这个参数允许你指定一个特定的相机来进行拾取操作。如果不提供,将使用场景中的默认相机。这在多相机场景中特别有用,你可以指定哪个相机的视角用于拾取。
trianglePredicate?(可选):这是一个更细粒度的过滤函数,用于在三角形级别上控制拾取逻辑。它接受四个参数:三个表示三角形顶点的向量和一个射线对象。返回一个布尔值,决定是否拾取该三角形。这个参数可以用来实现更复杂的拾取逻辑,比如只拾取面向摄像机的三角形。
pick方法返回一个PickingInfo对象,其中包含了拾取操作的结果,例如拾取的网格、距离、撞击点等信息。如果没有拾取到任何对象,则返回null。
下面的示例代码展示了监测鼠标的实时移动,通过pick方法获取射线与网络的相交信息,还可以判断相交的网络名称,如果相交的网格是围棋棋盘则简单打印了交点的坐标信息。可见通过Babylon.js提供的API可以非常轻松地实现鼠标和3D场景的交互操作,激发自己的创意并借助框架提供的能力可以轻松地开发出有趣的3D应用。
// 监测鼠标的实时移动
scene.onPointerMove = function castRay() {
// (scene.pointerX, scene.pointerY)为鼠标实时屏幕坐标
var hit = scene.pick(scene.pointerX, scene.pointerY)
// hit.pickedMesh表示射线与场景的物体有相交
// hit.pickedMesh.name表示相交物体的name属性值
if (hit && hit.pickedMesh && hit.pickedMesh.name == "goboard"){
//hit.pickedPoint表示射线与场景中物体相交点的坐标
console.log(hit.pickedPoint.x, hit.pickedPoint.y, hit.pickedPoint.z)
}
}
参考文献
[1]. https://doc.babylonjs.com/typedoc/classes/BABYLON.Scene#pick
[2]. https://doc.babylonjs.com/features/featuresDeepDive/mesh/interactions/picking_collisions/