Unity Term Book
物理

Raycasting

Raycasting 沿某方向发射不可见射线以检测 Collider,是 Unity 射击判定、落地检测、鼠标拾取与视线判定的基础技术。Physics.Raycast 与 LayerMask 配合可精确过滤目标,帮助程序实现准星交互、AI 感知与场景查询,同时注意性能开销与 Scene 视图调试可视化。

想象一下...

Raycast 就像黑暗中的手电筒。你朝一个方向照——光束前进直到碰到东西。然后它报告碰到了谁多远在哪,以及表面朝哪。在射击游戏里,Raycast 常代替真实物理子弹——更省、更快、更准。

概念详解

Physics.Raycast() 接收起点、方向,若射线命中 Collider 则返回 true。 细节写入 RaycastHit:命中位置( .point)、表面方向(.normal)、距离( .distance),以及物体(.collider)。

Unity 还提供多种变体:Physics.RaycastAll() 返回路径上所有 Collider (不只是最近的)、Physics.SphereCast() 扫过球体(角色“贴地”更顺)、 Physics.BoxCast() 扫过盒子。从 Unity 2022+ 起, Physics.RaycastCommand 可在 Job System 上并行跑射线。

性能:Raycast 不是免费的——每次调用都会遍历 broadphase BVH。务必传入 LayerMaskmaxDistance。避免 每帧循环乱打——AI 可每 0.2 秒朝 4 个方向打一次,而不是每帧。

常见用途:射击(hitscan,无需 Rigidbody 子弹)、 落地检测(跳跃前角色是否站稳)、 鼠标拾取Camera.ScreenPointToRay() 把点击变成 3D 射线)、 视线(AI 到玩家之间是否被挡住)。

Raycast 变体

Raycast

直线射线,只返回 最近的 Collider. 适用于大多数场景。

Physics.Raycast()

SphereCast

球形扫描 — 更适合角色、大型武器和广域检测。

Physics.SphereCast()

RaycastAll

返回路径上的 全部 Collider。用于穿甲弹、透过玻璃的灯光。

Physics.RaycastAll()

动手步骤

1

选定起点与方向

FPS 常用 Camera.main.transform.position 与 .forward;落地检测常用 Vector3.down。

2

声明 RaycastHit

RaycastHit hit; —— 再以 out hit 传给 Physics.Raycast()。放在 Update 外可减少 GC。

3

设置 LayerMask

在 Inspector 赋值,或 LayerMask.GetMask("Enemy", "Ground"),只测需要的层。

4

限制 maxDistance

始终传入最大距离——无限射线会测整场景。例:枪射程 50f,落地检测 1.2f。

5

在 Scene 视图画 Debug Ray

Debug.DrawRay(origin, direction * maxDist, Color.green) —— 运行时可见,构建包中无效果。

交互模拟器

在画布上移动鼠标瞄准射线。拖动障碍物。

移动鼠标...

尚未命中

代码示例

基础

Hitscan 射击——无需 Rigidbody 子弹。

using UnityEngine;

public class HitscanGun : MonoBehaviour
{
  public float range = 50f;
  public int damage = 25;
  public LayerMask shootMask;
  public ParticleSystem muzzleFlash;

  Camera cam;
  RaycastHit hit; // Declare outside Update to avoid GC

  void Start() => cam = Camera.main;

  void Update()
  {
      if (Input.GetButtonDown("Fire1")) Fire();
  }

  void Fire()
  {
      muzzleFlash.Play();

      // Ray from camera center, forward
      if (Physics.Raycast(cam.transform.position,
                         cam.transform.forward,
                         out hit, range, shootMask))
      {
          Debug.Log("Hit: " + hit.collider.name +
                     " at " + hit.distance.ToString("F1") + "m");

          hit.collider.GetComponent<Health>()
             ?.TakeDamage(damage);
      }
  }
}

代码示例

进阶

精确落地检测 + 鼠标到世界拾取——两个最常见用例。

using UnityEngine;

public class RaycastUtils : MonoBehaviour
{
  [Header("Ground Check")]
  public float groundCheckDist = 1.2f;
  public LayerMask groundLayer;
  bool isGrounded;

  [Header("Mouse Picking")]
  public LayerMask clickable;

  Camera cam;
  RaycastHit hit;

  void Awake() => cam = Camera.main;

  void Update()
  {
      // Ground check every frame — SphereCast is smoother than Raycast
      isGrounded = Physics.SphereCast(
          transform.position, 0.4f,
          Vector3.down, out hit,
          groundCheckDist, groundLayer
      );

      // Click → world position
      if (Input.GetMouseButtonDown(0))
      {
          Ray ray = cam.ScreenPointToRay(Input.mousePosition);
          if (Physics.Raycast(ray, out hit, 100f, clickable))
          {
              Debug.Log("Clicked: " + hit.collider.name);
              // Move the character to hit.point
              MoveTo(hit.point);
          }
      }
  }

  void OnDrawGizmosSelected()
  {
      Gizmos.color = isGrounded ? Color.green : Color.red;
      Gizmos.DrawLine(transform.position,
          transform.position + Vector3.down * groundCheckDist);
  }
}

📌 快速记忆

  • 始终传入 LayerMask + maxDistance
  • 在 Update 外声明一次 RaycastHit(避免 GC)
  • 鼠标到 3D 用 Camera.ScreenPointToRay()
  • 用 Debug.DrawRay() 在 Scene 视图看射线
  • 角色移动用 SphereCast 比 Raycast 更顺

⚠️ 常见错误

  • ❌ 射线打到自己(self-hit)

    起点落在射击者 Collider 内——永远返回 true

    ✅ 把起点沿 forward 偏移(+0.1f),或忽略自己的层

  • ❌ 每帧 Raycast 且无 MaxDistance

    无限射线测整场景,FPS 暴跌

    ✅ 始终传入与用途匹配的 maxDistance