Menu
CoddyTech

Enemy Brains

Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 39 of 64.

An enemy's brain is a state machine with one state per behavior. This skeleton walks between its posts, chases a player who comes within 6 units, and swings its sword once a second when it is close. Each state decides its own way out, and the skeleton keeps the helpers they share:

public class Skeleton : MonoBehaviour
{
    public Transform player;
    public Transform[] posts;
    public float speed = 2f;
    public StateMachine machine = new StateMachine();
    public IState patrol, chase, attack;

    void Start()
    {
        patrol = new PatrolState(this);
        chase = new ChaseState(this);
        attack = new AttackState(this);
        machine.ChangeState(patrol);
    }

    void Update() { machine.Tick(); }

    public float PlayerDistance() { return Vector3.Distance(transform.position, player.position); }

    public void WalkTo(Vector3 target)
    {
        target.y = transform.position.y;
        transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
    }
}

public class PatrolState : IState
{
    private Skeleton skel;
    private int next;
    public PatrolState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() < 6f) { skel.machine.ChangeState(skel.chase); return; }
        Vector3 post = skel.posts[next].position;
        post.y = skel.transform.position.y;
        skel.WalkTo(post);
        if (skel.transform.position == post) next = (next + 1) % skel.posts.Length;
    }
}

public class ChaseState : IState
{
    private Skeleton skel;
    public ChaseState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        float d = skel.PlayerDistance();
        if (d > 6f) { skel.machine.ChangeState(skel.patrol); return; }
        if (d < 1.5f) { skel.machine.ChangeState(skel.attack); return; }
        skel.WalkTo(skel.player.position);
    }
}

public class AttackState : IState
{
    private Skeleton skel;
    private float cooldown;
    public AttackState(Skeleton skel) { this.skel = skel; }
    public void Enter() { cooldown = 0f; }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() > 1.5f) { skel.machine.ChangeState(skel.chase); return; }
        cooldown -= Time.deltaTime;
        if (cooldown <= 0f) { Debug.Log("Swing!"); cooldown = 1f; }
    }
}

The states are made once and live as long as the skeleton, so their fields last between visits. PatrolState's next is still set after a chase, and the skeleton walks on to the post it was heading for. Enter resets only what should start over: AttackState's sets cooldown to 0, so the first swing comes as soon as the skeleton reaches the player.

All movement goes through WalkTo, here MoveTowards on the transform, which walks through walls. To move the skeleton with a Rigidbody instead, WalkTo sets the body's velocity and the skeleton ticks the machine in FixedUpdate, where Time.deltaTime is the physics step. The three states do not change: they say where to go, the owner says how.

challenge icon

Challenge

Medium

Give the knight PatrolState and ChaseState in KnightStates.cs, each given the knight.

  1. Patrol: walk to waypoints[next]; within 0.3, move next on, looping, and keep it between visits. Closer than sightRange, chase.
  2. Chase: walk to the player; farther than loseRange, patrol.
  3. In Knight, make the machine and states in Awake, patrol in Start, tick in FixedUpdate.

Press Play, then come near and run. Then Check.

Try it yourself

using UnityEngine;

// PatrolState and ChaseState

All lessons in Game Architecture in Unity

Practice on your own: Unity playground