CocosCreator: 用射线投射做3D物体选中检测

简介

在3D游戏开发中,有时候我们需要实现一个功能:玩家通过鼠标点击或触摸屏幕来选择场景中的物体,并对选中的物体进行高亮显示,如下图:

这种交互方式可以增强游戏的沉浸感,使玩家更好地理解和控制游戏世界。要实现这个功能,通常使用射线投射来对3D场景中的物体进行碰撞检测。

原理

射线投射(Raycasting)在3D游戏和图形编程中是一种常见的技术,用于解决各种问题,包括(但不限于)碰撞检测,物体选中、物体选取,人工智能视觉等。

射线投射的基本思想是:你有一条从特定点开始沿特定方向的射线(可以想象成一条无穷长的直线),并且你想知道这条射线是否与3D空间中的某个物体相交。

这个过程的实现可以分为以下几个步骤:

  1. 定义射线:射线有一个起点和一个方向。在这个问题中,起点通常是摄像机的位置,方向是从摄像机指向鼠标点击(或触摸)位置的方向。这个方向可以通过将鼠标点击的屏幕坐标转换为3D世界坐标得到。这个转换过程需要使用摄像机的视角和视野等参数。
  2. 检测碰撞:然后你需要检查这条射线是否与场景中的任何物体的碰撞体(Collider)相交。这可以通过检查射线与每个物体的碰撞体的交点来实现。如果存在交点,那么就可以说射线与物体相交。
  3. 处理结果:如果射线与多个物体相交,那么你需要选择一个进行操作。通常选择的是最近的物体(也就是射线起点与交点距离最短的物体)。

这个过程的复杂性取决于场景中物体的数量和复杂性。对于简单的物体(如球或盒子),检测射线与物体的交点是相对简单的。对于更复杂的物体,可能需要使用更复杂的算法,或者使用简化的碰撞体(例如,使用物体的外接盒作为碰撞体)。

大多数现代3D游戏引擎都提供了射线投射的内置支持,可以很方便地进行这个过程。例如,在Unity中,你可以使用Physics.Raycast函数来进行射线投射。在Three.js中,你可以使用THREE.Raycaster对象来进行射线投射。而CocosCreator(3.x版本)中使用PhysicsSystem.instance.raycastClosest来做实现。

实现

在场景中,添加几个3D物体到一个平面上,如图

挂载类Game代码如下:

typescript 复制代码
import { _decorator, Component, CameraComponent, systemEvent, SystemEventType, Touch, PhysicsSystem, geometry, LabelComponent, MeshRenderer, Material } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Game')
export class Game extends Component {

    @property({displayName: "3D相机", type: CameraComponent})
    camera: CameraComponent = null!;

    @property({displayName: "文字", tooltip: "文字", type: LabelComponent})
    label: LabelComponent = null!;

    @property({displayName: "描边材质", tooltip: "描边材质", type: Material})
    outline: Material = null!;

    @property({displayName: "默认材质", tooltip: "默认材质", type: Material})
    default: Material = null!;

    // 上一个选中的结果
    last: MeshRenderer = null!;

    onLoad () {
        this.label.string = "请点击屏幕";

        systemEvent.on(SystemEventType.TOUCH_START, (e: Touch) => {
            // 获取触摸点并且创建射线
            let pos = e.getLocation();
            let ray = this.camera.screenPointToRay(pos.x, pos.y);
            // 传入射线
            this.rayCollisionDetection(ray);
        }, this);
    }

    rayCollisionDetection (ray: geometry.Ray) {
        // 如果选中了节点
        if (PhysicsSystem.instance.raycastClosest(ray) == true) {
            // 获取射线最短的检测结果
            var node = PhysicsSystem.instance.raycastClosestResult;
            // 获取名字
            let name = node.collider.name;

            // 根据不同的名字进行不同的判断
            if (name == "Cylinder<CylinderCollider>") {
                this.label.string = "点击到了物体:圆柱";
            } else if (name == "Cube<BoxCollider>") {
                this.label.string = "点击到了物体:立方体";
            } else if (name == "Capsule<CapsuleCollider>") {
                this.label.string = "点击到了物体:胶囊";
            } else if (name == "Cone<ConeCollider>") {
                this.label.string = "点击到了物体:圆锥";
            } else if (name == "Sphere<SphereCollider>") {
                this.label.string = "点击到了物体:球";
            } else if (name == "Torus<MeshCollider>") {
                this.label.string = "点击到了物体:圆环";
            } else {
                this.label.string = "点击到了物体:" + node.collider.name;
            }

            // 设置检测结果的材质为红色描边
            node.collider.node.getComponent(MeshRenderer)!.material = this.outline;
            // 如果不是第一次选中并本次选中和上次不同
            if (this.last != null && node.collider.node.getComponent(MeshRenderer) != this.last) {
                // 设置为默认材质
                this.last.material = this.default;
            }
            // 设置上次选中的结果
            this.last = node.collider.node.getComponent(MeshRenderer)!;
        } else {
            // 没选中任何物体设置上次选中结果材质为默认
            this.label.string = "没点到任何物体";
            if (this.last != null) {
                this.last.material = this.default;
            }
        }
    }

}

这段代码的核心原理主要涉及到两个方面:射线投射(Raycasting)和材质切换。

射线投射(Raycasting)

射线投射是3D图形和游戏开发中常用的技术。它基于一个简单的概念:从一个点发出一条射线,看这条射线是否和场景中的物体相交。

在这段代码中,射线的起点是3D相机,方向是从相机指向屏幕上的触摸点。通过camera.screenPointToRay(pos.x, pos.y)这行代码,我们将2D屏幕坐标转换为3D空间中的一条射线。然后,我们使用PhysicsSystem.instance.raycastClosest(ray)来检测这条射线是否与场景中的任何物体相交。

如果射线与物体相交,PhysicsSystem.instance.raycastClosest(ray)将返回true,并且我们可以通过PhysicsSystem.instance.raycastClosestResult获取到射线碰撞的最近的物体。这就是我们选中的物体。

材质切换

材质是定义物体表面外观的属性,包括颜色、纹理、光照效果等。在这段代码中,我们定义了两种材质:默认材质和描边材质。默认材质是物体正常的外观,描边材质则是物体被选中时的外观。

当我们选中一个物体时,我们会获取到物体的MeshRenderer组件,然后将它的材质切换为描边材质。这样就可以实现物体被选中时的高亮效果。当我们选中另一个物体时,我们会将上一次选中的物体的材质切换回默认材质。

通过这两个步骤,我们就可以实现在3D游戏中通过点击或触摸来选中物体,并对选中的物体进行高亮显示的功能。

相关推荐
烧仙草奶茶1 个月前
【cocos creator】2.x里,使用3D射线碰撞检测
3d·cocos creator·cocos-creator·2.x
仅此而已7291 个月前
Cocos Creator倒计时
游戏引擎·cocos creator
仅此而已7291 个月前
cocosUI多分辨率适配
游戏引擎·cocos creator·多分辩率适配
SilenceJude2 个月前
cocos creator 3学习记录01——如何替换图片
前端·cocos creator
GrimRaider3 个月前
[Cocos Creator] v3.8开发知识点记录(持续更新)
开发·cocos creator
S_clifftop3 个月前
cocos creator如何使用cryptojs加解密(及引入方法)
cocos creator·加密解密·cryptojs
平淡风云3 个月前
cocosCreator获取手机剪切板内容
java·智能手机·typescript·android studio·cocos creator
zhenmu3 个月前
【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数
cocos creator·shader·effect
Thomas_YXQ4 个月前
Cocos Creator 编辑器的数据绑定详解
编辑器·游戏引擎·cocos creator·cocos2d·cocos·微信小游戏
Thomas_YXQ5 个月前
Cocos Creator 3D物理引擎的碰撞检测与触发器详解
游戏·3d·cocos creator·cocos2d·cocos