Unity 2D NavMesh AI C# Architecture

Pacman Remake

Een moderne hercreatie van de klassieke arcade hit, ontwikkeld in Unity met geavanceerde pathfinding, dynamic input support en strakke gameplay mechanics.

Over het Project

Ik werd uitgedaagd door mijn vrienden om zo snel mogelijk een volledig functionele Pacman-kloon te ontwikkelen. Hoewel er nog een paar uitbreidingen ontbreken, is de core loop van de gameplay – inclusief spelerinvoer, ghost AI en collectibles – volledig operationeel!

Player Movement

De KeyInput ondersteunt WASD, pijltoetsen en joysticks. Ik gebruik Unity's nieuwe Input System in combinatie met een Rigidbody voor responsieve, soepele bewegingen.

PlayerMovement.cs
C#
using UnityEngine;
using UnityEngine.InputSystem;

[SelectionBase]
public class PlayerMovement : MonoBehaviour
{
    private Rigidbody _Rigidbody;
    private InputAction _MoveAction;
    private PlayerInput _PlayerInput;

    [SerializeField] private float _PlayerSpeed;

    private void Start()
    {      
        _PlayerInput = GetComponent();
        _MoveAction = _PlayerInput.actions.FindAction("Move");

        _Rigidbody = GetComponent();
    }

    private void Update()
    {
        CalculateMovement();
    }

    private void CalculateMovement()
    {
        Vector2 direction = _MoveAction.ReadValue().normalized;
        Vector3 movement = new Vector3(direction.x, 0f, direction.y);
        _Rigidbody.velocity = movement * _PlayerSpeed;
    }
}

Collectibles

Om ervoor te zorgen dat de speler de pellets kan verzamelen en punten scoort, is er een lichtgewicht trigger-handler geschreven.

CollectibleHandler.cs
C#
using UnityEngine;

public class CollectibleHandler : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Coin"))
        {
            GameManager.Instance.GetPointValue(other.GetComponent().GetPoints());
            Destroy(other.gameObject);
        }
    }
}

Player Health

Dit script verwerkt de levens van de speler en communiceert direct met de centrale GameManager bij opgelopen schade.

PlayerHealth.cs
C#
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    public void ChangeHealth(int Damage)
    {
        GameManager.Instance.PlayerHasDied = true;
        GameManager.Instance.Lives -= Damage;
        GameManager.Instance.HandleChangeHealth(this.gameObject);
    }
}

Enemy Manager

De EnemyManager beheert de spoken bij het begin van het spel, inclusief een gecontroleerde start-vertraging via een Coroutine.

EnemyManager.cs
C#
using MyBox;
using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class EnemyManager : Singleton
{
    public float EnemyWaitTime;
    public GameObject[] Enemies;

    private void Awake()
    {
        InitializeSingleton(false);
        StartSendingEnemies();
    }

    private void StartSendingEnemies()
    {
        StartCoroutine(SendEnemiesWithDelay());
    }

    private IEnumerator SendEnemiesWithDelay()
    {
        foreach (GameObject enemy in Enemies)
        {
            yield return new WaitForSeconds(EnemyWaitTime);
            enemy.GetComponent().enabled = true;
            enemy.GetComponent().CanMove = true;
        }
    }
}

Enemy Movement

De beweging van de spoken wordt dynamisch aangestuurd via Unity's NavMeshAgent om de speler over het speelveld te achtervolgen.

EnemyMovement.cs
C#
using MyBox;
using UnityEngine;
using UnityEngine.AI;

[SelectionBase] [RequireTag("Enemy")]
public class EnemyMovement : MonoBehaviour
{
    private NavMeshAgent _EnemyNavmeshAgent;

    public bool CanMove = false;

    [SerializeField] private GameObject _Player;
    [SerializeField] private string _PlayerString;

    private void Start()
    {
        _EnemyNavmeshAgent = GetComponent();
        FindPlayerByTag(_PlayerString);
    }

    private void Update()
    {
        if (CanMove) GoToPlayer();
    }

    public void GoToPlayer() => GoToDestination(_Player.transform);

    private void FindPlayerByTag(string tag)
    {
        _Player = GameObject.FindGameObjectWithTag(tag);
    }

    private void GoToDestination(Transform destination)
    {
        _EnemyNavmeshAgent.SetDestination(destination.position);
    }
}

Enemy Attack

Wanneer een spook in aanraking komt met de speler, voert het schade uit en activeert het een cooldown-timer om meervoudige hits te voorkomen.

EnemyAttack.cs
C#
using UnityEngine;
using System.Collections;

public class EnemyAttack : MonoBehaviour
{
    private bool _Attacked = false;
    [SerializeField] private float _AttackCooldown = 0.5f;
  
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player") && _Attacked == false)
        {
            _Attacked = true;
            other.GetComponent().ChangeHealth(1);
            StartCoroutine(ResetAttackCoroutine());
        }
    }

    private IEnumerator ResetAttackCoroutine()
    {
        yield return new WaitForSeconds(_AttackCooldown);
        _Attacked = false;
    }
}

Animatie Keyframes

Voor de introductie-sequentie zijn specifieke animatie-keyframes gebruikt die de startposities van de speler en de spoken afstemmen.

Player Animation Keyframes
Player Movement Animation Keyframes
Enemy Animation Keyframes
Enemy Patrol & Spawn Animation Keyframes