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
MediumGive the knight PatrolState and ChaseState in KnightStates.cs, each given the knight.
- Patrol: walk to
waypoints[next]; within0.3, movenexton, looping, and keep it between visits. Closer thansightRange, chase. - Chase: walk to the player; farther than
loseRange, patrol. - 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
1Components, Not Hierarchies
Small ComponentsA Base Enemy ScriptAbstract ComponentsComponents Working TogetherRecap: Modular TurretRecap: Twin Cannon4Serialized Data
Serializable ClassesLists in the InspectorEnums in the InspectorRecap: Loot TableRecap: Roll the Loot7State Machines
States as ClassesThe Alert StateA State MachineTransitionsEnemy BrainsThe Knight AttacksRecap: Door States2Interfaces in Unity
IDamageableInterfaces and TryGetComponentGetComponentsRecap: Interact KeyRecap: Nearest in ReachPractice on your own: Unity playground