Skip to content
Bit Quirky Behavior Trees and
State Machines

Search documentation

All topics and APIs

Close returns focus to Search.

What are you looking for?

Try a common topic or enter a complete API identifier.

Browse the reference instead

Esc closes · Tab reaches results · Enter opens the focused link

v0.6.0
Menu

Dungeon Heist

Throw a coin and watch one guard drop its patrol mid-step, commit to the noise, walk over, and look around. The interruption sample, with the heist rules left in C#.

A dark Kenney dungeon, three relics on pedestals, five guards on routes, and a pocket full of coins. One of those guards, Guard_2, decides everything it does from a graph file you can open. Throw a coin behind it while it is walking and you get the moment this whole sample exists for: it abandons the walk on the same update the noise lands.

Guard_2 is waiting out a patrol waypoint when the coin noise lands, and the suspicion and investigation branch takes the tick: the dwell holds first, and the look around at the landing spot follows it.
Guard_2 is waiting out a patrol waypoint when the coin noise lands, and the suspicion and investigation branch takes the tick: the dwell holds first, and the look around at the landing spot follows it.

Play it in one minute

Under a minute

Scene
Assets/_Project/Scenes/DungeonHeist.unity
Select
Guards > Guard_2 in the Hierarchy
Controls
Keyboard and mouse: WASD or the arrow keys to move, hold Left Ctrl to sneak, hold Left Shift to sprint, Q to throw a coin, click in the Game view to capture the mouse for camera look and Esc to release it. Gamepad: left stick to move, right stick to look, east button to sneak, south button to sprint, north button or right shoulder to throw a coin. Relics are taken by walking into them; there is no take key.
Goal
Steal all three relics without being caught. Walk within about a metre and a half of a relic and it is yours.

Select Guard_2 in the Hierarchy before you press Play and the editor attaches to it as the scene starts. It walks Route_NorthEast, a four point loop, standing still for four seconds at each point. The HUD reads relics 0 of 3 and alarm calm.

Now spend a coin. Q throws one on a low arc up to eight metres ahead of you, and it makes its noise where it lands, not where you stood. Any guard within the landing radius of seven metres hears it, and if that guard is Guard_2 you can watch the decision happen in the editor while it happens in the game.

Getting caught needs a guard in an active chase that closes to about a metre. The heist then resets around you: you reappear at the entrance, the relics you already stole stay stolen, every guard goes back to patrol on its own route, the alarm drops to calm, and the gates open again. Take the third relic and you win on the spot.

The Dungeon Heist courtyard in Play Mode with guards, barrel stacks, arches, a watcher statue, and relic props.
Figure 1. The courtyard, reframed for this still so the props and the guards are both readable. Only Guard_2 runs the graph file below.
The same interruption from the floor of the vault. Guard_2 is walking its route away from the thief when a coin lands behind it; it stops, turns, walks to the noise and sweeps the spot, and the nearer guard comes to look as well. No relic is taken, and the alarm only ever rises to wary.
The same interruption from the floor of the vault. Guard_2 is walking its route away from the thief when a coin lands behind it; it stops, turns, walks to the noise and sweeps the spot, and the nearer guard comes to look as well. No relic is taken, and the alarm only ever rises to wary.

Paths here follow the standalone project layout. Importing the packaged sample puts the same folders under Assets/BitQuirky/BehaviorTreesAndStateMachines/Samples/.

  • Priority through one Selector with eight branches, walked top to bottom on every pass, so “stop being caught” beats “chase” beats “go look at that noise” beats “carry on patrolling”.
  • Lower Priority interruption on the conditions that watch for a threat, which is what lets a coin or a clear sighting take the agent away from a walk that is already underway. The two Self Conditionals do the opposite job: they drop their own branch once its reason stops holding.
  • Signal capture with an out parameter. A member condition calls TryGetPendingSignal(out Stimulus signal) and captures the out value into a graph variable, so the guard commits to one coin and finishes the trip instead of chasing whatever is newest.
  • Coroutine actions: six of this guard’s leaves are IEnumerator methods that hold Running while they walk, dwell, or look, and that get cancelled cleanly when a higher branch wins.
  • A two-way member binding you can edit while the game runs. patrolSpeed reads and writes a FloatVariable, and this guard owns a private clone of it.
  • A focus target held as a managed reference, so one movement node steers at the player during a chase and at the landed coin during an investigation, without knowing which is which.

What the graph deliberately leaves in C#: vision cones and line of sight, the hearing rule, stimulus ranking, suspicion state, the search timer, NavMesh movement, the route data, alarm escalation, the gate lockdown, the watcher statues, relic pickup, the win condition, the catch reset arithmetic, the player controller, the coin’s flight. Note what that list implies about the graph file: it holds no distances and no durations. Every number the guard uses belongs to the game.

The graph decides Your game code does
Guard2.bqbehavior on Guard_2, composed and enabled by GuardBehaviorActor Guards is the authority for suspicion state, stimulus ranking, the search clock, and whether a route return is owed. The graph asks it; it answers.
Which of the eight branches owns this update, and when to abandon the one that was running GuardVisionSensor with LineOfSightRaycaster decides sight from an 8 metre cone at 55 degrees either side, and Hearing decides audibility from the noise radius times this guard’s hearing multiplier.
When to walk, when to hold still and face something, when to sweep the area GuardBehaviorMovementService and NavMeshGuardMotor move the body. GuardBehaviorWaitService owns the 0.8 second dwell, the 2 second look, and the waypoint pause.
Which noise to commit to, stored in the pending_signal variable GuardBehaviorSignalBinder latches every noise this guard can hear and hands over the newest one. StimulusArbiter decides whether it outranks what the guard is already reacting to.
Nothing at all Guard_1, Guard_3, Guard_4, and Guard_5 are Guard.prefab instances running the hand-written GuardAgent, kept as the comparison. The alarm director, bell, gates, statues, relics, HUD, entrance, player, and coin thrower are all ordinary components.

Guard2.bqbehavior is the scene’s only graph file, and Guard_2 carries the scene’s only Behavior Agent. That agent is saved switched off: the actor composes it, hands it three reference slots (guards, gameMaster, this guard’s own patrol-speed clone), initializes it, then enables the tick. Selecting the object in Play Mode is therefore unambiguous, because there is exactly one agent to attach to.

MemberConformance.bqbehavior sits in the same folder and the scene never touches it. It is a five node Sequence that exercises one of each member form once: a method whose returned bool lands in the blackboard, a message with no result, a member condition comparing an enum property against Lockdown, a member event, and a variable read that carries its own change notification. It runs on MemberConformance.prefab under an EditMode proof, so read it as a worked example of the binding forms and expect no gameplay from it.

Here is the whole coin journey, from your keypress to a guard standing over a coin looking around.

  1. Open Assets/_Project/Behavior/Guard2.bqbehavior in Window > Behavior & State > Behavior Tree Editor and leave the toolbar on Static. The root is a Repeat on Forever over the Selector called priority selector, and its Children list is the guard’s whole policy in order: shared catch reset, catch, chase, capture stimulus, the grouped suspicion branch, search, return to route, patrol.

  2. Select newest eligible signal pending in the capture stimulus branch. The rail shows a method binding on the guard’s own signal binder, TryGetPendingSignal, with its out parameter captured as a separated output into the pending_signal graph variable, and a comparison that turns the returned bool into the node’s result.

The rail for the newest eligible signal pending node: Member holding the TryGetPendingSignal binding, Outputs holding the out parameter captured into pending_signal, and the Comparison card’s header below them.
Figure 2. One call doing three jobs: ask whether a noise is waiting, keep the whole Stimulus struct, report Success so the branch can continue.
  1. Read the two nodes after it. store one stimulus payload atomically hands guard_id and the captured pending_signal to Guards.TryCaptureStimulus, which is where ranking happens: a coin cannot displace a confirmed sighting of you. consume the captured signal then clears the latch so the same noise is never captured twice.

  2. Press Play, select Guard_2, and switch the toolbar to Live. While it patrols, the running leaf is move to the current waypoint or wait out the waypoint pause or its remainder, and the branches above sit idle.

  3. Throw a coin within seven metres of the guard. On the update the noise lands, newest eligible signal pending becomes true, the Lower Priority interruption takes the patrol coroutine away, and the suspicion branch takes the tick.

  4. Watch the investigation run in order: enter suspicious, then hold and face the active stimulus for 0.8 seconds, then a validity check, enter investigate, move to the stimulus, look around for 2 seconds, and clear the completed investigation atomically. Its sibling branch, expire stale investigation, is the one that wins instead when the noise goes stale during the dwell.

A Live behavior tree for Guard_2 showing the investigation priority selector over the suspicion and investigation sequence, whose dwell node carries the running pill, with the validity check, enter investigate and move to the stimulus beside it.
Figure 3. Live on Guard_2 while the heist plays. The canvas colors say which branch owns the update: the suspicion and investigation sequence holds the tick, and the dwell at its head is the node actually running.

The last link in the chain is the focus target. The actor projects whatever object the Guards asset’s decision named into the liveFocus variable: you during a chase, the landed coin during an investigation. One member node reads Position off whatever is in there and hands it to the movement service, so pursue the live focus position steers at the right thing without a branch per target type.

The surfaced patrolSpeed member row in Live holding the value 6 typed during play.
Figure 4. The surfaced patrolSpeed member row in Live holding the value 6 typed during play.
patrolSpeed is a two-way binding, so its Current row accepts a value while the game runs and the guard picks up the new patrol speed on the next tick.
patrolSpeed is a two-way binding, so its Current row accepts a value while the game runs and the guard picks up the new patrol speed on the next tick.

Distances and durations are game data, which is why none of them is a graph edit. Hearing lives on Guard_2 as the Hearing Multiplier in the Perception block of its Guard Behavior Input Binder, multiplied against the coin’s Landing Noise Radius on GameLogic/Player/Coins.asset and scaled again by the active alarm profile. The dwell, the look, and the stimulus expiry sit together in the Timing block of GameLogic/Guard/Guards.asset. Raise Investigate Look Around Seconds from 2 to 6 and the look around node holds Running for six seconds in Live without a single change to the graph file.

Copy as-is: the shape. One repeating priority Selector; safety branches plus recovery branches above ambient work; a capture branch that stores a changing fact once; thin coroutine leaves that each do one atomic thing. GuardBehaviorActor is worth reading before you write your own host: it composes the agent, injects every collaborator explicitly, fills the reference slots, initializes the agent, then enables it. Nothing in it decides behavior.

Rebind: the member targets and the reference slots. Your authority is not called Guards, so point the capture node at your own Try method, point the state calls at your own methods, and give the graph your own struct as the captured payload. The interruption opt-in travels with the shape: put the branch that should win above the branch that should lose, and set Abort to Lower priority on the Selector that owns both. That one setting covers every condition inside that Selector’s branches. This guard’s file predates the control and still marks each watching condition individually, which the runtime honors either way, so read the per-condition rows as history and not as a pattern to copy.

Needs new C#: a motor that can be told to stop. Every leaf here is interruptible because the movement and wait services handle cancellation, so a preempted walk really stops. If your character moves by tween, by grid step, or by animation event, write that cancellation before you wire the graph, or your guard will keep gliding to where the coin used to be. A second graph-driven guard also needs its own route, its own speed clone, and the same three slots filled; sharing one clone between two guards hands them one speed.

When it goes wrong

SymptomCheckFix
A different guard investigates your coin.Which object is selected, and where did the coin land? Four of the five guards run hand-written C#.Keep Guard_2 selected and land the coin inside its hearing range. Editing the graph file changes that one guard.
The editor shows Static when you expected Live.Guard_2 saves its Behavior Agent disabled and the actor enables it during composition.Attach in Play Mode after the scene has initialized. Before Play there is no running instance to attach to.
You edited a graph file and the scene ignored it.Confirm you opened Guard2.bqbehavior and not MemberConformance.bqbehavior.MemberConformance is a binding fixture with an EditMode proof behind it. The scene points at Guard2 only.
The capture condition never succeeds.Read whether a noise actually reached this guard, then whether Guards rejected it as lower ranked than the current stimulus.Land the coin closer, or break the sighting that outranks it. Sneaking emits no noise at all, so a sneaking player is silent by design.
A member node reports its target as unresolved.Look for the generated member provider component beside the agent on Guard_2, then for the three filled reference slots (guards, gameMaster, patrolVariable).Reimport the graph file so the provider regenerates, and confirm the actor filled all three slots. A provider baked for another graph binds nothing.
liveFocus is empty.Which branch is running? Patrol has no focus target, and neither does search or return.Read an empty focus as normal outside chase and investigate. It matters only while a focus-consuming node should be running.
A coroutine leaf stays Running after a higher branch wins.Inspect the running instance in Live rather than the saved graph, and read whether the service handles its cancellation.Cancel the underlying work in your own service. The graph drops the branch; only your code can stop the motor.
Pressing E does nothing at a relic.The Interact action exists in the input actions asset and no game code listens for it.Walk into the relic instead. Pickup is proximity based at a metre and a half, and the glow brightens as you close in.
  • Interrupt: the exact rules behind the takeover you just watched, including what gets reset.
  • Guard and investigate: this capture-then-act pattern on its own, ready to lift.
  • Horde Survival: the same ideas at two thousand agents, on Entities.
  • Waiting and cancellation: how a coroutine leaf holds Running and what cancelling one means for your code.
  • Live editing: which values a running agent will let you change, and which it will not.

Every model and texture in the vault is by Kenney, who gives away thousands of game assets for free:

  • Modular Dungeon Kit: the rooms and corridors, the gates, and the shared colormap.
  • Mini Dungeon: the chests you loot, the coin you throw, plus the barrels and columns you hide behind.
  • Graveyard Kit: the keeper figure that stands in as the watcher statue.
  • Animated Characters: the thief and the guards, the characterMedium rig with its fantasy skins. That rig ships as the Animated Characters Bundle inside All-in-1; the series page lists the free packs it was split into.

We took ours from Kenney Game Assets All-in-1, the paid bundle of everything Kenney has released; the links above go to the same packs on kenney.nl, where each one is a free download.

All four are CC0, which asks nothing of you and allows commercial use; we always credit Kenney when we use their assets, and support them by buying the packs. We hope you will too! Each kit sits under Assets/ThirdParty/Kenney/ with its own License.txt. Keep those files beside the art if you move it into your own project. The asset’s code and graph files carry their own license, so check both before shipping a derivative.

Full-size image