State machines
When an NPC needs to do more than one thing, amateur scripts quickly devolve into a tangle of ten boolean flags: isAngry, hasSeenPlayer, isReturning, isReloading. Soon these flags contradict each other, and the ped stutters between walking, aiming, and running away.
The antidote is a Finite State Machine (FSM). The rule is simple: an entity exists in exactly one state at any given moment.
The four states of an encounter
In Project 05, you will build a gang encounter with four clear states:
[ Calm ] <------------------------+
| |
| (player approaches) | (player retreats)
v |
[ Alert ] |
| |
| (weapon drawn or gets close) |
v |
[ Combat ] -----------------> [ Returning ]
- Calm: Pedestrians stand guard or chat using idle scenarios.
- Alert: Player gets close. Peds turn toward the player with suspicious postures.
- Combat: Guns drawn, firing and taking cover against the player.
- Returning: Player runs away. Peds cease fire, lower weapons, and resume their guard post.
Modeling states in C#
Represent states using an enum:
public enum EncounterState
{
Calm,
Alert,
Combat,
Returning
}
The state transition method
Never change state by directly overwriting a variable across twenty different places in your file. Use a single transition method so entry and exit actions always execute:
private EncounterState _state = EncounterState.Calm;
private void TransitionTo(EncounterState nextState, Ped actor, Ped player)
{
if (_state == nextState)
{
return;
}
// Actions taken on EXITING the old state
switch (_state)
{
case EncounterState.Combat:
actor.Task.ClearAll(); // Cease fire immediately
break;
}
_state = nextState;
// Actions taken on ENTERING the new state
switch (_state)
{
case EncounterState.Calm:
actor.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
break;
case EncounterState.Alert:
actor.Task.LookAt(player, 5000);
GTA.UI.Notification.PostTicker("Gang member is watching you closely.", false);
break;
case EncounterState.Combat:
actor.Task.Combat(player, TaskCombatFlags.None, TaskThreatResponseFlags.None);
break;
case EncounterState.Returning:
actor.Task.ClearAll();
actor.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
_state = EncounterState.Calm; // Return complete
break;
}
}
The power of state machines
Notice the immediate benefits:
- The ped cannot be in
CalmandCombatsimultaneously. Contradictory behaviors become structurally impossible. - When combat ends,
Task.ClearAll()is guaranteed to execute during the exit fromCombat. - Adding a fifth state (like
Fleeingwhen health is low) only requires adding one enum entry and two switch cases.
Next, we look at separating game logic from engine calls, which makes testing your rules outside the game possible.