State and Logic
BehaviorTree
Build AI out of small reusable nodes. One compiled tree can drive any number of NPCs, each with its own progress.
Getting it
local BehaviorTree = Engine:GetPackage("BehaviorTree")Overview
A behavior tree describes what an agent (usually an NPC) should do as a tree of nodes. Every tick the tree is evaluated from the root, and each node reports one of three statuses:
| Status | Meaning |
|---|---|
"Success" | The node finished and did what it should. |
"Failure" | The node could not do it. |
"Running" | The node needs more ticks. Next tick picks up where it left off. |
You describe the tree once with node functions, then compile it. A compiled tree keeps progress per agent, so the same tree can run hundreds of NPCs. Progress is stored with weak keys, so an agent that is garbage collected takes its state with it.
local BT = Engine:GetPackage("BehaviorTree")
local guardTree = BT.Compile(BT.Selector({
BT.Sequence({
BT.Condition(function(npc, board)
board.target = findVisiblePlayer(npc)
return board.target ~= nil
end),
BT.Task({
run = function(npc, board, dt)
if not board.target.Parent then
return BT.Failure
end
npc.Humanoid:MoveTo(board.target.HumanoidRootPart.Position)
return BT.Running
end,
}),
}),
BT.Sequence({
BT.Task({ run = moveToNextWaypoint }),
BT.Wait(2),
}),
}))
local boards = {}
RunService.Heartbeat:Connect(function(dt)
for _, npc in guards do
boards[npc] = boards[npc] or {}
guardTree:Tick(npc, boards[npc], dt)
end
end)The blackboard is any table you pass in on every tick. Nodes use it to share data, such as the current target.
Tree
Compile
BehaviorTree.Compile(def: NodeDef): TreeTurns a node description into a runnable tree.
| Method | Description |
|---|---|
tree:Tick(agent, blackboard, dt): Status | Evaluates the tree once for this agent. |
tree:Reset(agent, blackboard) | Throws away the agent's progress. Running tasks get their onExit with reason "Aborted". |
tree:GetLastStatus(agent): Status? | The status the last Tick returned for this agent. |
BehaviorTree.Success, BehaviorTree.Failure and BehaviorTree.Running hold the status strings.
Composite nodes
Sequence
BehaviorTree.Sequence(nodes: { NodeDef }): NodeDefRuns children in order until one fails. Succeeds when all succeed. Think "do A, then B, then C".
Selector
BehaviorTree.Selector(nodes: { NodeDef }): NodeDefRuns children in order until one succeeds. Fails when all fail. Think "try A, else B, else C".
If a higher-priority child starts succeeding or running while a later child was still running, the later child is reset, so selectors react to new situations immediately.
RandomSequence / RandomSelector
BehaviorTree.RandomSequence(nodes: { NodeDef }): NodeDef
BehaviorTree.RandomSelector(nodes: { NodeDef }): NodeDefLike Sequence and Selector, but in a fresh random order every time they start over.
Parallel
BehaviorTree.Parallel(nodes: { NodeDef }, policy: ParallelPolicy?): NodeDefTicks every child each tick. Succeeds once SuccessThreshold children succeeded (default: all) and fails once FailureThreshold children failed (default: 1). Children that are still running when it finishes are reset.
Decorators
| Node | Behaviour |
|---|---|
Invert(node) | Swaps Success and Failure. |
Succeeder(node) | Always Success once the child finishes. |
Failer(node) | Always Failure once the child finishes. |
Repeat(node, n?, breakOnFailure?) | Runs the child n times, or forever without n. Stops with Failure when the child fails, unless breakOnFailure is false. |
RepeatUntilFailure(node, maxAttempts?) | Repeats until the child fails (then fails). Succeeds after maxAttempts successes. |
RepeatUntilSuccess(node, maxAttempts?) | Repeats until the child succeeds. Fails after maxAttempts failures. |
Cooldown(node, t) | After the child finishes, returns Failure for t seconds without running it. |
Timeout(node, t) | Fails and resets the child if it is still running after t seconds. |
Delay(node, t) | Returns Running for t seconds, then runs the child. |
Leaf nodes
Condition
BehaviorTree.Condition(fn: (agent, blackboard) -> boolean): NodeDefSuccess when fn returns true, Failure otherwise.
Task
BehaviorTree.Task(def: TaskDef): NodeDefDoes the actual work.
| Field | Description |
|---|---|
run: (agent, blackboard, dt) -> Status | Called every tick while the task runs. Return a status. |
onEnter: (agent, blackboard) -> ()? | Called on the first tick of a new run. |
onExit: (agent, blackboard, reason) -> ()? | Called when the task finishes ("Success" or "Failure") or is interrupted ("Aborted"). |
Wait / Succeed / Fail
BehaviorTree.Wait(t: number): NodeDef -- Running for t seconds, then Success
BehaviorTree.Succeed(): NodeDef -- always Success
BehaviorTree.Fail(): NodeDef -- always FailureTypes
export type Status = "Success" | "Failure" | "Running"
export type ExitReason = Status | "Aborted"
export type Blackboard = { [any]: any }
export type NodeDef
export type TaskDef
export type ParallelPolicy = { SuccessThreshold: number?, FailureThreshold: number? }
export type Tree