In working on my own IF projects, I am making use of behaviour trees (I think this is a good article that describes them conceptually and in-depth). In a nutshell, they are a way of combining queries (that ask questions about the game state) and actions (that alter the game state) into a structure that has a clever trick. They are programming without a lot of programming.
TLDR? Okay, a few key concepts:
We are going to talk about “tasks”, which are either composites (that structure other tasks), queries (that succeed or fail based on the game state), or actions (that alter the game state). And tasks “succeed” or “fail”. Let’s introduce a couple of composite tasks:
SELECT A, B, C, D
SELECT is an example of a task and is a composite task because it has “child” tasks that it operates on. In this case, it executes the tasks
D. If any of them succeeds, it stops there, and
SELECT is considered to have succeeded. If
D all fail, then
SELECT fails also.
SEQUENCE A, B, C, D
This executes tasks A, B, C, and D in turn. If any of them fails, it stops, and
SEQUENCE fails. If they all succeed,
SELECT picks the first task that succeeds, and
SEQUENCE ensures that all tasks succeeded. We can use just these two concepts to build quite sophisticated behaviours.
Here are a couple of simple
SEQUENCE BOB_IS_THIRSTY BOB_HAS_WATER BOB_DRINKS
- If Bob isn’t thirsty, this sequence will fail.
- If Bob is thirsty but has no water, the sequence will fail.
- If Bob is thirsty & has water, he takes a drink.
A likely consequence of
BOB_DRINKS is that he’s no longer thirsty and has less or no water.
SEQUENCE BOB_SEES_WATER BOBS_FLASK_ISNT_FULL BOB_FILLS_FLASK
- If Bob doesn’t see water, this sequence will fail
- If Bob’s flask is full, this sequence will fail
- If Bob bob sees water & his flask isn’t full, he’ll fill the flask.
Here’s a slightly more complex example arising out of a game I am building that uses
SELECT to combine
SEQUENCES and build an NPC behaviour:
SELECT SEQUENCE BLACK_RONIN_IS_WOUNDED SELECT SEQUENCE BLACK_RONIN_IS_IN_MONASTERY BLACK_RONIN_HEALS BLACK_RONIN_MOVES_TOWARDS_NEAREST_MONASTERY SEQUENCE BLACK_RONIN_SEES_PLAYER_TRAIL BLACK_RONIN_MOVES_IN_PLAYER_DIRECTION SEQUENCE BLACK_RONIN_SEES_PLAYER BLACK_RONIN_AMBUSHES BLACK_RONIN_MOVES_IN_RANDOM_DIRECTION
In my game, the Black Ronin hunts the player unless he’s wounded, in which case he heads for a monastery and, when he reaches one, heals. If he catches up with the player, he will attempt to ambush them; otherwise, he heads in a random direction hoping to pick up the player’s trail.
Something interesting about this is that the person building these behaviours doesn’t have to be the person implementing the queries (like
BLACK_RONIN_IS_WOUNDED) and actions (
BLACK_RONIN_HEALS). As long as they can master the concepts of
SEQUENCE, they can use them to build relatively complex behaviours.
It’s programming, but with a built-in structure and a relatively limited set of concepts you need to understand/use (there are more than
In practice, instead of
BLACK_RONIN_IS_WOUNDED, I have a query task
ACTOR_TEST actor=black_ronin attribute=health test=LT value=75. This is a little more verbose but allows me to apply tests to different attributes of different actors with one type of query. But if you wanted, you could build per-actor queries that directly modelled what you wanted to know.
Does anyone else find this approach interesting for creating NPCs with their own agency & motivation?*
- In fact, I use them beyond this in terms of how things like items & even locations can respond to/modify the game state.