Unity3D数学第四篇:射线与碰撞检测(交互基础篇)

Unity3D数学第一篇:向量与点、线、面(基础篇)

Unity3D数学第二篇:旋转与欧拉角、四元数(核心变换篇)

Unity3D数学第三篇:坐标系与变换矩阵(空间转换篇)

Unity3D数学第四篇:射线与碰撞检测(交互基础篇)

Unity3D数学第五篇:几何计算与常用算法(实用算法篇)

在前几篇中,我们学习了如何用向量表示位移和方向,如何用四元数处理旋转,以及如何在不同坐标系之间进行转换。现在,我们将这些知识融会贯通,来解决 3D 游戏中最常见的交互问题:物体之间是否接触?在哪里接触? 这就是射线检测 (Raycasting)碰撞检测 (Collision Detection) 的核心任务。

1. 射线的概念与作用:精确的"探针"

在 3D 空间中,一条射线 (Ray) 是一个具有起点 (Origin)方向 (Direction) 的无限长直线。你可以把它想象成一束激光,从一个点笔直地射向某个方向。

Unity 中使用 UnityEngine.Ray 结构体来表示射线:

C#

复制代码
// Unity
// 定义一条从世界坐标 (0, 0, 0) 向上发射的射线
Ray myRay = new Ray(Vector3.zero, Vector3.up);

// 定义一条从当前物体位置向前发射的射线
Ray forwardRay = new Ray(transform.position, transform.forward);
1.1 为什么需要射线?

在游戏开发中,射线是进行精确、点对点检测的利器,尤其适用于以下场景:

  • 鼠标/触控拾取: 点击屏幕上的 3D 物体。

  • 射击游戏: 判断子弹是否命中敌人或障碍物。

  • AI 视线/障碍物检测: AI 角色判断前方是否有障碍物或敌人。

  • 交互高亮: 当玩家准星对准某个可互动物体时,高亮显示。

  • 地形检测: 角色在空中时,检测下方是否有地面。

  • 物理模拟辅助: 在自定义物理行为中,判断某个方向上是否有阻碍。

1.2 Physics.Raycast():发射射线进行检测

Unity 的 Physics 类提供了多种射线检测方法,其中最常用的是 Physics.Raycast()

C#

复制代码
// Unity
// Physics.Raycast 的最常用重载
public static bool Raycast(
    Ray ray,                     // 要发射的射线
    out RaycastHit hitInfo,      // 输出参数,用于存储碰撞信息
    float maxDistance = Mathf.Infinity, // 射线的最大检测距离
    int layerMask = DefaultRaycastLayers // 要检测的物理层
);
  • 返回值 bool 如果射线击中任何碰撞体,返回 true;否则返回 false

  • Ray ray 要发射的射线实例,包含起点和方向。

  • out RaycastHit hitInfo 这是一个输出参数 ,如果射线击中物体,所有关于碰撞的详细信息都会填充到这个 RaycastHit 结构体中。

  • maxDistance 射线的最大检测距离。只检测这个距离范围内的碰撞。默认为无限远 (Mathf.Infinity)。

  • layerMask 一个位掩码 (bitmask) ,用于指定射线只检测哪些物理层 (Layer) 上的物体。这是非常重要的性能优化逻辑控制手段。

1.3 RaycastHit 结构体:碰撞的详细信息

Physics.Raycast() 返回 true 时,RaycastHit 结构体中包含了以下有用的信息:

  • hitInfo.collider:被射线击中的 Collider 组件。

  • hitInfo.transform:被射线击中的 GameObject 的 Transform 组件。

  • hitInfo.gameObject:被射线击中的 GameObject

  • hitInfo.point:射线与碰撞体相交的世界坐标点

  • hitInfo.normal:碰撞点处被击中表面的法线向量(一个单位向量,垂直于表面,指向外)。

  • hitInfo.distance:从射线起点到碰撞点的距离

示例:鼠标点击拾取物体

C#

复制代码
// Unity
void Update() {
    // 检查鼠标左键是否按下
    if (Input.GetMouseButtonDown(0)) {
        // 1. 从摄像机向鼠标位置发射一条射线
        // Camera.main.ScreenPointToRay() 内部处理了屏幕坐标到世界射线的转换
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit; // 用于存储碰撞信息

        // 2. 发射射线,检测最远 100 米范围内的所有碰撞体
        if (Physics.Raycast(ray, out hit, 100f)) {
            // 射线击中了物体
            Debug.Log("你点击了: " + hit.collider.gameObject.name);
            Debug.Log("点击点世界坐标: " + hit.point);
            Debug.Log("击中表面的法线: " + hit.normal);

            // 示例:给被点击的物体一个力
            Rigidbody hitRb = hit.collider.GetComponent<Rigidbody>();
            if (hitRb != null) {
                // 沿着击中点的法线方向施加一个力
                hitRb.AddForce(hit.normal * 100f, ForceMode.Impulse);
            }
        } else {
            // 没有击中任何物体
            Debug.Log("你点击了空地。");
        }
    }
}
1.4 Physics.RaycastAll():获取所有碰撞

如果一条射线可能穿透多个物体(例如,穿透性子弹),或者你需要检测射线路径上所有的物体,可以使用 Physics.RaycastAll()。它会返回一个 RaycastHit 数组。

C#

复制代码
// Unity
// 发射一条射线,获取所有碰撞信息
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit[] hits = Physics.RaycastAll(ray, 10f); // 检测 10 米内所有碰撞

// 遍历所有碰撞结果
foreach (RaycastHit hit in hits) {
    Debug.Log("命中物体: " + hit.collider.gameObject.name + ",距离: " + hit.distance);
}
// 注意:RaycastAll 返回的结果是按距离从近到远排序的。

1.5 物理层 (LayerMask) 的重要性

layerMask 参数是进行射线检测时非常重要的优化手段。它允许你只检测特定层级上的物体

  • 设置层级: 在 Unity Editor 中,选择一个 GameObject,在 Inspector 面板的顶部 Layer 下拉菜单中选择或添加层级。

  • 创建 LayerMask:

    C#

    复制代码
    // Unity
    int layerToDetect = LayerMask.GetMask("Ground", "Enemy"); // 只检测 "Ground" 和 "Enemy" 层
    // 或者通过位运算
    int groundLayer = LayerMask.NameToLayer("Ground");
    int enemyLayer = LayerMask.NameToLayer("Enemy");
    int combinedLayerMask = (1 << groundLayer) | (1 << enemyLayer); // 将两个层的位进行或运算
    
    // 发射射线时使用 LayerMask
    if (Physics.Raycast(ray, out hit, 100f, layerToDetect)) {
        // ...
    }
  • 忽略特定层级:

    C#

    复制代码
    // 检测除了 "Player" 层以外的所有层
    int ignorePlayerLayer = ~LayerMask.GetMask("Player");
    if (Physics.Raycast(ray, out hit, 100f, ignorePlayerLayer)) {
        // ...
    }
  • 性能考量: 合理使用 layerMask 可以极大地减少射线检测的计算量,因为它避免了对不相关层级上的所有碰撞体进行不必要的几何测试。这是优化复杂场景性能的关键一环。


2. 碰撞检测器 (Colliders):3D 物体的"形状"

射线检测是"点-线"与"面"的碰撞。而更广义的碰撞检测 ,则是两个或多个碰撞体 (Collider) 之间的相互接触。

Unity 的碰撞检测系统依赖于添加到 GameObject 上的 Collider 组件 。这些组件定义了物体的物理形状,即使物体的网格模型非常复杂,碰撞体通常是简化的几何形状,以提高性能。

2.1 常见碰撞体类型

Unity 提供了多种内置的碰撞体类型:

  • BoxCollider (盒子碰撞体):

    • 最简单、计算最快的碰撞体之一。

    • 适用于大部分矩形、立方体或近似形状的物体(墙壁、箱子)。

    • 可以通过 sizecenter 属性调整大小和中心偏移。

  • SphereCollider (球体碰撞体):

    • 计算也非常高效。

    • 适用于球形或近似球形的物体(球、角色头部、一些圆形投掷物)。

    • 通过 radiuscenter 调整。

  • CapsuleCollider (胶囊体碰撞体):

    • 圆柱体两端带半球的形状。

    • 非常适合角色控制器:因为它可以很好地模拟人体形状,并且在斜坡上滚动而不是卡住,方便处理角色移动。

    • 通过 radiusheightdirection (X, Y, Z 轴) 调整。

  • MeshCollider (网格碰撞体):

    • 直接使用物体的网格模型作为碰撞形状。

    • 最精确 ,但也是性能开销最大的碰撞体类型。

    • 警告: 除非必要,不建议在移动的物体上使用 MeshCollider,尤其是不勾选 Convex 选项的非凸 MeshCollider。它会显著增加物理计算量。

    • 通常用于: 不动的复杂地形、大型建筑等作为静态碰撞体。

    • Convex 属性: 勾选后,Unity 会计算一个凸包 (Convex Hull) 作为碰撞体。这会大大降低计算复杂度,允许 MeshCollider 与其他 MeshCollider 发生碰撞(否则两个非凸 MeshCollider 无法互相碰撞)。

  • WheelCollider (车轮碰撞体): 专门用于模拟车辆车轮的物理行为,具有悬挂、摩擦等特性。

  • TerrainCollider (地形碰撞体): 专门用于 Unity 地形 (Terrain) 组件的碰撞检测。

2.2 碰撞体属性
  • Is Trigger (是否为触发器):

    • 勾选: 碰撞体变为触发器 。触发器只检测物体的进入、停留、离开 ,但不产生物理反弹或阻碍。它只会触发 OnTriggerEnter/Stay/Exit 回调。

    • 不勾选: 碰撞体是实体碰撞体 。它会产生物理交互,阻碍其他物体,并触发 OnCollisionEnter/Stay/Exit 回调。

    • 应用:

      • 触发器: 区域进入检测(如进入一个房间触发剧情)、拾取物品(进入拾取范围)。

      • 实体碰撞体: 玩家与墙壁的碰撞、子弹击中敌人产生反弹或伤害。

2.3 刚体 (Rigidbody) 的作用

要使碰撞体能够进行物理模拟(受力、重力、反弹、滑动等),它必须依附于一个带有 Rigidbody (刚体) 组件的 GameObject。

  • 没有 Rigidbody 的碰撞体:

    • 只能作为静态碰撞体运动学碰撞体 (Kinematic Collider) 。它们不会响应物理力,但可以被带有 Rigidbody 的物体撞击或被射线检测到。

    • 适用于地面、墙壁、不可移动的障碍物等。

  • Rigidbody 的碰撞体:

    • 会受到物理引擎的控制,响应重力、力、扭矩等。

    • 动态碰撞体

    • 适用于玩家角色、箱子、球等需要物理模拟的物体。

    • Is Kinematic 属性: 勾选后,刚体将不受物理引擎控制 ,但仍然可以通过 transform 手动移动它,且它会影响其他非运动学刚体。这是一种特殊的运动学刚体,常用于需要手动控制但又要参与物理交互的物体(如可移动的平台)。


3. 碰撞回调函数:响应碰撞事件

当两个带有碰撞体的物体发生碰撞或触发事件时,Unity 会调用相应的回调函数。这些函数需要在你的脚本中定义。

3.1 碰撞事件 (Collision Events)

当两个实体碰撞体发生物理接触时触发(至少一个物体需要有 RigidbodyIs Trigger 未勾选)。

  • OnCollisionEnter(Collision collision) 首次接触时调用。

  • OnCollisionStay(Collision collision) 接触过程中每帧调用。

  • OnCollisionExit(Collision collision) 停止接触时调用。

Collision 参数: 包含了碰撞的详细信息,例如:

  • collision.gameObject:与当前物体发生碰撞的另一个 GameObject。

  • collision.collider:与当前物体发生碰撞的另一个 Collider。

  • collision.contacts:一个 ContactPoint 数组,包含所有接触点的详细信息(位置、法线)。

  • collision.relativeVelocity:碰撞发生时的相对速度。

示例:物体碰撞反弹

C#

复制代码
// Unity
void OnCollisionEnter(Collision collision) {
    if (collision.gameObject.CompareTag("Wall")) {
        Debug.Log("我撞到墙了!");
        // 获取撞击点的法线,用于计算反弹
        Vector3 collisionNormal = collision.contacts[0].normal;
        // 如果是球体,可以根据入射速度和法线计算反弹速度
        // 例如:rb.velocity = Vector3.Reflect(rb.velocity, collisionNormal);
    }
}
3.2 触发事件 (Trigger Events)

当一个或两个碰撞体是触发器 (Is Trigger 勾选) 时触发。这些事件不会产生物理阻碍,只用于检测进入/离开某个区域。

  • OnTriggerEnter(Collider other) 首次进入触发器时调用。

  • OnTriggerStay(Collider other) 停留在触发器内每帧调用。

  • OnTriggerExit(Collider other) 离开触发器时调用。

Collider other 参数: 触发事件的另一个 Collider 组件。

示例:拾取物品

C#

复制代码
// Unity (挂在拾取物品上,并勾选其 Collider 的 Is Trigger)
void OnTriggerEnter(Collider other) {
    if (other.CompareTag("Player")) {
        Debug.Log("玩家进入了拾取区域,拾取物品!");
        // 销毁物品或添加到玩家背包
        Destroy(gameObject);
    }
}
3.3 碰撞矩阵 (Collision Matrix)

在 Unity 的 Project Settings -> Physics (或 Physics 2D) 中,有一个 Layer Collision Matrix (层碰撞矩阵)。这个矩阵允许你精确控制哪些层之间的物体可以相互碰撞或触发事件。

  • 作用: 这是一个重要的性能优化逻辑隔离工具。你可以禁用不必要的层间碰撞,从而减少物理引擎的计算量。

  • 示例:

    • 禁用"Player"层与"SelfProjectile"(玩家发射的子弹)层的碰撞,避免玩家被自己的子弹击中。

    • 禁用"UI"层与"Environment"层的碰撞,防止射线检测 UI 时错误地击中背景物体。


4. 其他高级碰撞检测方法

除了 Physics.Raycast 和碰撞回调,Unity 还提供了更灵活的检测方法。

4.1 Physics.SphereCast() / BoxCast() / CapsuleCast():带厚度的射线

这些方法就像发射一个有形状的"探针",而不是一条无限细的射线。它们检测一个球体、盒子或胶囊体沿着一个方向扫过时是否与任何物体发生碰撞。

C#

复制代码
// Unity
// Physics.SphereCast 示例:检查前方是否能通过
public float sphereRadius = 0.5f;
public float maxDistance = 1f;
public LayerMask obstacleLayer;

void Update() {
    // 从当前位置向前发射一个球体
    RaycastHit hit;
    if (Physics.SphereCast(transform.position, sphereRadius, transform.forward, out hit, maxDistance, obstacleLayer)) {
        Debug.Log("前方有障碍物被球体检测到: " + hit.collider.gameObject.name);
        // 通常用于角色控制器,避免卡住
    }
}
  • 应用:

    • 角色控制器: 检测角色前方是否有空间移动,避免穿墙。

    • 扫雷: 坦克或大型载具在移动前检测路径是否畅通。

    • AoE 技能预判: 技能释放前检测范围内是否有敌人。

4.2 Physics.OverlapSphere() / OverlapBox() / OverlapCapsule():范围检测

这些方法检测一个特定形状的区域内,当前有哪些碰撞体存在。它们不涉及运动,只是一个静态的区域查询。

C#

复制代码
// Unity
// Physics.OverlapSphere 示例:检测半径内所有敌人
public float detectionRadius = 5f;
public LayerMask enemyLayer;

void Update() {
    if (Input.GetKeyDown(KeyCode.Space)) {
        // 在当前位置周围检测半径 5 米内的所有敌人
        Collider[] hitColliders = Physics.OverlapSphere(transform.position, detectionRadius, enemyLayer);

        if (hitColliders.Length > 0) {
            Debug.Log("检测到附近的敌人:");
            foreach (Collider collider in hitColliders) {
                Debug.Log(collider.gameObject.name);
                // 示例:对敌人施加效果
                // collider.GetComponent<EnemyHealth>().TakeDamage(10);
            }
        } else {
            Debug.Log("附近没有敌人。");
        }
    }
}
  • 应用:

    • AoE 技能: 施放范围伤害技能时,检测范围内的所有目标。

    • 收集物品: 玩家进入特定范围自动拾取。

    • AI 索敌: AI 在自身周围检测是否有敌人进入其警戒范围。

4.3 物理查询优化:非分配版本 (NonAlloc)

Physics.OverlapSpherePhysics.RaycastAll 这样的方法,每次调用时都会分配内存 来创建新的数组。如果这些操作在 UpdateFixedUpdate 中频繁调用,可能会导致内存垃圾 (Garbage Collection - GC) 产生,从而引发卡顿。

Unity 提供了这些方法的 NonAlloc 版本,它们接受一个预先创建好的数组作为参数,将结果填充到该数组中,从而避免了运行时内存分配

C#

复制代码
// Unity (NonAlloc 示例)
private Collider[] _overlapResults = new Collider[50]; // 预先分配一个足够大的数组

void FixedUpdate() {
    // 检测周围敌人,并避免 GC
    int numColliders = Physics.OverlapSphereNonAlloc(transform.position, detectionRadius, _overlapResults, enemyLayer);

    for (int i = 0; i < numColliders; i++) {
        Debug.Log("NonAlloc 命中敌人: " + _overlapResults[i].gameObject.name);
    }
}

对于性能敏感的游戏,在物理查询频繁的地方使用 NonAlloc 方法是非常重要的优化手段


5. 可视化调试:Debug.DrawLine 与 Gizmos

在开发和调试射线与碰撞检测时,将它们可视化 出来至关重要。Unity 提供了 Debug.DrawLineGizmos 来帮助你。

5.1 Debug.DrawLine() / Debug.DrawRay()

这些方法允许你在 Scene 视图中绘制临时的线条和射线,方便你观察它们的起点、方向和长度。只在运行游戏时可见。

C#

复制代码
// Unity
void Update() {
    // 绘制一条从当前位置向前延伸 10 米的红线
    Debug.DrawLine(transform.position, transform.position + transform.forward * 10f, Color.red);

    // 绘制一条射线 (与 Debug.DrawLine 类似,但更强调起点和方向)
    Debug.DrawRay(transform.position, transform.forward * 10f, Color.blue);

    // 绘制射线检测结果
    RaycastHit hit;
    if (Physics.Raycast(transform.position, transform.forward, out hit, 10f)) {
        Debug.DrawLine(transform.position, hit.point, Color.green); // 命中部分绿色
        Debug.DrawRay(hit.point, hit.normal, Color.yellow); // 绘制法线
    }
}
5.2 OnDrawGizmos()

OnDrawGizmos() 是一个特殊的 Unity 回调函数,它允许你在 Scene 视图中绘制持久的调试辅助线。即使游戏不运行,你也可以看到它们。

  • Gizmos.color 设置绘制颜色。

  • Gizmos.DrawWireSphere() / DrawSphere() 绘制空心/实心球体。

  • Gizmos.DrawWireCube() / DrawCube() 绘制空心/实心立方体。

  • Gizmos.DrawRay() 绘制射线。

  • Gizmos.matrix 用于在特定变换空间中绘制 Gizmos,例如绘制一个物体局部坐标系下的盒子。

示例:可视化检测范围

C#

复制代码
// Unity
public float detectionRadius = 5f;
public float raycastDistance = 10f;

// 在 Scene 视图中绘制辅助线,无论游戏是否运行
void OnDrawGizmos() {
    // 绘制球形检测范围
    Gizmos.color = Color.yellow;
    Gizmos.DrawWireSphere(transform.position, detectionRadius);

    // 绘制前方射线检测路径
    Gizmos.color = Color.red;
    Gizmos.DrawRay(transform.position, transform.forward * raycastDistance);
}

OnDrawGizmosSelected() 只有当 GameObject 被选中时才绘制 Gizmos。这对于避免 Scene 视图过于混乱非常有用。

合理使用 Debug.DrawLineGizmos,能够让你清晰地看到射线、碰撞体、检测范围等在 3D 空间中的实际表现,从而大大提高调试效率。


6. 常见面试题与深入思考

6.1 面试问答

  1. 问:请解释 Physics.Raycast()Physics.OverlapSphere() 的主要区别和各自的典型应用场景。

    • 考察点: 对射线检测和范围检测不同用途的理解。

    • 解析:

      • Physics.Raycast()

        • 区别: 从一个点向一个方向发射一条无限细的"线",检测它是否与第一个遇到的碰撞体相交。

        • 应用: 鼠标点击拾取、射击游戏子弹命中、AI 视线检测、单点障碍物检测。它适合点对点的精确打击或探测。

      • Physics.OverlapSphere()

        • 区别: 在一个给定的中心点周围检测一个球形区域内所有存在的碰撞体。它不涉及运动或方向,只是一个静态的范围查询。

        • 应用: 范围伤害(AoE)技能、AI 索敌(检测附近敌人)、拾取区域检测。它适合检测一个区域内的所有目标。

  2. 问:Collider 组件的 Is Trigger 属性有什么作用?当两个碰撞体发生接触时,何时会触发 OnCollisionEnter,何时会触发 OnTriggerEnter

    • 考察点: 对碰撞体模式(实体/触发器)和物理回调的理解。

    • 解析:

      • Is Trigger 作用: 勾选后,Collider 会变为触发器 。触发器只检测物体的进入、停留、离开,但不产生物理阻碍或反弹 。未勾选时,是实体碰撞体,会参与物理模拟,产生阻碍和反弹。

      • OnCollisionEnter 触发条件:

        • 两个都是实体碰撞体

        • 至少一个 碰撞体挂载了 Rigidbody 组件。

        • 它们之间发生了物理接触(碰撞)。

      • OnTriggerEnter 触发条件:

        • 至少一个 碰撞体勾选了 Is Trigger

        • 至少一个 碰撞体挂载了 Rigidbody 组件。

        • 它们之间发生了接触(穿越)。

      • 总结: OnCollision 处理物理世界的"撞击",OnTrigger 处理逻辑世界的"进入/离开区域"。

  3. 问:在频繁进行物理查询(例如在 UpdateFixedUpdate 中)时,你如何优化内存分配以避免 GC 抖动?请举例说明。

    • 考察点:NonAlloc 方法的理解和性能优化意识。

    • 解析:

      • Physics.RaycastAll()Physics.OverlapSphere() 等方法在每次调用时会创建新的数组 来存储结果,这会导致内存垃圾 (GC Allocations) 的产生。当这些操作频繁执行时,GC 会导致游戏卡顿。

      • 为了避免 GC 抖动,应该使用这些方法的 NonAlloc 版本Physics.RaycastNonAlloc()Physics.OverlapSphereNonAlloc() 等。

      • 优化方法: 预先在类中声明一个足够大的数组(例如 private Collider[] _resultsBuffer = new Collider[50];),然后在每次查询时将结果填充到这个预分配的数组中,而不是让 Unity 每次都创建新数组。这样可以消除运行时内存分配。

      • 示例: int numHits = Physics.OverlapSphereNonAlloc(transform.position, radius, _resultsBuffer, layerMask);

  4. 问:你如何可视化调试射线和碰撞范围,以便在 Scene 视图中看到它们?

    • 考察点: 对 Unity 调试工具的掌握。

    • 解析:

      • Debug.DrawLine() / Debug.DrawRay() 用于在运行时(游戏模式下)在 Scene 视图中绘制临时的线条或射线。它们在下一帧会被清除,适合观察动态的轨迹。

      • OnDrawGizmos() / OnDrawGizmosSelected() 用于在 Unity Editor 的 Scene 视图中绘制持久的辅助图形 (Gizmos) ,即使游戏没有运行。OnDrawGizmos() 始终绘制,OnDrawGizmosSelected() 仅当 GameObject 被选中时绘制。适合可视化碰撞体范围、检测半径、AI 视野等。

      • 通过设置 Gizmos.color 可以改变绘制颜色,Gizmos.DrawWireSphere()Gizmos.DrawRay() 等方法用于绘制不同形状。

6.2 深入思考:物理查询的几何原理

虽然 Unity 封装了大部分底层细节,但理解物理查询的几何原理可以帮助你更好地解决问题:

  • 射线与平面相交: 几何上,这涉及到解一个线性方程组,找到直线与平面的交点。

  • 射线与球体相交: 这通常归结为解一个二次方程。球体的公式是 (x−cx)2+(y−cy)2+(z−cz)2=r2,射线的参数方程是 P=O+tD。将射线方程代入球体方程,会得到一个关于参数 t 的二次方程,解出 t 即可得到交点。

  • AABB 盒(轴对齐包围盒)碰撞: 这是游戏中最常用的包围盒类型。两个 AABB 盒碰撞的判断非常高效,只需要判断它们在 X、Y、Z 三个轴上的投影是否重叠。如果所有轴都重叠,则发生碰撞。BoxCollider 在底层就是使用 AABB 或 OBB (Oriented Bounding Box) 进行优化计算的。

了解这些几何原理,可以帮助你在需要实现自定义碰撞检测(例如非标准形状的区域检测、射线穿透不规则多边形)时,能够从数学层面构建解决方案。


总结与展望

本篇教程带你深入了解了 3D 游戏开发中不可或缺的射线与碰撞检测

  • 我们学习了射线 (Ray) 的概念、如何利用 Physics.Raycast() 进行精确的单点检测,以及如何使用 RaycastHit 获取详细的碰撞信息。

  • 深入理解了 Unity 中各种碰撞体 (Collider) 的类型(Box, Sphere, Capsule, Mesh 等)及其应用场景,尤其是 Is Trigger 属性的妙用。

  • 掌握了刚体 (Rigidbody) 在物理模拟中的关键作用,以及碰撞体与刚体的搭配规则。

  • 学会了如何利用 OnCollisionOnTrigger 回调来响应物理事件和逻辑事件。

  • 探索了更高级的碰撞检测方法,如带厚度的射线 (*Cast)范围检测 (Overlap*) ,以及**NonAlloc 版本**进行性能优化的重要性。

  • 强调了使用 Debug.DrawLineGizmos 进行可视化调试的重要性。

射线与碰撞检测是构建游戏交互的核心,无论你的游戏类型如何,它们都将是你最常使用的工具之一。

在下一篇《几何计算与常用算法(实用算法篇)》中,我们将脱离 Unity 的特定组件,回归 3D 数学本身,探讨一些更通用的几何计算方法和实用算法,例如向量投影、点到线/平面的距离、插值以及其他在游戏逻辑中广泛应用的数学技巧。

现在,你对 Unity 的射线和碰撞系统是否已经有了更清晰和全面的认识?在你的开发经历中,有没有遇到过哪些复杂的碰撞检测问题?

Unity3D数学第一篇:向量与点、线、面(基础篇)

Unity3D数学第二篇:旋转与欧拉角、四元数(核心变换篇)

Unity3D数学第三篇:坐标系与变换矩阵(空间转换篇)

Unity3D数学第四篇:射线与碰撞检测(交互基础篇)

Unity3D数学第五篇:几何计算与常用算法(实用算法篇)