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。务必传入 LayerMask 和 maxDistance。避免
每帧循环乱打——AI 可每 0.2 秒朝 4 个方向打一次,而不是每帧。
常见用途:射击(hitscan,无需 Rigidbody 子弹)、
落地检测(跳跃前角色是否站稳)、
鼠标拾取(
Camera.ScreenPointToRay() 把点击变成 3D 射线)、
视线(AI 到玩家之间是否被挡住)。
Raycast 变体
直线射线,只返回 最近的 Collider. 适用于大多数场景。
Physics.Raycast()
球形扫描 — 更适合角色、大型武器和广域检测。
Physics.SphereCast()
返回路径上的 全部 Collider。用于穿甲弹、透过玻璃的灯光。
Physics.RaycastAll()
动手步骤
选定起点与方向
FPS 常用 Camera.main.transform.position 与 .forward;落地检测常用 Vector3.down。
声明 RaycastHit
RaycastHit hit; —— 再以 out hit 传给 Physics.Raycast()。放在 Update 外可减少 GC。
设置 LayerMask
在 Inspector 赋值,或 LayerMask.GetMask("Enemy", "Ground"),只测需要的层。
限制 maxDistance
始终传入最大距离——无限射线会测整场景。例:枪射程 50f,落地检测 1.2f。
在 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