Creating Behavior Tree Nodes

# Course Introduction

This will teach you about nodes that are used to create behaviour trees. You can follow along with the examples to create an AI monster that moves in accordance with the behaviour tree.

##### Reference Guide

  * [Creating AI with Behavior Trees](/docs?postId=562%7B%22target%22:%22_blank%22%7D)



##### API Reference

  * [BTNode](/apiReference?postId=455%7B%22target%22:%22_blank%22%7D)
  * [AIComponent](/apiReference?postId=361%7B%22target%22:%22_blank%22%7D)
  * [ParallelNode](/apiReference?postId=456%7B%22target%22:%22_blank%22%7D)
  * [RandomSelectorNode](/apiReference?postId=457%7B%22target%22:%22_blank%22%7D)
  * [SelectorNode](/apiReference?postId=458%7B%22target%22:%22_blank%22%7D)
  * [SequenceNode](/apiReference?postId=459%7B%22target%22:%22_blank%22%7D)



# Creating Behavior Trees

#### Creating Composite Nodes

Composite node entities can be created with each node's creator. A composite node's creator is as follows.

[{"Node":"Sequence","Constructor":"SequenceNode(string name = \"SequenceNode\")"},{"Node":"Selector","Constructor":"SelectorNode(string name = \"SelectorNode\")"},{"Node":"RandomSelector","Constructor":"RandomSelectorNode(string name = \"RandomSelectorNode\")"},{"Node":"Parallel","Constructor":"ParallelNode(string name = \"ParallelNode\")"}]
    
    
    @Component
    script BehaviorTreeInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        local seqNode = SequenceNode("sq")
        local selNode = Selector("sel")
        local randomNode = RandomSelector("rand")
        local prNode = Parallel("pr")
        end
    end
    

#### AttachChild()

A composite node can have a child node. You can use `AttachChild()` to create a child node. A Composite Node can have both an Action Node and another Composite Node as a child, and you can use it to create a complex tree structure.

#### AttachChildAt()

A tree structure will run sequentially in the order child nodes are registered. There are two ways to change the execution sequence. The first method is to change the sequence within the code. The second method is to use the `AttachChildAt()` function.

The `AttachChildAt()` function allows you to register a node at a certain position regardless of the order it was registered. You can use the `AttachChildAt()` function in the same way you use the `AttachChild()` function. The only difference is that you have to pass the node's position as the second parameter. The order starts from 0. If you register a new node to a position already occupied by another node, existing nodes will be pushed to the next position. [png](17168956058276319a1ddbdab49bdb2588b236c875a84)
    
    
    @Component
    script ActionSequenceBuilder extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        --CREATE NODE
        local sequence = SequenceNode("sq")
        local actionA = self:CreateNode("ActionA")
        local actionB = self:CreateNode("ActionB")
        local actionC = self:CreateNode("ActionC")
        local actionD = self:CreateNode("ActionD")
    
        --SET TREE
        sequence:AttachChild(actionA)
        sequence:AttachChild(actionB)
        sequence:AttachChild(actionC)
        sequence:AttachChildAt(actionD,1)
        end
    end
    

#### Removing Nodes

When removing a registered child node, you can use `DetachChild()`, `DetachChildAt()`.

  * `DetachChild()` passes the entity of the child node that will be removed as parameter to remove it.
  * `DetachChildAt()` passes the position of the child node that will be removed as parameter to remove it.



[png](17168956211348925ff93ffd347379cb977b79e710f7c)
    
    
    @Component
    script SequenceNodeManager extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        --CREATE NODE
        local sequence = SequenceNode("sq")
        local actionA = self:CreateNode("ActionA")
        local actionB = self:CreateNode("ActionB")
        local actionC = self:CreateNode("ActionC")
    
        --SET TREE
        sequence:AttachChild(actionA)
        sequence:AttachChild(actionB)
        sequence:AttachChild(actionC)
    
        --DETACH NODE
        sequence:DetachChildAt(1)
        sequence:DetachChild(actionC)
        end
    end
    

#### Root Node Setting

Root Node uses `SetRootNode()`. Root node passes the node as parameter to the top-level node. You can set the highest node as Root node to give AI to an entity and operate the BT.
    
    
    @Component
    script ActionSequenceBuilder extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        --CREATE NODE
        local sequence = SequenceNode("sq")
        local actionA = self:CreateNode("ActionA")
        local actionB = self:CreateNode("ActionB")
        local actionC = self:CreateNode("ActionC")
    
        --SET TREE
        sequence:AttachChild(actionA)
        sequence:AttachChild(actionB)
        sequence:AttachChild(actionC)
    
        --SET ROOT NODE
        self:SetRootNode(sequence)
        end
    end
    

#### Creating Decorator Nodes

To create a Decorator node, you can use BTNodeType. Create a new BTNodeType, then add Child or child of any type as property to create a Decorator Node. If you'd like to execute a child node, call the `Behave()` function.
    
    
    @Component
    script StatusRotator extends Component
        property any Child = nil
    
        method any OnBehave(number delta)
        if self.Child == nil then
        	return BehaviourTreeStatus.Failure
        end
    
        local result = self.Child:Behave(delta)
    
        if result == BehaviourTreeStatus.Success then
        	return BehaviourTreeStatus.Failure
    
        elseif result == BehaviourTreeStatus.Failure then
        	return BehaviourTreeStatus.Running
        else
        	return BehaviourTreeStatus.Success
        end
        end
    end
    

You can create an entity node and assign a child node to the created Decorator BTNodeType, like shown below.
    
    
    @Component
    script AiBehaviorTreeBuilder extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        local aiComponent = self.Entity.AIComponent
    
        local childNode = aiComponent:CreateLeafNode("childNode", function(delta) return BehaviourTreeStatus.Success end)
        local decorator = self:CreateNode("Decorator")
        decorator.Child = childNode
        end
    end
    

#### Behavior Tree Completion

After configuring the Root Node setting, the AIComponent will operate according to the created behaviour tree. [png](1645011460737db32f055d8984de6a762c4129e12207c)
    
    
    @Component
    script BehaviorTreeBuilder extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        --CREATE COMPOSITE NODE
        local sequence = SequenceNode("Sequence")
        local sequenceA = SequenceNode("SequenceA")
        local sequenceB = SequenceNode("SequenceB")
        local sequenceC = SequenceNode("SequenceC")
        local selectorA = SelectorNode("SelectorA")
        local randomSelectorA = RandomSelectorNode("RandomSelectorA")
        local parallelA = ParallelNode("ParallelA")
        local parallelB = ParallelNode("ParallelB")
    
        --CREATE ACTION NODE
        local actionA1 = self:CreateNode("ActionA", "ActionA1")
        local actionA2 = self:CreateNode("ActionA", "ActionA2")
        local actionB1 = self:CreateNode("ActionB", "ActionB1")
        local actionB2 = self:CreateNode("ActionB", "ActionB2")
        local actionC1 = self:CreateNode("ActionC", "ActionC1")
        local actionC2 = self:CreateNode("ActionC", "ActionC2")
        local actionD1 = self:CreateNode("ActionD", "ActionD1")
        local actionD2 = self:CreateNode("ActionD", "ActionD2")
        local actionE = self:CreateNode("ActionE", "ActionE")
        local actionF = self:CreateNode("ActionF", "ActionF")
        local actionG = self:CreateNode("ActionG", "ActionG")
    
        --SET TREE
        sequence:AttachChild(sequenceA)
        sequence:AttachChild(parallelA)
        sequenceA:AttachChild(randomSelectorA)
        sequenceA:AttachChild(actionA1)
        parallelA:AttachChild(sequenceB)
        parallelA:AttachChild(actionB1)
        parallelA:AttachChild(selectorA)
        randomSelectorA:AttachChild(sequenceC)
        randomSelectorA:AttachChild(actionA2)
        sequenceB:AttachChild(actionB2)
        sequenceB:AttachChild(actionC1)
        selectorA:AttachChild(parallelB)
        selectorA:AttachChild(actionD1)
        sequenceC:AttachChild(actionC2)
        sequenceC:AttachChild(actionD2)
        parallelB:AttachChild(actionE)
        parallelB:AttachChild(actionF)
        parallelB:AttachChild(actionG)
        end
    end
    

# Behaviour Tree Utilization Example

Let's use AIComponent and BTNodeType to create an AI monster than can detect players and wander around within a certain range. There are three major elements in this example.

  * When the player is far away, the monster will wander around, moving in random directions.
  * When the player approaches, the monster will start chasing the player.
  * When the player moves far away, the monster will stop giving chase and start wandering again.



[png](17168958937950674e59a9a8744ca81444187e01c8e85)

## Preparation

  1. Switch to RectTile mode and place tiles. [png](1717483867395248704741b464dfa9bbf21b93f26f45e)

  2. Select Workspace - MyDesk - Create Material and add a new Alert material.

     * Shader: Rainbow
     * Blend: 0.5
     * Rotate: 0
     * Spread: 0.25
     * TimeOffset: 0
     * TimeScale: 1

[png](1717483885157c4309e9ef2e34691a871092dd75b1906)

  3. Select Preset List - Monster - monster2358 and add a preset. Leave TransformComponent, StateAnimationComponent, and SpriteRendererComponent in place and add the following three components.

     * StateComponent, MovementComponent, KinematicbodyComponent



[png](17174839092271a14772637854b71abaf190445ea359b)

## Create AIPatrolComponent

  1. Select Workspace - BaseEnvironment - NativeScripts - Component - AIComponent and click Extend. Rename it as AIPatrolComponent.

  2. Add AIPatrolComponent to the monster you placed in the map. [png](1717488266930702706135fff47409f16850ee919f377)

  3. Add the `DrawRange()` function. Use PolygonRendererComponent to show the range where the monster can detect a player, like below.
         
         @Component
         script RangeVisualizer extends Component
         property number DetectionRange = 5
         
         @ExecSpace("ServerOnly")
         method void DrawRange()
         ---@type PolygonRendererComponent
         local polygon = self.Entity:AddComponent("PolygonRendererComponent")
         polygon.Color = Color(0, 1, 0, 0.25)
         
         local res = 24
         local radius = self.DetectionRange
         for i = 1, res do
             local rad = 2 * math.pi * (i / tonumber(res))
             local x = math.cos(rad) * radius
             local y = math.sin(rad) * radius
             polygon.Points:Add(Vector2(x, y))
         end
         end
         end
         



    
    
        
    4. Create a `Flip()` function. Write as follows to flip the monster's sprite horizontally, in accordance with its movement direction.
    
        ```lua
    @Component
    script MovementSpriteFlipper extends Component
        @ExecSpace("ServerOnly")
        method void Flip()
        local kb = self.Entity.KinematicbodyComponent
        local sprite = self.Entity.SpriteRendererComponent
    
        if kb.MoveVelocity:Magnitude() > 0 then
            if kb.MoveVelocity.x < 0 then
                sprite.FlipX = false
            else
                sprite.FlipX = true
            end
        end
        end
    end
    

  5. Create a `DetectPlayer()` function and write the following, so it can sense the distance between itself and the player. If the distance from the player is closer than the range, player entity will be recorded in the memory; if the distance is further than the range, player entity will be removed from memory. Detected players will be used in ActionFollow, DecoHasTarget, DecoHasNoTarget.
         
         @Component
         script PlayerProximityDetector extends Component
         @ExecSpace("ServerOnly")
         method void DetectPlayer()
         local target = nil
         local minDist = self.DetectionRange
         local myPos = self.Entity.TransformComponent.WorldPosition:ToVector2()
         
         local users = _UserService.UserEntities
         for userId, user in pairs(users) do
             local userPos = user.TransformComponent.WorldPosition:ToVector2()
             local dist = userPos:Distance(myPos)
             if dist < minDist then
                 target = user
                 minDist = dist
                 break
             end
         end
         
         if target == nil then
             self:SetMemory("PlayerInRange", nil)
         else
             self:SetMemory("PlayerInRange", target)
         end
         end
         end
         



    
    
    6. Add `OnUpdate`, and then write as follows to call `DetectPlayer()`, `Flip()` functions.
    
        ```lua
    @Component
    script PlayerDetector extends Component
        method void OnUpdate(number delta)
        self:DetectPlayer()
        self:Flip()
        end
    end
    

  7. Add 'OnBeginPlay', and then write the following to call up the `DrawRange()` function.
         
         @Component
         script RangeDrawer extends Component
         method void OnBeginPlay()
         self:DrawRange()
         end
         end
         



    
    
    8. Add a `Memory` property.
    
        ```lua
    @Component
    script DataStore extends Component
        property table Memory = {}
    end
    

  9. Create a `SetMemory()` function and write the following, so it can store the value to key.
         
         @Component
         script MemoryManager extends Component
         @ExecSpace("ServerOnly")
         method void SetMemory(string key, any value)
         self.Memory[key] = value
         end
         end
         



    
    
    10. Create a `GetMemory()` function and write the following, so it can get the value stored in key.
    
        ```lua
    @Component
    script MemoryAccessor extends Component
        @ExecSpace("ServerOnly")
        method any GetMemory(string key)
        return self.Memory[key]
        end
    end
    
    
    
    

## Create BTNodeType

Let's create a tree structure like below using BTNodeType. In the example, you will create eight BTNodeType nodes. These nodes will operate together to make the monster move around and chase the player.

  1. Select Workspace - MyDesk - Create Scripts - BTNodeType and create eight new BTNodeType nodes. These eight BTNodeType nodes will serve different roles, as shown below.



[{"Node":"ActionChangeStateAlert","Description":"Modifies the monster's material. The detection range will be shown in red."},{"Node":"ActionChangeStateCalm","Description":"Reverts the monster's material to the original. The detection range will be shown in green."},{"Node":"ActionFollow","Description":"Tracks down a player."},{"Node":"ActionMoveRandom","Description":"Moves in a random direction."},{"Node":"ActionStop","Description":"Stops moving."},{"Node":"ActionWait","Description":"Stands still for a certain amount of time."},{"Node":"DecoHasNoTarget","Description":"The Decorator executes the child node if no players are detected and returns Failure if a player is detected."},{"Node":"DecoHasTarget","Description":"The Decorator executes the child node if a player is detected and returns Failure if no players are detected."}]

#### ActionChangeStateAlert

Write the following to change the monster's appearance to the Rainbow material in ActionChangeStateAlert, and change the color of the detection range in PolygonRendererComponent.
    
    
    @Component
    script MaterialColorSetter extends Component
        method any OnBehave(number delta)
        local sprite = self.ParentAI.Entity.SpriteRendererComponent
        local polygon = self.ParentAI.Entity.PolygonRendererComponent
    
        if sprite == nil or polygon == nil then
            return BehaviourTreeStatus.Failure
        end
    
        sprite:ChangeMaterial("material://EntryID")
        polygon.Color = Color(1, 0, 0, 0.25)
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionChangeStateCalm

Write the following to show the detection range in green and set the monster's material to be turned off when no player entities are detected within the detection range.
    
    
    @Component
    script BasicGreenApplier extends Component
        method any OnBehave(number delta)
        -- Modifies the monster's appearance.
        -- Modifies the sprite's material to basic material and the color of the polygon renderer to green.
    
        local sprite = self.ParentAI.Entity.SpriteRendererComponent
        local polygon = self.ParentAI.Entity.PolygonRendererComponent
    
        if sprite == nil or polygon == nil then
            return BehaviourTreeStatus.Failure
        end
    
        sprite:ChangeMaterial(nil)
        polygon.Color = Color(0, 1, 0, 0.25)
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionFollow

Write the following to let the monster chase the player when it enters the specified range. Use `AIPatrolComponent:GetMemory()` to check for the identified players.
    
    
    @Component
    script PlayerPursuit extends Component
        method any OnBehave(number delta)
        ---@type any
        local parentAI = self.ParentAI
        local transform = parentAI.Entity.TransformComponent
        local target = parentAI:GetMemory("PlayerInRange")
    
        if target == nil then
            return BehaviourTreeStatus.Failure
        end
    
        local diff = target.TransformComponent.WorldPosition - transform.WorldPosition
        diff = diff:ToVector2()
        local dist = diff:Magnitude()
    
        if dist > 0 then
            local dir = diff:Normalize()
            local movement = self.ParentAI.Entity.MovementComponent
            movement:MoveToDirection(dir, 0)
            return BehaviourTreeStatus.Running
        end
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionMoveRandom

Write the following so that the monster can move around in different directions.
    
    
    @Component
    script TimedWander extends Component
        property Vector2 MoveDirection = Vector2(0,0)
        property number MoveTime = 0
        property number ElapseTime = 0
    
        method void OnInit()
        -- Sets new movement direction and time on each execution.
    
        local randomX = _UtilLogic:RandomDouble() - 0.5
        local randomY = _UtilLogic:RandomDouble() - 0.5
    
        self.MoveDirection = Vector2(randomX, randomY):Normalize()
        self.MoveTime = _UtilLogic:RandomDouble() * 2 + 2
        self.ElapsedTime = 0
    
        local movement = self.ParentAI.Entity.MovementComponent
        movement:MoveToDirection(self.MoveDirection, 0)
        end
    
        method any OnBehave(number delta)
        -- Moves in accordance with the time and direction specified in OnInit().
        -- Returns success when the monster touches the player.
    
        local movement = self.ParentAI.Entity.MovementComponent
    
        self.ElapsedTime = self.ElapsedTime + delta
    
        if self.ElapsedTime >= self.MoveTime then
            movement:MoveToDirection(Vector2.zero, 0)
            return BehaviourTreeStatus.Success        
        end
    
        return BehaviourTreeStatus.Running       
        end
    end
    

#### ActionStop

Write the following, so the monster can stop moving.
    
    
    @Component
    script StopMovement extends Component
        method any OnBehave(number delta)
        -- Stops moving.
        local movement = self.ParentAI.Entity.MovementComponent
    
        if movement ~= nil then
            movement:Stop()
        end
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionWait

Write the following, so the monster stands still and waits for a certain amount of time. Returns Running before the specified time passes, and returns Success otherwise.
    
    
    @Component
    script TimedWait extends Component
        property number ElapsedTime = 0
        property number Time = 0
    
        method void OnInt()
        self.ElapsedTime = 0
        end
    
        method any OnBehave(number delta)
        self.ElapsedTime = self.ElapsedTime + delta
    
        if self.ElapsedTime >= self.Time then
            return BehaviourTreeStatus.Success
        else
            return BehaviourTreeStatus.Running
        end
        end
    end
    

#### DecoHasNoTarget

Write the following, so the child node is executed if no players are detected and returns Failure otherwise.
    
    
    @Component
    script PlayerAbsenceCondition extends Component
        property any Child = nil
    
        method any OnBehave(number delta)
        ---@type any
        local parentAI = self.ParentAI
        local target = parentAI:GetMemory("PlayerInRange")
    
        if target == nil then
            return self.Child:Behave(delta)
        else
            return BehaviourTreeStatus.Failure
        end
        end
    end
    

#### DecoHasTarget

Write the following, so the child node is executed if a player is detected and Failure is returned otherwise.
    
    
    @Component
    script PlayerInRangeCondition extends Component
        property any Child = nil
    
        method any OnBehave(number delta)
        ---@type any
        local parentAI = self.ParentAI
        local target = parentAI:GetMemory("PlayerInRange")
    
        if target == nil then
            return BehaviourTreeStatus.Failure
        else
            return self.Child:Behave(delta)
        end
        end
    end
    

## Creating Behaviour Trees

  1. Add a new `CreateBehaviourTree()` function in AIPatrolComponent. Create a tree structure as follows.
         
         @Component
         script AIBehaviorTreeBuilder extends Component
         @ExecSpace("ServerOnly")
         method void CreateBehaviourTree()
         local root = SelectorNode("Root")
         
         local seqAttack = SequenceNode("Attack")
         local actAlertState = self:CreateNode("ActionChangeStateAlert", "ChangeState: Alert")
         local actFollow = self:CreateNode("ActionFollow", "Follow")
         
         seqAttack:AttachChild(actAlertState)
         seqAttack:AttachChild(actFollow)
         
         local decoHasTarget = self:CreateNode("DecoHasTarget", "HasTarget (Attack)")
         decoHasTarget.Child = seqAttack
         
         local seqPatrol = SequenceNode("Patrol")
         local seqMove = SequenceNode("Move")
         local actCalmState = self:CreateNode("ActionChangeStateCalm", "ChangeState: Calm")
         local actStop = self:CreateNode("ActionStop", "Stop")
         local actMoveRandom = self:CreateNode("ActionMoveRandom", "MoveRandom")
         local actWait = self:CreateNode("ActionWait", "Wait")
         actWait.Time = 3
         
         seqMove:AttachChild(actWait)
         seqMove:AttachChild(actMoveRandom)
         
         local decoHasNoTarget = self:CreateNode("DecoHasNoTarget", "HasNoTarget (Move)")
         decoHasNoTarget.Child = seqMove
         
         seqPatrol:AttachChild(actCalmState)
         seqPatrol:AttachChild(actStop)
         seqPatrol:AttachChild(decoHasNoTarget)
         
         root:AttachChild(decoHasTarget)
         root:AttachChild(seqPatrol)
         
         self:SetRootNode(root)
         end
         end
         



    
    
    2. Modify as shown below, so the `OnBeginPlay()` function calls the `CreateBehaviourTree()` function.
    
        ```lua
    @Component
    script BehaviorRangeController extends Component
        method void OnBeginPlay()
        self:CreateBehaviourTree()
        self:DrawRange()
        end
    end