Menu
CoddyTech

Enemy Brains

Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 31 of 50.

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

A knight guards the castle yard. The arrows walk the Player, whose Health has TakeDamage(int amount) (both ready). Knight.cs has the fields the Inspector fills (waypoints W0 to W2, player, playerHealth, speed 3 and the ranges below) and helpers that use its Rigidbody: DistanceTo(point) and PlayerDistance() measure across the ground, WalkTo(target) walks toward a point, Stop() stands still. Give it a brain:

  • In Knight.cs, make the machine and the three states (fields patrol, chase and attack), start patrolling, and tick the machine in FixedUpdate.
  • In KnightStates.cs, write PatrolState, ChaseState and AttackState, each taking the knight in its constructor.
  • Patrol: walk to waypoints[next]; within 0.3 of it, head for the next one, and after the last one back to the first. Closer than sightRange (5), the player is chased. After a chase, the patrol goes on to the waypoint it was heading for.
  • Chase: walk to the player. Closer than attackRange (1.5): attack. Farther than loseRange (8): patrol. Between 5 and 8 the chase goes on, so the knight does not flip between the two at the edge of its sight.
  • Attack: stand still and hit the player for 1 at once, then every second, while they are within attackRange; when they are farther, chase again.

Press Play, let the knight see you, take a hit, then run, then Check.

Try it yourself

using UnityEngine;

// PatrolState, ChaseState and AttackState
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Game Architecture in Unity