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

Adapters and services

Hand the graph your facts and take back its intent, through four small interfaces that keep game rules in your own code.

The graph decides to overtake. Your car code does the overtaking. Between them sit two small interfaces: one that hands the graph the facts it needs, one that takes back the intent it produced.

Racing Line’s CPU driver reads a route projection, a slipstream value, a gap to the player, and a pace multiplier, then plans throttle and steering for a car controller that already exists and already works. If the graph reached into the car controller directly, the racing rules would end up spread across a graph and three scripts, and nobody could say which one decided to brake.

CpuBehaviorGraphDriver keeps that boundary honest. It implements IBehaviorGameObjectInputBinder, samples per-tick facts, ships its agent disabled, and calls TickOnce once per race update from the coordinator that owns race timing. The graph plans. The car code drives.

The CpuRace graph open in the editor, with the decision branches the race coordinator ticks
Figure 1. CpuRace.bqbehavior from the Racing Line project, whole. Open it at 100% to read the cards: every leaf on it either reads a fact your code published or asks your code for work.
Boundary Interface Use it for
Facts in IBehaviorGameObjectInputBinder, IBehaviorEntityInputBinder What is true right now, sampled before the tick
Effects out IBehaviorGameObjectCommandHandler, IBehaviorEntityCommandHandler One immediate game operation per authored token
Motion IBehaviorMovementAdapter The backend behind the Move To node
Longer work IBehaviorSteppedService, IExternalServiceAdapter Work that progresses over several steps, or needs the completion ring

Pick the smallest one that fits. Most integrations need the first two and nothing else.

A binder resolves its fields once during composition, then writes values before each tick.

C#
public void Bind(BehaviorGameObjectBlackboardSchema schema)
{
_stomped = schema.Require<int>("stomped");
}
public void Sample(NativeArray<byte> blackboard)
{
_stomped.Write(blackboard, _enemyWasStomped ? 1 : 0);
}

Platformer’s GraphEnemyModeCoordinator does exactly that for the stomped field the enemy’s machine reads. The entity version has the same two phases: resolve BehaviorBlackboardFieldHandle<T> values in Bind, then return a JobHandle from Schedule(EntityQuery agents, JobHandle dependency) so your sampling can be a job of its own.

A binder answers questions. Keeping decisions out of it is what makes the graph the readable part of the system.

A handler claims the authored tokens it owns and handles the commands the graph emits for them.

C#
private static readonly string[] Tokens = { "stop_movement" };
public IReadOnlyList<string> ServiceTokens => Tokens;
public void Handle(in ExternalServiceCommand command)
{
_movement.Stop();
}

The same Platformer coordinator claims four tokens: patrolling, stop_movement, become_harmless, and apply_defeated_appearance. The enemy’s state machine names those tokens, and the coordinator turns each one into the operation the game already had.

One token has one owner per composition. A missing owner, a duplicate owner, or an incompatible one fails composition instead of picking a winner. The entity handler returns an ExternalServiceOutcome after applying its effect to the resolved entity.

Move To is backend neutral. It reaches whichever IBehaviorMovementAdapter you composed, and nothing else.

C#
bool Begin(in BehaviorMovementRequest request);
BehaviorMovementStatus Advance(float deltaTime);
void Pause();
void Resume();
void Stop();

Return Running while another advance is needed, Completed when the authored arrival condition is met, and Failed when a valid request cannot be finished by this backend. BehaviorMovementRequest carries a float3 target, a positive speed, an arrival tolerance, the command id, and the agent id.

Platformer’s Rigidbody2DBehaviorMovementAdapter is the whole pattern in one small MonoBehaviour: it pushes a Rigidbody2D toward the request and reports arrival. Above it, BehaviorMovementService keeps one request identity across pause, publishes exactly one terminal completion after stopping the backend, and refuses a second concurrent request. Queueing belongs in your code if your game wants it.

Nothing in the package adapts NavMesh, A* Pathfinding Project, a perception pack, or a dialogue asset. Those stay yours, reached through the smallest boundary on this page.

IBehaviorSteppedService receives Advance(float deltaTime, int frameNumber) plus Pause, Resume, and Stop, and progresses with the agent’s selected steps. It carries no command draining and no targeted interruption.

IExternalServiceAdapter owns one stable service token and its hashed id, declares whether replay by id is supported, drains its pre-partitioned commands through DrainAndDispatch, and answers OnAuthorityHandoff with a decision. Reach for it when your integration needs the completion ring and the handoff contract. Add IInterruptibleExternalServiceAdapter when the adapter can stop one exactly identified request synchronously.

  1. Write down the facts the graph needs and the operations it should ask for. Facts become blackboard fields; operations become service tokens.

  2. Implement the binder on a component near the object’s existing code, and resolve every field in Bind.

  3. Implement the handler for each token, calling the game operation that already exists.

  4. Compose them on the agent: binders into Input Binder Components, handlers into Command Handler Components, a movement backend into Movement Adapter Component.

  5. Press Play and watch the graph in Live. A command that reaches its handler shows the node completing; a token with no owner fails composition before that.

  • Disabling an agent pauses stepped services and member work, and re-enabling resumes the same request identities.
  • Cancellation and reset publish interruption before stopping services. Replacement releases the old graph’s work first.
  • A late result for an old request or an old runtime generation is stale. Drop it instead of applying it to the new activation.
  • Authority metadata gates which nodes may execute and which writes are accepted, and adapters get a handoff decision for outstanding requests. A networked game still owns transport, replication, and host election.

When it goes wrong

SymptomCheckFix
Composition throws about a service token.Compare the tokens the graph emits with the ServiceTokens each handler claims.Claim every emitted token exactly once across the composed handlers.
A blackboard field is missing at composition time.Read the name your binder passes to Require against the declared variable.Match the authored name, or declare the variable in the graph.
Move To succeeds instantly and nothing moved.Read what your adapter returns from Begin and Advance.Return Running until the arrival condition is met, then Completed.
A cancelled action keeps moving the character.Confirm Stop is implemented and actually stops motion.Release the request in Stop, and keep Pause free of state loss.
An old completion lands on a fresh activation.Compare the request identity and generation your adapter recorded.Treat a mismatch as stale and drop it.
Full-size image