Nguyễn Đình Cường
0 comment
09 Nov, 2026
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.
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.
Enemy.PointA và PointB, đặt ở hai đầu đoạn đường.Player.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.
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.
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.
Nguyễn Đình Cường
0 comment