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游戏中通过点击或触摸来选中物体,并对选中的物体进行高亮显示的功能。

相关推荐
VaJoy21 天前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy23 天前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy25 天前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy1 个月前
Cocos Creator Shader —— 附录
cocos creator
成长ing121381 个月前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk2 个月前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121383 个月前
点击音效系统
前端·cocos creator