Under Construction

Prometheus Lab

Portfolio & Resources

Back to Blog
Unity13 min3 views

Tích vô hướng (Dot)

Author

Minh Khoa

Author

Kỹ thuật toán học tối ưu trong Unity dành cho Gameplay Developer

Trong phân mục làm Game hành động (Action/Stealth), việc tính toán xem Player đâm chém từ phía nào của mục tiêu là vô cùng quan trọng. So với việc dùng hàm tính góc tốn kém (Vector3.Angle), một Senior Unity Developer luôn ưu tiên dùng Dot Product (Tích vô hướng) vì tốc độ tính toán phần cứng cực kỳ nhanh và logic dễ kiểm soát.


image.png## 1. Hiểu bản chất Dot Product trong việc xét vị trí

Cú pháp chuẩn trong Unity: float result = Vector3.Dot(VectorA, VectorB); (Lưu ý: VectorA và VectorB đều phải là vector đã được gọi .normalized để đưa chóp vector về đơn vị 1).

Kết quả trả về luôn nằm trong khoảng [-1, 1]:

  • = 1 : Hai vector hoàn toàn cùng hướng.
  • > 0 : Góc giữa hai vector là góc nhọn (< 90 độ), nghĩa là cùng nhìn/hướng về một phía tương đối.
  • = 0 : Hai vector vuông góc.
  • < 0 : Góc giữa hai vector là tù (> 90 độ).
  • = -1: Hai vector ngược cực hoàn toàn (180 độ).

2. Tư duy thiết lập các Vector (Mục tiêu đứng yên)

Để biết bạn (Player) đang đứng trước hay sau kẻ địch (Enemy), ta cần thiết lập 2 vector cơ sở:

  1. Vector A (Hướng kẻ địch đang nhìn): enemy.transform.forward
  2. Vector B (Hướng từ Enemy trỏ tới Player): (player.position - enemy.position).normalized

Suy luận logic Vị trí:

  • Nếu Dot(A, B) > 0: Có nghĩa là Player đang nằm ở Bán cầu TRƯỚC của Enemy (Trong tầm mắt). 👉 Suy ra: Đánh trực tiếp.
  • Nếu Dot(A, B) < 0: Có nghĩa là Player đang nằm ở Bán cầu SAU lưng của Enemy (Ngoài tầm mắt). 👉 Suy ra: Đánh lén (Backstab).

3. Mã Nguồn C# Hệ Thống Cơ Bản

Dưới đây là một Script cơ sở hoàn chỉnh. Xin lưu ý thiết lập ngưỡng Threshold âm sâu thay vì bằng 0 để vùng "ám sát" được thu hẹp, giúp game công bằng hơn.

csharp
using UnityEngine;

public class StealthCombatSystem : MonoBehaviour
{
    [Header("Cấu hình Ám sát")]
    [Tooltip("Ngưỡng góc để tính là sau lưng (-0.5 sẽ xấp xỉ góc 120 độ hẹp ở sau lưng, -1 là thẳng hàng tuyệt đối)")]
    [Range(-1f, 0f)]
    public float backstabThreshold = -0.5f;

    /// <summary>
    /// Được gọi từ Animation Event hoặc Input khi đòn đánh trúng Enemy
    /// </summary>
    public void PerformAttack(Transform player, Transform enemy)
    {
        // 1. Phân lập dữ liệu Hướng
        Vector3 enemyForward = enemy.forward;
        Vector3 dirFromEnemyToPlayer = (player.position - enemy.position).normalized;

        // Bỏ qua trục Y (Chiều cao) nếu game có địa hình đồi dốc
        // Việc này đảm bảo ta chỉ tính trên mặt phẳng ngang 2D X-Z
        enemyForward.y = 0;
        dirFromEnemyToPlayer.y = 0;

        // 2. Tính Tích vô hướng (Dot)
        float dotProduct = Vector3.Dot(
            enemyForward.normalized,
            dirFromEnemyToPlayer.normalized
        );

        // 3. Xử lý Logic
        if (dotProduct <= backstabThreshold)
        {
            Debug.Log("Sát thủ Ám sát! Cơ chế x3 Sát thương.");
            ExecuteAssassination(enemy);
        }
        else
        {
            Debug.Log("Trực diện lộ mặt! Sát thương cơ bản.");
            ExecuteDirectAttack(enemy);
        }
    }

    private void ExecuteAssassination(Transform enemy)
    {
        // Kích hoạt Animation kết liễu
        // Trừ lượng HP lớn hoặc Instant Kill
    }

    private void ExecuteDirectAttack(Transform enemy)
    {
        // Kích hoạt Animation chém bình thường
        // Trừ HP tiêu chuẩn
    }
}

4. 🧠 Senior Bonus: Tránh "Bug lách luật"

Tình huống dở khóc dở cười: Player biết mình đang đứng sau lưng con trùm. Thay vì đánh, Player quay mặt chạy ra xa (quay đít vào Boss) nhưng cố tình vung nút đánh vớ vẩn rớt Hitbox vào Enemy. Nếu chỉ code như trên, game VẪN TÍNH LÀ ÁM SÁT vì điều kiện vị trí thỏa mãn lập tức!

Giải pháp: Phải xét thêm điều kiện: Player có đang nhìn về phía Enemy không?

Dùng thêm vòng kiểm tra thứ 2:

csharp

// Lấy hướng từ Player tới Enemy
Vector3 dirFromPlayerToEnemy = (enemy.position - player.position).normalized;

// Bỏ qua độ cao, chỉ xét trên mặt phẳng XZ
dirFromPlayerToEnemy.y = 0;

// Tính Dot để kiểm tra Player có đang nhìn về phía Enemy hay không
float forwardDotPlayer = Vector3.Dot(
    player.forward,
    dirFromPlayerToEnemy
);

// Điều kiện kép:
// 1. Player đang đứng sau lưng Enemy
// 2. Player đang nhìn về phía Enemy
if (dotProduct <= backstabThreshold && forwardDotPlayer > 0.5f)
{
    ExecuteAssassination(enemy);
}
else
{
    ExecuteDirectAttack(enemy);
}

Tổng Kết

Với ứng dụng triệt để của Vector3.Dot, các hệ cơ chế như Pari đỡ đòn (Shield Block), Góc nhìn lén, Radar tầm nhìn địch (Field of View - FOV), và Ám sát/hỗn chiến đều có thể được tối ưu hóa xuất sắc. Bạn hãy ứng dụng kỹ thuật này cho dự án kế tiếp nhé!