W3D1: Finite state machines, Cinemachine and debugging
Finite state machines in Unity built from MonoBehaviours, a practical Cinemachine tour, and hands-on debugging with breakpoints and Debug.Log.
Lucian Lazar · 23 Jul 2024
Week 3 opens with the theory-and-tools day that sits right before the three-day live build of Duck Invaders. Lucian walks through how to structure enemy AI as a finite state machine using Unity’s own enable/disable lifecycle, then spends a good chunk of the session on Cinemachine, and finishes with breakpoint debugging and a look at how to scope your own project down to something you can actually finish. If you’ve ever written an if with five negated booleans in it and felt bad about it, this session is aimed squarely at you.
What’s covered
Finite state machines
- What an FSM actually is and when you need one: NPCs, weather systems, tower defence wave systems, battle systems. Anything with three or more discrete states.
- The warning signs: nested ifs three or four levels deep, and a pile of booleans like
isSwimming,isDashing,isDodgeRolling. Ten booleans give you 1,024 combinations, and most of them are impossible states you’re still writing checks against. switchstatements as the simplest FSM. Lucian is clear that this is fine, and sometimes preferable, when the states are trivial and the high-level view matters more than separation.- The class-per-state approach, where each state is a MonoBehaviour on its own empty GameObject and only one is enabled at a time.
- Sub-FSMs: the patrolling state is itself a small state machine, and the parent doesn’t care.
The worked example
- An enemy that patrols three points, engages when you come within 15m, attacks at 3m, then disengages and resumes its old patrol target.
EnemyFSM(the brain),EnemyStates(a[Serializable]container so all states show in the inspector), and an abstractEnemyStatebase that exposes shortcuts to the FSM, the player, andChangeState.- Why
protectedmatters, whatabstractvsvirtualgets you, and why exposing a read-only property beats making a field public. - Vector maths you actually use:
Vector3.MoveTowards,Vector3.Distance, subtracting positions to get a direction,normalized, andClampMagnitudefor capping velocity. AddForcewithForceMode.Impulsefor the knockback, and why it lives inFixedUpdate.- Cycling patrol targets with modulo.
Cinemachine
- Installing it from Package Manager, adding a CinemachineBrain to the main camera, and what a virtual camera actually is: a description of a camera state, not a camera.
- Look-at only, follow only, and the FreeLook rig.
- Dead zone and soft zone in the Composer, and why setting the dead zone to zero makes the camera twitch constantly.
- Priority-based camera switching and default blend curves.
- A Prince of Persia style scene where four static cameras hand off automatically based on which one is closest to the player, and how movement input is remapped into camera space so W always means “into the screen”.
- His take on NodeCanvas and visual scripting: fine for visualising states, a trap once things get complex and hard to debug.
Debugging
- Attaching Visual Studio to Unity, setting breakpoints, and writing conditional breakpoints.
- Stepping over and into (F10, F11), inspecting values on hover, and finding a copy-paste bug where both axes read
Input.GetAxis("Horizontal"). Debug.Logwith a context object so clicking the message highlights the right GameObject.
Timestamps
- 00:00:30 — Week 3 plan: what the next five days look like, including the three-day Duck Invaders live build and the publishing session
- 00:08:40 — What a finite state machine is and where you’d use one: NPCs, weather, wave systems
- 00:11:20 — The state pattern recap with the coffee machine example
- 00:13:10 —
switchas the simplest FSM, and when it’s actually the right call - 00:18:00 — The anti-pattern: nested ifs, mutually non-exclusive booleans, and the combinatorial mess they create
- 00:24:00 — Demo of the finished enemy: patrol, engage, attack, knockback, disengage
- 00:30:00 — Inspector walkthrough of each state’s tunable values and the shoot particle effect
- 00:36:00 — Player controller: AddForce accelerating,
ClampMagnitudeto cap velocity - 00:42:00 —
EnemyFSMandEnemyStates,[Serializable], and initialising states with a back-reference - 00:55:00 — The abstract
EnemyStatebase,protectedmembers, and shortcut properties - 01:00:00 —
ChangeStateand why one funnel keeps the machine consistent - 01:10:00 — Engaging state and
Vector3.MoveTowardsexplained with a drawing - 01:25:00 — Attacking state: instantiating the effect,
SetPositionAndRotation, cooldown timing,Destroywith a delay, impulse knockback - 01:52:00 — Patrolling state and cycling targets with modulo
- 02:00:00 — NodeCanvas and why Lucian avoids visual scripting for real logic
- 02:05:00 — Installing Cinemachine, adding the brain, creating your first virtual camera
- 02:15:00 — Composer aim: dead zone, soft zone, and what they do to camera feel
- 02:32:00 — FreeLook rig and locking player rotation to the camera, transforming input into camera space
- 02:40:00 — Multi-camera transitions and the script that activates the nearest vcam
- 02:55:00 — Debugging: attaching the debugger, conditional breakpoints, stepping,
Debug.Logwith context - 03:05:00 — Cutting the Duck Invaders GDD down to a three-day scope, and tips for building your own 2D platformer level
What you build
A patrolling enemy with four states, driven by a MonoBehaviour-based FSM where each state is a GameObject that gets enabled and disabled. Unity calls OnEnable, Update and OnDisable for you, which means you get state entry, tick and exit for free instead of writing your own dispatcher and timer. The enemy walks between three trees, spots you at 15m, chases faster than it patrols, stops at 3m to shoot a particle burst that knocks you back, and returns to whichever patrol point it was heading for before you interrupted it.
On the Cinemachine side you get four separate scenes: a look-at camera that tracks the player without moving, a follow camera using the framing transposer, a FreeLook orbit rig, and a four-camera scene that hands off automatically.
public void ChangeState(EnemyState newState)
{
if (currentState != null)
currentState.enabled = false; // OnDisable fires here
currentState = newState;
currentState.enabled = true; // OnEnable fires here
}
```csharp
// Engaging: close the gap, face the player, decide what's next
float distanceToMoveThisFrame = moveSpeed * Time.deltaTime;
enemy.position = Vector3.MoveTowards(enemy.position, player.position, distanceToMoveThisFrame);
enemy.LookAt(player);
float distanceToPlayer = Vector3.Distance(enemy.position, player.position);
if (distanceToPlayer > engageRange) { ChangeState(states.disengaging); return; }
if (distanceToPlayer <= states.attacking.AttackRange) ChangeState(states.attacking);
The nice property here is that only one state object is ever enabled, so two states can never fight over the same transform in the same frame. Lucian talks through this on stream as he double-checks his own code.
There's honest self-correction throughout. He spots that disabling states in `Awake` doesn't stop `OnEnable` from having already fired, changes them to start deactivated in the scene, swaps a parented projectile for a detached one with `SetPositionAndRotation`, and converts empty virtual methods to abstract so subclasses are forced to implement them. Then he reverses that when he realises abstract removes the option of leaving a state hook empty.
## Who should watch this
If you've got a player controller working but your enemy logic is turning into a swamp of flags, this is the session that fixes it. It's also the clearest Cinemachine introduction in the course, and useful even if you skip the FSM half. The debugging section is short but worth it if you're still diagnosing everything with print statements and guesswork.
Part of the full curriculum. Course overview.