Unity Term Book
Physics

Raycasting

Raycasting bắn tia vô hình theo một hướng để phát hiện Collider — nền tảng của bắn súng, kiểm tra mặt đất, pick chuột và gameplay line-of-sight trong Unity.

Hãy tưởng tượng...

Raycast giống như đèn pin trong bóng tối. Bạn rọi đèn theo một hướng — tia sáng đi thẳng cho đến khi chạm vào vật gì đó. Ngay lúc đó, đèn "báo cáo" cho bạn biết: đã chạm vào cái gì, ở khoảng cách bao nhiêu, tại điểm nào, và bề mặt hướng như thế nào. Trong game bắn súng, Raycast thay cho viên đạn vật lý thực — nhẹ hơn, nhanh hơn, chính xác hơn.

Khái niệm chi tiết

Physics.Raycast() nhận origin (điểm xuất phát), direction (hướng bắn), và trả về true nếu tia chạm vào Collider nào đó. Kết quả chi tiết được lưu trong struct RaycastHit: vị trí điểm chạm (.point), hướng bề mặt ( .normal), khoảng cách (.distance), và object bị chạm ( .collider).

Unity cung cấp nhiều biến thể Raycast cho các use case khác nhau: Physics.RaycastAll() trả về tất cả Collider bị xuyên qua (thay vì chỉ cái gần nhất), Physics.SphereCast() bắn tia hình cầu (giúp nhân vật “snapping” mịn hơn), Physics.BoxCast() bắn hình hộp. Từ Unity 2022+, Physics.RaycastCommand cho phép chạy Raycast song song trên Job System.

Hiệu năng: Raycast không rẻ — mỗi lần gọi tra cứu toàn bộ broadphase BVH tree. Luôn truyền LayerMask để lọc layer không cần thiết, và giới hạn maxDistance. Tránh gọi trong vòng lặp không cần thiết — một AI có thể raycast 4 hướng mỗi 0.2 giây thay vì mỗi frame.

Ứng dụng phổ biến: shooting (hitscan weapon không dùng Rigidbody đạn), ground check (kiểm tra nhân vật đứng trên mặt đất trước khi cho phép nhảy), mouse picking ( Camera.ScreenPointToRay() chuyển điểm chuột thành Ray 3D), line of sight (AI kiểm tra có vật cản giữa mình và player không).

Các biến thể Raycast

Raycast

Tia thẳng, chỉ trả về Collider gần nhất. Dùng cho hầu hết mọi thứ.

Physics.Raycast()

SphereCast

Tia hình cầu — tốt hơn cho nhân vật, vũ khí cỡ lớn, phát hiện khu vực rộng.

Physics.SphereCast()

RaycastAll

Trả về tất cả Collider trên đường đi. Dùng cho đạn xuyên giáp, đèn chiếu qua kính.

Physics.RaycastAll()

Hướng dẫn thực hành

1

Xác định origin và direction

Thường là Camera.main.transform.position và Camera.main.transform.forward cho FPS, hoặc transform.down cho ground check.

2

Khai báo biến RaycastHit

RaycastHit hit; — sau đó truyền out hit vào Physics.Raycast(). Luôn khai báo ngoài Update để tránh garbage collection.

3

Thiết lập LayerMask

Tạo LayerMask trong Inspector hoặc dùng LayerMask.GetMask("Enemy", "Ground") để chỉ kiểm tra layer cần thiết.

4

Giới hạn maxDistance

Luôn truyền khoảng cách tối đa — tia vô hạn kiểm tra toàn scene, lãng phí. Ví dụ: tầm bắn súng 50f, ground check 1.2f.

5

Vẽ Debug Ray trong Scene

Debug.DrawRay(origin, direction * maxDist, Color.green) — hiển thị tia trong Scene View khi play, không ảnh hưởng build.

Trình mô phỏng tương tác

Di chuyển chuột trên canvas để xem tia Raycast từ nguồn sáng. Kéo các vật cản xung quanh.

Di chuyển chuột...

Chưa có hit

Ví dụ Code

Cơ bản

Hitscan shooting — bắn súng không dùng 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; // Khai báo ngoài để tránh GC

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

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

  void Fire()
  {
      muzzleFlash.Play();

      // Bắn ray từ tâm camera về phía trước
      if (Physics.Raycast(cam.transform.position,
                         cam.transform.forward,
                         out hit, range, shootMask))
      {
          Debug.Log("Bắn trúng: " + hit.collider.name +
                     " cách " + hit.distance.ToString("F1") + "m");

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

Ví dụ Code

Nâng cao

Ground check chính xác + mouse-to-world picking, kết hợp cả hai use case phổ biến nhất.

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 mỗi frame — SphereCast mịn hơn Raycast
      isGrounded = Physics.SphereCast(
          transform.position, 0.4f,
          Vector3.down, out hit,
          groundCheckDist, groundLayer
      );

      // Click chuột → chuyển sang toạ độ 3D
      if (Input.GetMouseButtonDown(0))
      {
          Ray ray = cam.ScreenPointToRay(Input.mousePosition);
          if (Physics.Raycast(ray, out hit, 100f, clickable))
          {
              Debug.Log("Click vào: " + hit.collider.name);
              // Di chuyển nhân vật đến hit.point
              MoveTo(hit.point);
          }
      }
  }

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

📌 Ghi nhớ nhanh

  • Luôn truyền LayerMask + maxDistance để tối ưu
  • RaycastHit khai báo một lần ngoài Update (tránh GC)
  • Camera.ScreenPointToRay() cho mouse-to-3D
  • Debug.DrawRay() để xem tia trong Scene View
  • SphereCast mượt hơn Raycast cho character movement

⚠️ Lỗi thường gặp

  • ❌ Raycast trúng chính mình (self-hit)

    Origin nằm bên trong Collider của người bắn — luôn trả về true

    ✅ Đặt origin ra ngoài (+0.1f theo hướng forward), hoặc ignore layer của mình

  • ❌ Raycast mỗi frame không có MaxDistance

    Tia dài vô hạn kiểm tra toàn bộ scene, gây giảm FPS nghiêm trọng

    ✅ Luôn truyền maxDistance phù hợp với use case