Source file unity-csharp/arcade-classics/pac-man-lite/GridMover.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: a shared base class that moves an actor smoothly cell-to-cell and asks subclasses which way to turn
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[csharp-inheritance]]
 
using UnityEngine;
 
// Both the player and the ghosts move the same way: glide to the next cell
// centre, then decide a direction. That shared mechanism lives here; the
// decision (read input vs run AI) is left abstract for subclasses. This is a
// small, concrete use of [[csharp-inheritance]].
public abstract class GridMover : MonoBehaviour
{
    [SerializeField] protected float speed = 4f;
 
    public Vector2Int CurrentCell { get; private set; }
 
    protected MazeGrid maze;
    protected Vector2Int currentDir = Vector2Int.zero;
 
    private Vector2Int targetCell;
    private Vector3 targetPos;
    private bool hasTarget;
 
    public virtual void Init(MazeGrid grid, Vector2Int startCell)
    {
        maze = grid;
        SnapTo(startCell);
    }
 
    public void SnapTo(Vector2Int cell)
    {
        CurrentCell = cell;
        transform.position = maze.CellToWorld(cell);
        currentDir = Vector2Int.zero;
        hasTarget = false;
    }
 
    private void Update()
    {
        if (maze == null)
        {
            return;
        }
 
        OnTick();
        StepMovement();
    }
 
    private void StepMovement()
    {
        if (!hasTarget)
        {
            // At a cell centre: ask the subclass which way to go next.
            Vector2Int dir = ChooseDirection(CurrentCell, currentDir);
            if (dir != Vector2Int.zero && maze.IsWalkable(CurrentCell + dir))
            {
                currentDir = dir;
                targetCell = CurrentCell + dir;
                targetPos = maze.CellToWorld(targetCell);
                hasTarget = true;
            }
            else
            {
                return; // nowhere to go this frame — idle
            }
        }
 
        transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
        if ((transform.position - targetPos).sqrMagnitude < 0.0001f)
        {
            transform.position = targetPos;
            CurrentCell = targetCell;
            hasTarget = false;
            OnArrived(CurrentCell);
        }
    }
 
    // Runs every frame before movement — used by the player to buffer input.
    protected virtual void OnTick() { }
 
    // Decide the next grid direction from the current cell. Vector2Int.zero = stay.
    protected abstract Vector2Int ChooseDirection(Vector2Int cell, Vector2Int dir);
 
    // Called once each time the actor reaches a new cell centre.
    protected virtual void OnArrived(Vector2Int cell) { }
}