행동 트리 노드 만들기

# 학습 과정 소개

행동 트리를 만드는 데 사용하는 노드에 대해 알아보고, 직접 예제를 따라하며 행동 트리에 따라 움직이는 AI 몬스터를 만들어 봅시다.

##### 참고 가이드

  * [행동 트리를 활용한 AI 만들기](/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)



# 행동 트리 만들기

#### Composite Node 생성

Composite Node 객체는 각 노드의 생성자를 통해 생성할 수 있습니다. Composite Node의 생성자는 다음과 같습니다.

[{"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()

Composite Node는 자식 노드를 가질 수 있습니다. 자식 노드를 만들 때는 `AttachChild()`를 활용합니다. Composite Node는 Action Node, Composite Node도 자식으로 가질 수 있으며 이를 이용해 복잡한 트리 구조를 형성할 수 있습니다.

#### AttachChildAt()

트리 구조는 자식 노드를 실행할 때 자식 노드로 등록된 순서를 따르므로 등록 순서를 유의해야 합니다. 노드의 실행 순서를 변경 방법은 두 가지입니다. 첫 번째로 코드상에서 순서를 변경합니다. 두 번째로 `AttachChildAt()` 함수를 활용합니다.

`AttachChildAt()`는 등록 순서와 관계없이 특정 위치에 노드를 등록할 수 있습니다. `AttachChildAt()` 함수의 기본 사용 방식은 `AttachChild()`와 동일합니다. 차이점은 두 번째 매개 변수로 노드가 위치할 순서를 넘겨주는 것입니다. 순서는 0부터 시작하며, 이미 등록된 노드와 같은 위치에 새 노드를 등록하면 기존 노드의 위치가 하나씩 뒤로 밀려납니다. [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
    

#### Node 제거하기

이미 등록된 자식 노드를 제거할 때는 `DetachChild()`, `DetachChildAt()`를 활용합니다.

  * `DetachChild()`는 매개 변수로 떼어낼 자식 노드 객체를 넘겨 자식 노드를 제거합니다.
  * `DetachChildAt()`은 떼어낼 자식 노드 객체의 위치를 넘겨 자식 노드를 제거합니다.



[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 설정

Root Node는 `SetRootNode()`를 사용합니다. 매개 변수로 Root 노드가 노드를 최상위 노드로 넘겨줍니다. 최상위 노드를 Root 노드로 설정함으로써 엔티티에 AI를 부여하고 행동 트리를 작동시킬 수 있습니다.
    
    
    @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
    

#### Decorator Node 생성

Decorator Node는 BTNodeType을 활용해 생성할 수 있습니다. 새 BTNodeType을 생성하고, 프로퍼티로 any 타입의 Child 또는 child를 추가해 Decorator Node로 만들 수 있습니다. 자식 노드를 실행하고 싶을 때는 `Behave()` 함수를 호출합니다.
    
    
    @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
    

생성된 Decorator BTNodeType의 객체 노드 생성과 자식 노드 할당은 아래와 같이 할 수 있습니다.
    
    
    @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
    

#### 행동 트리 완성

Root Node 설정까지 마치면, 만든 행동 트리를 기반으로 AIComponent가 동작합니다. [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
    

# 행동 트리 활용 예제

AIComponent와 BTNodeType을 활용해 일정 범위 내에서 플레이어를 감지하고, 배회하는 AI 몬스터를 만들어 봅시다. 이 예제의 주요 구현 요소는 세 가지입니다.

  * 플레이어가 멀리 있을 때 몬스터는 무작위 방향으로 이동하며 배회합니다.
  * 일정 거리 안으로 플레이어가 들어오면 몬스터는 플레이어를 추적합니다.
  * 플레이어가 멀어지면 몬스터는 추적을 그만두고 다시 주위를 배회합니다.



[png](17168958937950674e59a9a8744ca81444187e01c8e85)

## 사전 준비

  1. RectTile 모드로 전환하고, 타일을 배치합니다. [png](1717483867395248704741b464dfa9bbf21b93f26f45e)

  2. Workspace - MyDesk - Create Material을 선택해 새로운 Alert 머티리얼을 추가합니다.

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

[png](1717483885157c4309e9ef2e34691a871092dd75b1906)

  3. Preset List - Monster - monster2358 프리셋을 추가합니다. 프리셋의 컴포넌트 중 TransformComponent, StateAnimationComponent, SpriteRendererComponent만 남겨 놓은 뒤 아래 3개 컴포넌트를 추가합니다.

     * StateComponent, MovementComponent, KinematicbodyComponent



[png](17174839092271a14772637854b71abaf190445ea359b)

## AIPatrolComponent 만들기

  1. Workspace - BaseEnvironment - NativeScripts - Component - AIComponent를 선택해 Extend합니다. 이름을 AIPatrolComponent로 변경합니다.

  2. 맵에 배치한 몬스터에 AIPatrolComponent를 추가합니다. [png](1717488266930702706135fff47409f16850ee919f377)

  3. `DrawRange()` 함수를 추가합니다. PolygonRendererComponent를 활용해 몬스터가 플레이어를 감지할 수 있는 범위를 보여줄 수 있도록 아래와 같이 작성합니다.
         
         @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. `Flip()` 함수를 생성합니다. 몬스터의 이동 방향에 따라 몬스터의 스프라이트를 좌우 반전할 수 있도록 아래와 같이 작성합니다.
    
        ```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. `DetectPlayer()` 함수를 생성하고, 플레이어와의 거리를 감지할 수 있도록 아래와 같이 작성합니다. 플레이어와의 거리가 Range 이하면 플레이어 엔티티를 메모리에 기록하고, 거리가 Range 이상이면 플레이어 엔티티를 메모리에서 제거합니다. 감지한 플레이어는 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. `OnUpdate`를 추가하고, `DetectPlayer()`, `Flip()` 함수를 호출하도록 아래와 같이 작성합니다.
    
        ```lua
    @Component
    script PlayerDetector extends Component
        method void OnUpdate(number delta)
        self:DetectPlayer()
        self:Flip()
        end
    end
    

  7. `OnBeginPlay`를 추가하고, `DrawRange()` 함수를 호출하도록 아래와 같이 작성합니다.
         
         @Component
         script RangeDrawer extends Component
         method void OnBeginPlay()
         self:DrawRange()
         end
         end
         



    
    
    8. `Memory` 프로퍼티를 추가합니다.
    
        ```lua
    @Component
    script DataStore extends Component
        property table Memory = {}
    end
    

  9. `SetMemory()` 함수를 생성하고 value를 key에 저장하도록 아래와 같이 작성합니다.
         
         @Component
         script MemoryManager extends Component
         @ExecSpace("ServerOnly")
         method void SetMemory(string key, any value)
         self.Memory[key] = value
         end
         end
         



    
    
    10. `GetMemory()`를 생성하고, key에 저장된 value를 얻도록 아래와 같이 작성합니다.
    
        ```lua
    @Component
    script MemoryAccessor extends Component
        @ExecSpace("ServerOnly")
        method any GetMemory(string key)
        return self.Memory[key]
        end
    end
    
    
    
    

## BTNodeType 만들기

BTNodeType을 활용해 아래의 트리 구조를 만들어 봅시다. 예제에서 만들 BTNodeType은 총 8개입니다. 이 노드들이 서로 동작하며 맵에서 움직이고, 플레이어를 추적하게 됩니다.

  1. Workspace - MyDesk - Create Scripts - BTNodeType을 선택해 새로운 8개의 BTNodeType을 만듭니다. 8개의 BTNodeType은 아래와 같이 각기 다른 역할을 합니다.



[{"노드":"ActionChangeStateAlert","설명":"몬스터의 머티리얼을 변경합니다. 감지 범위는 빨간색으로 변경됩니다."},{"노드":"ActionChangeStateCalm","설명":"몬스터의 머티리얼을 원래대로 되돌립니다. 감지 범위를 나타내는 원의 색상을 초록색으로 변경합니다."},{"노드":"ActionFollow","설명":"플레이어를 추적합니다."},{"노드":"ActionMoveRandom","설명":"무작위 방향으로 이동합니다."},{"노드":"ActionStop","설명":"정지합니다."},{"노드":"ActionWait","설명":"일정 시간 동안 대기합니다."},{"노드":"DecoHasNoTarget","설명":"감지된 플레이어가 없으면 자식 노드를 실행하고, 있으면 Failure를 반환하는 Decorator입니다."},{"노드":"DecoHasTarget","설명":"감지된 플레이어가 있으면 자식 노드를 실행하고, 없으면 Failure를 반환하는 Decorator입니다."}]

#### ActionChangeStateAlert

ActionChangeStateAlert에 몬스터의 외형이 Rainbow 머티리얼로 변경되고, 감지 범위인 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

몬스터의 감지 범위에 플레이어 엔티티가 없을 때 감지 범위는 초록색, 몬스터의 머티리얼을 꺼진 상태가 될 수 있도록 아래와 같이 작성합니다.
    
    
    @Component
    script MonsterAppearanceSetter 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(nil)
        polygon.Color = Color(0, 1, 0, 0.25)
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionFollow

범위 안에 플레이어가 들어왔을 때 플레이어를 추적할 수 있도록 아래와 같이 작성합니다. 감지된 플레이어를 확인하기 위해 `AIPatrolComponent:GetMemory()`를 사용합니다.
    
    
    @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

몬스터가 다양한 방향으로 이동할 수 있도록 아래와 같이 작성합니다.
    
    
    @Component
    script TimedRandomMove extends Component
        property Vector2 MoveDirection = Vector2(0,0)
        property number MoveTime = 0
        property number ElapseTime = 0
    
        method void OnInit()
        -- 실행할 때마다 이동 방향과 시간을 새롭게 결정합니다.
    
        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)
        -- OnInit()에서 결정된 방향과 시간만큼 이동합니다.
        -- 플레이어와 닿으면 success를 반환합니다.
    
        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

몬스터가 움직임을 정지할 수 있도록 아래와 같이 작성합니다.
    
    
    @Component
    script MovementStopper extends Component
        method any OnBehave(number delta)
        -- 정지합니다.
        local movement = self.ParentAI.Entity.MovementComponent
    
        if movement ~= nil then
            movement:Stop()
        end
    
        return BehaviourTreeStatus.Success
        end
    end
    

#### ActionWait

일정 시간 동안 동작을 멈추고 대기합니다. 일정 시간이 경과하기 전까지 Running을 반환하고, 시간이 경과하면 Success를 반환하도록 아래와 같이 작성합니다.
    
    
    @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

감지된 플레이어가 없으면 자식 노드를 실행하고, 있으면 Failure를 반환할 수 있도록 아래와 같이 작성합니다.
    
    
    @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

감지된 플레이어가 있으면 자식 노드를 실행하고, 없으면 Failure를 반환할 수 있도록 아래와 같이 작성합니다.
    
    
    @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
    

## 행동 트리 만들기

  1. AIPatrolComponent에 새로운 `CreateBehaviourTree()` 함수를 추가합니다. 트리 구조를 생성할 수 있도록 아래와 같이 작성합니다.
         
         @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. `OnBeginPlay()`함수에서 `CreateBehaviourTree()` 함수를 호출하도록 아래와 같이 수정합니다.
    
        ```lua
    @Component
    script BehaviorRangeController extends Component
        method void OnBeginPlay()
        self:CreateBehaviourTree()
        self:DrawRange()
        end
    end