You implement attacking and add bIsAttacking. You implement dashing and add bIsDashing. You add death handling and bIsDead. Before long you get "the corpse plays the attack animation" and "attacking during a dash makes the character slide forever."
The cause is that you are holding mutually exclusive states in separate booleans. UE gives you Blueprint Enum and Switch on Enum to narrow state down to a single variable. This article explains how boolean-based state breaks down, how to design state management with an Enum, and how to build a patrolling enemy AI step by step.
What You'll Learn
- How adding booleans makes combinations explode as 2 to the Nth power
- How to create a Blueprint Enum and set Display Names
- How to branch per state with Switch on Enum
- Why state changes should be funneled through a single SetState function
- How this differs from the AnimBP State Machine (logic vs. visuals)
- Hands-on: an enemy AI that goes Patrol → Chase → Search → Return Home using an Enum
Why Booleans Break Down
Holding state in booleans works fine at first. With one or two, a Branch is all you need.
The trouble starts at three. The moment you hold bIsMoving, bIsAttacking, and bIsDead, the variables can take 2 × 2 × 2 = 8 combinations.

| bIsMoving | bIsAttacking | bIsDead | Meaning |
|---|---|---|---|
| false | false | false | Idle |
| true | false | false | Moving |
| false | true | false | Attacking |
| false | false | true | Dead |
| true | true | false | Attacking while moving? |
| true | false | true | Moving while dead |
| false | true | true | Attacking while dead |
| true | true | true | Everything at once |
Only the top four mean anything. The other four are combinations that must never happen. Blueprint will not stop them, though. Preventing impossible states is entirely on you.
With five booleans you get 2 to the fifth, or 32 combinations, of which six are valid. Every flag you add doubles the number of combinations you have to keep straight.
The nasty part is that this does not break right when you write it. One missing line, "I forgot to set bIsAttacking back to false in the death handler," shows up days later as "sometimes corpses attack."
An Enum removes the problem structurally. There is one state variable and it can hold exactly one value, so impossible combinations cannot be created in the first place. The whole category of "forgot to reset the flag" disappears.
Creating an Enum
An Enum (enumeration) is a type that holds exactly one option out of a predefined list.
Create one by right-clicking in the Content Browser → Blueprints → Enumeration. By convention the name starts with E_. We'll call ours E_EnemyState.
Open it and add entries with the Add Enumerator button. Each entry has two fields.
| Field | Meaning |
|---|---|
| Display Name | The name shown in Blueprint dropdowns and on pins |
| Description | The tooltip shown on hover. Fine to leave empty |

Changing a Display Name later does not break the Blueprints that use it, because it is treated as a display-only change.
The top entry becomes the default. When you create a variable of the Enum type, its initial value is automatically the first item in the list. Put your "doing nothing" state at the top and a forgotten default value never turns into a bug.
Once created, the Enum can be used directly as a variable type. Add a variable named CurrentState to BP_Enemy and type E_EnemyState in the type search box.
Hold exactly one state variable. That is the starting point of a state machine.
Branching with Switch on Enum
Drag the Enum variable into the graph, pull a wire off the pin, and search for switch to get the Switch on E_EnemyState node.

This node lists one execution pin per Enum entry. Only the pin matching the value wired into Selection executes.
The result is the same as chaining four Branch nodes, but the readability is not.
| Chained Branches | Switch on Enum | |
|---|---|---|
| How branching looks | Conditions stretch downward | Every possible value lines up as a pin |
| Missing cases | Hard to notice | Unconnected pins are visible at a glance |
| Adding an entry | You add a Branch yourself | A pin appears automatically |
Unlike Switch on Int, the Enum version shows every option as a pin. Being able to spot "I never wrote the handling for this state" instantly is its biggest advantage.
The way you structure it mirrors match in Godot or switch in C#. Keep only the entry points per state right after the Switch and push the bodies into functions to keep it readable.
Event Think (your own custom event)
→ Switch on E_EnemyState (Selection: CurrentState)
Patrol → ThinkPatrol (function)
Chase → ThinkChase (function)
Search → ThinkSearch (function)
Return Home → ThinkReturnHome (function)
Funnel State Changes Through SetState
Here is the key design point. When you change state, do not scatter Set CurrentState nodes around.
The reason is that a state change always brings along "things to do at that exact moment." Raise movement speed when entering chase, play a montage when entering attack, stop a timer when leaving patrol. A bare Set node skips all of it.
So create a single function dedicated to state changes.

Function: SetState (input: NewState / E_EnemyState)
→ Branch (Condition: CurrentState == NewState)
True → Return Node (prevents re-entering the same state)
False → ExitState (State: CurrentState) ← leaving the current state
→ Set CurrentState (NewState) ← the only place the variable is written
→ EnterState (State: CurrentState) ← entering the new state
Compare two Enums with the Equal (E_EnemyState) node. Pull off an Enum variable pin and search for ==.
Both EnterState and ExitState are built on Switch on E_EnemyState.
Function: EnterState (input: State / E_EnemyState)
→ Switch on E_EnemyState (Selection: State)
Patrol → Set Max Walk Speed (200) → Move To the next patrol point
Chase → Set Max Walk Speed (600)
Search → Set SearchTimer (0.0)
Return Home → Set Max Walk Speed (200) → Move To HomeLocation
Function: ExitState (input: State / E_EnemyState)
→ Switch on E_EnemyState (Selection: State)
Chase → Set LastSeenLocation (record the player's current position)
Others → do nothing
This shape buys you three things.
- Everything that happens at the moment of transition lives in one place: "Raise speed when entering chase" has exactly one home, a single line in
EnterState - Re-entering the same state is rejected: Thanks to the Branch at the top, calling
SetState(Chase)every frame only sets the speed once - It is easy to debug: Drop one
Print StringinsideSetStateand the full transition history appears on screen (see Print String debugging)
The only place allowed to write the state is inside SetState. Whether you follow that rule decides whether the design stays traceable later.
How This Differs from the AnimBP State Machine
UE has another thing called a state machine: the State Machine in an Animation Blueprint. The shared name causes confusion, but they handle completely different things.

| The Enum state machine in this article | AnimBP State Machine | |
|---|---|---|
| Where it lives | Actor / Character Blueprint | Animation Blueprint |
| What it manages | Game logic state (chasing, attacking) | Visual state (which animation plays) |
| Transition conditions | Distance, input, HP, etc. | Values received from the logic side |
| Who changes the state | The SetState you wrote | Conditions set on the transition arrows |
The division of labor is clear: logic decides, AnimBP follows. If both sides make decisions you end up with "logic says attacking, visuals say walking."
Syncing is easy: read the owning character's CurrentState from the AnimBP's Event Graph and store it in an AnimBP variable. The AnimBP's transition conditions then only look at that variable.
AnimBP Event Blueprint Update Animation
→ Try Get Pawn Owner → Cast To BP_Enemy
→ Get CurrentState → Set AnimState (an AnimBP variable of type E_EnemyState)
The AnimBP side is covered in State Machines and Blend Spaces in Animation Blueprints. You can use the Enum directly as a transition condition, so the two fit together naturally.
Hands-On: A Patrolling Enemy AI with an Enum
Guards in a stealth game, trash mobs in an action RPG, patrolling units in a tower defense. "Patrol normally, chase when you spot someone, search when you lose them, give up and go home" is the baseline enemy AI in every genre. We'll build it with four states.
What It Looks Like Running
The enemy paces between fixed points. When the player gets within a certain distance it speeds up and gives chase. Run away and it comes to the last place it saw you, stands there for three seconds, then heads back to its original spot and resumes patrolling.

| Transition | Condition |
|---|---|
| Patrol → Chase | Distance to player is 1200 or less |
| Chase → Search | Distance to player is greater than 1800 |
| Search → Chase | Distance drops back to 1200 or less while searching |
| Search → Return Home | 3 seconds since the search started |
| Return Home → Patrol | Distance to the original spot is 200 or less |
| Return Home → Chase | Distance drops to 1200 or less on the way back |
Setup
Create a new project from the Third Person template.
- Right-click in the Content Browser →
Blueprints→Enumeration. Name itE_EnemyStateand useAdd Enumeratorto create Patrol / Chase / Search / ReturnHome in that order - Create a Blueprint named
BP_Enemywith Character as the parent class and place one in the level - Place a NavMeshBoundsVolume in the level and scale it to cover the whole floor (without it the enemy will not move a single step. See NavMesh setup)
- In
BP_Enemy's Details panel, set AI Controller Class toAIController
Add these variables to BP_Enemy. Match the default values too. Different values give different results.
| Variable | Type | Default | Role |
|---|---|---|---|
CurrentState | E_EnemyState | Patrol | Current state. Only SetState writes to it |
HomeLocation | Vector | (assigned in BeginPlay) | Center of the patrol |
PatrolTarget | Vector | (assigned in BeginPlay) | The patrol point currently being approached |
bPatrolFar | Bool | true | Which patrol point to head to next (true = the far one) |
LastSeenLocation | Vector | (0,0,0) | Last position the player was seen |
SightRange | Float | 1200.0 | Detection distance |
LoseRange | Float | 1800.0 | Distance at which the player is lost |
SearchSeconds | Float | 3.0 | How long to keep searching |
SearchTimer | Float | 0.0 | Elapsed search time |
ThinkInterval | Float | 0.2 | Thinking interval, in seconds |
Making LoseRange larger than SightRange matters. Set them equal and detection and loss alternate right at the boundary, making the enemy jitter.
Run the Decisions on a Timer, Not Tick
Putting the distance check in Event Tick means calculating it every frame (60 times per second at 60 fps). Enemy AI decisions do not need that frequency. Once every 0.2 seconds is plenty.
Event BeginPlay
→ Set HomeLocation (GetActorLocation)
→ Set PatrolTarget (HomeLocation + (600, 0, 0))
→ SetState (NewState: Patrol)
→ Set Timer by Event
Time: ThinkInterval (0.2)
Looping: ✔
Event: Think (drag off the red pin to create a Custom Event)
That alone cuts the cost to 1/12 (see designing without Tick).
The Finished Think Event

Custom Event: Think
→ Switch on E_EnemyState (Selection: CurrentState)
Patrol → ThinkPatrol
Chase → ThinkChase
Search → ThinkSearch
Return Home → ThinkReturnHome
Distance comes up constantly, so pull it out as a pure function first.
Function: GetDistanceToPlayer (Pure / return value: Distance / Float)
→ Get Player Pawn (Player Index: 0) → Get Actor Location
→ Get Actor Location (self)
→ Vector Length (A - B) → Return Node
Then the four per-state functions.
Function: ThinkPatrol
→ AI Move To (Pawn: self, Destination: PatrolTarget)
→ Branch (Vector Length(PatrolTarget - GetActorLocation) <= 200.0) ← reached the patrol point?
True → Set bPatrolFar (NOT bPatrolFar) ← flip the next destination
→ Set PatrolTarget (Select:
bPatrolFar = true → HomeLocation + (600, 0, 0)
bPatrolFar = false → HomeLocation)
→ Branch (GetDistanceToPlayer <= SightRange)
True → SetState (Chase)
Function: ThinkChase
→ AI Move To (Pawn: self, Destination: the player's current position)
→ Branch (GetDistanceToPlayer > LoseRange)
True → SetState (Search)
Function: ThinkSearch
→ Set SearchTimer (SearchTimer + ThinkInterval)
→ Branch (GetDistanceToPlayer <= SightRange)
True → SetState (Chase)
False → Branch (SearchTimer >= SearchSeconds)
True → SetState (ReturnHome)
Function: ThinkReturnHome
→ Branch (GetDistanceToPlayer <= SightRange)
True → SetState (Chase)
False → Branch (Vector Length(HomeLocation - GetActorLocation) <= 200.0)
True → SetState (Patrol)
One note on ThinkPatrol's round trip. Once the enemy gets within 200 of PatrolTarget, it flips bPatrolFar and uses the Select node to swap the destination between HomeLocation and HomeLocation + (600, 0, 0). So the enemy aims for the opposite side each time it arrives, pacing back and forth between the two points. Select adapts to whatever type you wire in, and feeding a bool into Index makes it pick between two inputs (search for Select). That saves you an extra Branch.
And then EnterState and ExitState. Everything that happens at the instant of a transition collects here.
Function: EnterState (input: State / E_EnemyState)
→ Print String (Append("→ ", state name)) ← transitions become visible
→ Switch on E_EnemyState (Selection: State)
Patrol → Set Max Walk Speed (200) → AI Move To (PatrolTarget)
Chase → Set Max Walk Speed (600)
Search → Set SearchTimer (0.0) → AI Move To (LastSeenLocation)
Return Home → Set Max Walk Speed (200) → AI Move To (HomeLocation)
Function: ExitState (input: State / E_EnemyState)
→ Switch on E_EnemyState (Selection: State)
Chase → Set LastSeenLocation (Get Player Pawn → Get Actor Location)
Set Max Walk Speed is a Character Movement Component node (see configuring the Character Movement Component).
Look at Chase in ExitState. The last-seen position is recorded at the instant chasing ends. Written there, the value is guaranteed to be set by the time Search begins. The bug of "forgot to record it, so the search heads to the origin" cannot happen structurally.
Verifying It
Press Play, approach the enemy, then run away. You have it right when the Print String in the top-left shows this in order.
→ Patrol
→ Chase
→ Search
→ Return Home
→ Patrol
You can also confirm it visually. The enemy's movement speed visibly increases as you approach (200 → 600), it stops and waits three seconds when you get away, then returns to its starting spot and resumes pacing. Those three seconds are ThinkInterval 0.2 seconds × 15. It reads as "pausing a beat before giving up."
If it doesn't work:
- The enemy doesn't move at all → no NavMeshBoundsVolume, or it doesn't cover the floor. Press
Pto show the green visualization - Not even
→ Patrolappears →Loopingis off onSet Timer by Event, orSetStateis never called fromBeginPlay - Chase and Search alternate rapidly →
SightRangeandLoseRangeare the same value. Put at least 600 between them - Search never ends →
SearchTimeris being incremented by a fixed number instead ofThinkInterval, or something callsSet CurrentStatedirectly instead of going throughSetState - Speed never changes →
Set Max Walk Speedis on theThinkside. Speed changes belong at the moment of entering a state, so move them toEnterState - It stops partway home → the 200 threshold for
Return Home → Patrolis smaller than AI Move To's stopping distance. Try a larger value
Two things to take away.
- One state variable, one place that writes it: Just following the rule that only
SetStatemay touchCurrentStateeliminates the entire "forgot to reset the flag" class of bugs. You can verify it by searching the graph forSet CurrentStatenodes and finding exactly one - Anything that happens on entering a state belongs in EnterState: Put speed changes, animation playback, or timer resets on the
Thinkside and they run every cycle, breaking the behavior. Split them by placement: "do this once" at the entry point, "do this repeatedly" inThink
Once the enemy starts picking actions by priority, like hiding, calling for backup, or flanking, an Enum's branching becomes impossible to follow. From there, move on to building enemy AI with Behavior Trees. If you'd rather keep the shape of states and transitions but move to something sturdier, Introduction to StateTree is the shorter path.
Watch Out When You Add Enum Entries
Sooner or later you will want another state. Three things to keep in mind.
Adding an entry automatically adds a pin to Switch nodes. But the new pin is connected to nothing. You have to open every place that uses Switch on E_EnemyState and wire up the new pin. Forget, and nothing happens while in that state.

Removing an entry disconnects whatever was wired to that pin. The nodes stay in the graph but stop executing. Check where it is used before deleting. Right-click the asset → Reference Viewer lists which Blueprints reference it.
If you pass seven states, question the design. The number of transition combinations grows past what you can follow. The usual cause is two things that should be separate getting mixed together. "Movement state (idle/walk/run)" and "combat state (out of combat/guard/attacking)" change independently, so splitting them into two Enums is more natural. The combinations multiply, but only one value can hold within each Enum, so the boolean problem doesn't come back.
Bonus: Good to Know for Later
- Use an Event Dispatcher to announce state changes: For hookups like "show a warning on the UI when the enemy starts chasing," call an Event Dispatcher from
EnterState. The enemy never needs to know about the UI (see loose coupling with Event Dispatchers) - Taking damage is a transition trigger: On a hit,
SetState(Hit); at 0 HP,SetState(Dead). CallingSetStatefrom the damage handler reliably interrupts even a mid-attack state (see implementing damage) - You can build a transition permission table: If you start wanting rules like "Dead goes nowhere," add one permission check at the top of
SetState. AMap<E_EnemyState, ...>is the natural place for the table (see choosing between Array, Set, and Map) - Enums are useful for classification too: Beyond state, they work well for item types and elements. But things that need multiple properties at once, like "fire element and rare," are a poor fit. That is Gameplay Tags territory
- Don't turn everything into state: A chest that only opens or a switch that only presses has two states and little branching, so a
bIsOpenedboolean is enough. Enums pay off when you have three or more exclusive states and multiple transition conditions
Summary
- Managing state by adding booleans breaks down as combinations grow by 2 to the Nth power
- Define your options with a Blueprint Enum (right-click → Blueprints → Enumeration)
- Switch on Enum lists every possible value as a pin, so missing cases are visible
- Funnel state changes through a single SetState, always running
ExitState→ variable update →EnterState - The AnimBP State Machine handles visuals. Logic decides, AnimBP follows
- Once your AI picks actions by priority, consider a Behavior Tree
The deciding question is "can I pin the current state down to one value?" If you can, an Enum pays off.
Try listing the variables starting with bIs in the Blueprint you're working on right now. How many of them must never be true at the same time?