Behavior trees¶
Behavior trees are a common tool for driving NPC decision-making. They work particularly well in a discrete context like turn-based games, so we’ve included a set of building blocks for developing behavior trees. Each time you run a tree it’s evaluated from top to bottom, ultimately producing a single action.
Behavior tree nodes¶
BehaviorTree.Node¶
This is the base class that all other behavior nodes inherit from. Custom behaviors are created by extending this class.
--- @class WaitBehavior : BehaviorTree.Node
local WaitBehavior = prism.BehaviorTree.Node:extend("WaitBehavior")
function WaitBehavior:run(level, actor, controller)
return prism.actions.Wait(actor)
end
return WaitBehavior
BehaviorTree.Root¶
The entry point of the tree. It evaluates its children in order and returns the first action it encounters.
BehaviorTree.Sequence¶
Executes each child in order. If every child succeeds, the sequence succeeds. If any child fails,
the sequence fails. If a child returns an Action, execution pauses and that action is returned
immediately.
prism.BehaviorTree.Sequence({
prism.behaviors.FindTargetBehavior,
prism.behaviors.AttackBehavior,
})
BehaviorTree.Selector¶
Evaluates children in order, returning the first successful result. If a child produces an
Action, that action is returned right away. Think of it as “try this, otherwise that.”
prism.BehaviorTree.Selector({
prism.behaviors.EatBehavior,
prism.behaviors.HuntBehavior,
prism.behaviors.WaitBehavior,
})
BehaviorTree.Succeeder¶
Always reports success, no matter what its child returns. Handy when you want to ignore failure and keep things moving.
BehaviorTree.Conditional¶
Evaluates a condition function. Returns true if the condition passes, otherwise false.
prism.BehaviorTree.Conditional(function(level, actor, controller)
return actor:get(prism.components.Health).current > 50
end)
Registering behavior trees¶
There isn’t a registry for behavior trees by default, but you can create one with
prism.registerRegistry(). You can use separate modules to manage dependencies between
nodes.
Using behavior trees in an entity¶
One useful pattern is to embed a behavior tree inside a custom controller. This lets you express complex logic cleanly while still returning a single action each turn.
--- @class BTController : Controller
--- @overload fun() : BTController
local BTController = prism.components.Controller:extend("BTController")
function BTController:__new(tree)
self.tree = tree
end
function BTController:act(level, actor)
self.blackboard = {}
return self.tree:run(level, actor, self)
end
return BTController
To use it, you would do something like this:
prism.registerActor("Beetle", function()
return prism.Actor.fromComponents({
prism.components.BTController(
prism.BehaviorTree.Root {
prism.behaviors.RandomMoveBehavior,
prism.behaviors.WaitBehavior,
}
),
})
end)
Note
You could alternatively register the root node to save memory.
Controller Blackboard Pattern¶
The controller includes a shared blackboard, a simple table used to store state between nodes.
This allows different parts of the tree to communicate and build on each other’s results.
Caution
The blackboard is not automatically cleared or created by default, so you’ll need to manage it intentionally.
--- @class FindEnemyBehavior : BehaviorTree.Node
local FindEnemyBehavior = prism.BehaviorTree.Node:extend("FindEnemyBehavior")
function FindEnemy:run(level, actor, controller)
local senses = actor:get(prism.components.Senses)
if not senses then
return false
end
local target = senses:query(level, prism.components.Controller):first()
if not target then
return false
end
-- Persist the target for subsequent nodes
controller.blackboard["target"] = target
return true
end
return FindEnemyBehavior