Anubis: Modifying Simple Behaviours with Interruptions and Selectors
Overview
In this tutorial, we will expand on the basic guard behaviour created in the Getting Started tutorial by introducing the concepts of interruptions and node selectors. This is where Anubis scripting starts to become more powerful and flexible, allowing you to control when behaviours get interrupted and how the game decides which actions to take. We will modify our script to make the guard either Flee or Attack the player when they see the player approaching them.
By the end of this tutorial, you will:
- Implement interrupts that dynamically change behaviour mid-execution
- Understand the selection process between different nodes in a behaviour tree
Part One: What Are Interruptions and Node Selection?
Interruptions
Interrupts allow a character’s current behaviour to stop and switch to a higher-priority action. For example, if a character is wandering but a player suddenly enters the area, the wander action can be interrupted, and the greet action can take over.
Node Selectors
Node selection is how Anubis decides which action to execute in a behaviour tree. We use Selectors to define this process. Anubis evaluates each child node and chooses the first one that is valid based on its conditions.
Part Two: Expanding the Behaviour Tree
Let’s modify the guard that we created in the Getting Started tutorial.
Add a New Action: Cower
In this expanded version of the behaviour tree, we will add a new action called Cower that makes the guard cower in fear if they see a player character (because our guard is a coward, that’s why!). This action will interrupt the wandering or guarding actions.
Open Guard.ann and update it to this version:
game.states.Guard = State {
function ()
-- local variable that tells us whether the guard is tired or not
local tired = false
-- local variable for a trigger upon entering which the player will trigger Cower reaction from our guard
-- you will need to replace S_EventTrigger_0687d319-0436-4091-8389-15f28536a8e8 with your trigger
-- P.S. An event trigger is used here for tutorial purposes only. In real scripting you should use
-- Box Triggers
local playerApproachTrigger = Entity("S_EventTrigger_0687d319-0436-4091-8389-15f28536a8e8")
-- local variable that tells us whether there is a player nearby (we store the player in this variable)
local nearbyPlayer = nil
-- Cower action
nodes.Cower = Action {
function ()
DebugText(me, "I'm cowering!")
Sleep(6.0)
end,
Valid = function()
return nearbyPlayer ~= nil
end
}
-- Wander action
nodes.Wander = Action {
function ()
DebugText(me, "Not tired, wandering around")
Sleep(6.0)
tired = true -- after walking around needs to rest
end,
CanEnter = function ()
return not tired -- Wander only if not tired
end,
Valid = function()
return nearbyPlayer == null
end
}
-- Stand and guard action
nodes.Stand = Action {
function ()
DebugText(me, "Tired, standing still and guarding something")
Sleep(6.0)
tired = false
end,
CanEnter = function ()
return tired -- Stand still only if tired
end,
Valid = function()
return nearbyPlayer == null
end
}
events.EnteredTrigger = function(e)
if e.Trigger == playerApproachTrigger and e.Target.Character.IsPlayer then
nearbyPlayer = e.Target
end
end
events.LeftTrigger = function(e)
if e.Trigger == playerApproachTrigger and e.Target == nearbyPlayer then
nearbyPlayer = nil
end
end
end
}
What Changed?
Variable playerApproachTrigger: This is an Event Trigger that we use to determine if the player is near the guard. Normally you would use a Box Trigger, which only sends the events EnteredTrigger and LeftTrigger if it is first registered in Osiris. For simplicity's sake in this tutorial, we use an Event Trigger, which sends events for all characters without any registration.
Variable playerIsNearby: We’ve added a new local variable that is set to true when a player character approaches the guard.
Cower Action: This action triggers if a player character approaches the guard. The guard can only enter the action and stay in it as long as the variable nearbyPlayer is set (not nil). To simulate the guard fleeing, we display a debug message every 6 seconds.
Valid function for Wander and Stand nodes: We’ve added the Valid function to the existing Wander and Stand nodes. Both of these nodes are only valid as long as the variable playerIsNearby is set to false. As soon as as it is set to true, the Wander/Stand action is interrupted and the Anubis framework re-evaluates all the nodes, trying to find the one it can enter (which, in our case, will be the Cower node).
event EnteredTrigger: This event is triggered when the player enters a trigger. We check that (a) it’s our trigger and (b) the character that entered the trigger is a player. Then we store the entered trigger player in a variable.
event LeftTrigger: This event is triggered when the player leaves our Event Trigger. We check that (a) it’s our trigger and (b) the character that left the trigger is the same player that we stored in the nearbyPlayer variable before. Then we reset the nearbyPlayer variable back to nil, which forces the guard to exit the Cower action and trigger either the Wander or the Guard action.
Testing Interrupts
To test your changes, you will need to:
- Reload the Anubis framework
- Enter Game Mode, approach the guard, and enter the Event Trigger you placed earlier
Part Three: Adding a Selector
Let’s imagine that while our guard is a coward, there is a 50% chance that, instead of cowering, he will attack the player.
So let's add a new Action called Attack and make sure that when the player is nearby, the guard randomly chooses between Cower and Attack. This can be achieved via Selectors – a special mechanism that allows us to choose a node from a group of nodes using some custom rules.
Add Attack Action and Guard Action Selector
game.states.Guard = State {
function ()
-- local variable that tells us whether the guard is tired or not
local tired = false
-- local variable for a trigger upon entering which the player will trigger Cower reaction from our guard
local playerApproachTrigger = Entity("S_EventTrigger_0687d319-0436-4091-8389-15f28536a8e8")
-- local variable that tells us whether there is a player nearby (we store the player in this variable)
local nearbyPlayer = nil
nodes.GuardAction = Selector {
function(nodes)
return FindRandomSelectable(nodes)
end,
Valid = function()
return nearbyPlayer ~= nil
end
}
-- Cower action
nodes.GuardAction.Cower = Action {
function ()
DebugText(me, "I'm cowering!")
Sleep(6.0)
end
}
-- Attack action
nodes.GuardAction.Attack = Action {
function ()
DebugText(me, "I'm attacking!")
Sleep(6.0)
end
}
-- Wander action
nodes.Wander = Action {
function ()
DebugText(me, "Not tired, wandering around")
Sleep(6.0)
tired = true -- after walking around needs to rest
end,
CanEnter = function ()
return not tired -- Wander only if not tired
end,
Valid = function()
return nearbyPlayer == nil
end
}
-- Stand and guard action
nodes.Stand = Action {
function ()
DebugText(me, "Tired, standing still and guarding something")
Sleep(6.0)
tired = false
end,
CanEnter = function ()
return tired -- Stand still only if tired
end,
Valid = function()
return nearbyPlayer == nil
end
}
events.EnteredTrigger = function(e)
if e.Trigger == playerApproachTrigger and e.Target.Character.IsPlayer then
nearbyPlayer = e.Target
end
end
events.LeftTrigger = function(e)
if e.Trigger == playerApproachTrigger and e.Target == nearbyPlayer then
nearbyPlayer = nil
end
end
end
}
What Changed?
GuardActionSelectorSelector:- This is a special type of node that contains several others nodes within it and can choose one of them as the main behaviour of the NPC following some rules.
FindRandomSelectablefunction –return FindRandomSelectable(nodes)– returns a random node of the Selector. To make a node a part of the selector, it should have a name in the format{SelectorName}.{ActionName}, for exampleGuardAction.Cower.Valid function: Selectors can use CanEnter and Valid, just like other nodes.
- Cower action: This is now named
GuardAction.Cower, which allows the Anubis framework to treat it as a node that belongs to the GuardAction Selector. We also removed the Valid function from the Cower action, since we now check the Selector for validity instead. - Attack action: A new action that is implemented similarly to the Cower action.
Testing the Selector
Enter Game Mode and enter/leave the trigger near the guard several times. You will see that “I’m cowering!”/“I’m attacking!” appears above the head of the guard randomly every time you enter the trigger.
Conclusion
In this tutorial, you expanded your understanding of Anubis by learning how to implement interruptions and selection logic in behaviour trees. You now have a character that can dynamically change its actions based on player actions. In Adding Proxy Nodes and Expanding Selection Logic, we’ll dive into more advanced topics like using proxy nodes.
