Unity 2D: tạo kẻ địch đơn giản biết tuần tra và đuổi theo người chơi

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Một game thú vị cần có thử thách, và thử thách thường đến từ kẻ địch. Bạn không cần trí tuệ nhân tạo phức tạp: một máy trạng thái đơn giản gồm tuần tra và đuổi theo đã đủ để tạo cảm giác kẻ địch có "đầu óc". Bài này hướng dẫn cách làm trong Unity 2D.

Ý tưởng: máy trạng thái hữu hạn

Kẻ địch của ta có hai trạng thái. Ở trạng thái Tuần tra, nó đi qua lại giữa hai điểm. Khi người chơi bước vào bán kính phát hiện, nó chuyển sang Đuổi theo và chạy về phía người chơi. Nếu người chơi chạy thoát ra khỏi một bán kính lớn hơn, nó quay về tuần tra. Việc dùng hai bán kính khác nhau (phát hiện nhỏ, mất dấu lớn) giúp tránh hiện tượng kẻ địch chuyển trạng thái liên tục khi người chơi đứng sát ranh giới.

Bước 1: Chuẩn bị đối tượng

  1. Tạo Sprite cho kẻ địch (ví dụ hình vuông màu đỏ), đặt tên Enemy.
  2. Thêm Rigidbody 2D (Freeze Rotation Z) và Box Collider 2D.
  3. Tạo hai GameObject trống làm điểm tuần tra, đặt tên PointA và PointB, đặt ở hai đầu đoạn đường.
  4. Đảm bảo nhân vật có Tag Player.

Bước 2: Script kẻ địch

using UnityEngine;

public class EnemyAI : MonoBehaviour
{
    private enum State { Patrol, Chase }

    [Header("Di chuyển")]
    [SerializeField] private float patrolSpeed = 2f;
    [SerializeField] private float chaseSpeed = 3.5f;
    [SerializeField] private Transform pointA;
    [SerializeField] private Transform pointB;

    [Header("Phát hiện")]
    [SerializeField] private float detectRadius = 5f;
    [SerializeField] private float loseRadius = 8f;

    private Rigidbody2D rb;
    private Transform player;
    private Transform currentTarget;
    private State state = State.Patrol;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        currentTarget = pointB;

        GameObject p = GameObject.FindGameObjectWithTag("Player");
        if (p != null) player = p.transform;
    }

    void FixedUpdate()
    {
        UpdateState();

        if (state == State.Patrol) Patrol();
        else Chase();
    }

    private void UpdateState()
    {
        if (player == null) return;

        float distance = Vector2.Distance(transform.position, player.position);

        if (state == State.Patrol && distance <= detectRadius)
            state = State.Chase;
        else if (state == State.Chase && distance >= loseRadius)
            state = State.Patrol;
    }

    private void Patrol()
    {
        MoveTowards(currentTarget.position, patrolSpeed);

        if (Mathf.Abs(transform.position.x - currentTarget.position.x) < 0.2f)
        {
            currentTarget = currentTarget == pointA ? pointB : pointA;
        }
    }

    private void Chase()
    {
        MoveTowards(player.position, chaseSpeed);
    }

    private void MoveTowards(Vector2 target, float speed)
    {
        float dir = Mathf.Sign(target.x - transform.position.x);
        rb.velocity = new Vector2(dir * speed, rb.velocity.y);

        // lật hình theo hướng đi
        Vector3 scale = transform.localScale;
        scale.x = Mathf.Abs(scale.x) * dir;
        transform.localScale = scale;
    }

    void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, detectRadius);
        Gizmos.color = Color.gray;
        Gizmos.DrawWireSphere(transform.position, loseRadius);
    }
}

Một số điểm cần giải thích. Kiểu enum giúp trạng thái có tên rõ ràng thay vì số khó nhớ. Thuộc tính [Header] chia nhóm các tham số trong Inspector. Hàm OnDrawGizmosSelected vẽ hai vòng tròn trong cửa sổ Scene khi bạn chọn kẻ địch, rất tiện để chỉnh bán kính bằng mắt. Với các phiên bản Unity mới, nếu rb.velocity bị đánh dấu lỗi thời, hãy dùng rb.linearVelocity theo gợi ý của trình biên dịch.

Bước 3: Gây sát thương cho người chơi

Thêm một hàm xử lý va chạm vào cùng script. Đơn giản nhất là tải lại màn chơi khi chạm kẻ địch:

using UnityEngine.SceneManagement;

private void OnCollisionEnter2D(Collision2D collision)
{
    if (collision.gameObject.CompareTag("Player"))
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

Nếu muốn nhân vật có máu, hãy gọi một hàm TakeDamage trên script của nhân vật thay vì tải lại cảnh. Đừng quên thêm scene vào File > Build Settings để LoadScene hoạt động.

Cân bằng độ khó

  • Để tốc độ đuổi theo gần bằng hoặc chậm hơn người chơi một chút, người chơi mới có cơ hội thoát.
  • Cho kẻ địch dừng một nhịp ngắn ở mỗi điểm tuần tra để tạo cảm giác tự nhiên.
  • Dùng Raycast để kiểm tra có tường chắn tầm nhìn không, tránh phát hiện xuyên tường.
  • Thêm tiếng kêu hay biểu tượng dấu chấm than khi phát hiện, giúp người chơi hiểu điều gì đang xảy ra.

Kết luận

Hai trạng thái và hai bán kính là đủ để kẻ địch trông có vẻ thông minh. Khi quen, bạn có thể thêm trạng thái tấn công, bỏ chạy hoặc tìm đường bằng NavMesh và các thuật toán như A*.

Để học làm game có chiều sâu hơn với nhiều kỹ thuật AI, hãy xem các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment