DOTY

Behavior Tree 본문

Unity

Behavior Tree

증식세포 2024. 11. 14. 02:47
728x90
반응형

AI의 트리

 

Node : Success, Failure, Running에 따라서 노드 상태 결정

Action : 어떤 동작

Sequence : 일의 순서. 만약 자식이 하나라도 실패하면 실패로 반환

Selector : 일의 선택. 만약 어떤 자식이 하나라도 성공하면 성공으로 반환

네 가지를 구현해보자.

 

구현할 내용들

1. Node - Interface

NodeState를 enum 형태로 선언

Node에서 필요한 것은 진행 : Running, 성공 : Success, 실패 : Failure 가 필요하다.

Node클래스는 세가지의 노드들에 대해서 각자 평가하는 함수 - Evaluate()가 존재.

이 함수는 Tree형태로 되어있고 각 원하는 동작에 대한 Behavior Tree를 설계할 때 이 Tree의 Root로 정해준다.

 

 

 

2. Action - Node Interface

NodeState를 반환하는 함수를 매개변수로 하고 그 함수의 결과에 따라서 코드가 진행된다.

Sequence, Selector 내부에서 Action Node 의 결과에 따라 진행이 결정된다.

    public NodeState Evaluate()
    {
        return onUpdate?. Invoke() ?? NodeState.Failure;
    }

 

3. Sequence

Sequence와 연결된 모든 Node들을 하나씩 진행한다. List에 여러 Node들을 넣고 매개변수로 넘겨 NodeState를 확인. NodeState가 하나라도 Failure이 있다면 더 이상 진행하지 않고 반환.

    public NodeState Evaluate()
    {
        if(childs == null || childs.Count == 0)
        {
            return NodeState.Failure;
        }

        foreach (INode child in childs)
        {
            switch(child.Evaluate())
            {
                case NodeState.Failure:
                    return NodeState.Failure;
                case NodeState.Success:
                    continue;
                case NodeState.Running:
                    return NodeState.Running;
                
            }
        }

        return NodeState.Success;
    }

 

4. Selector

Selector와 연결된 모든 Node들을 한번씩 확인한다.  List에 여러 Node들을 넣고 매개변수로 넘겨 NodeState를 확인.

NodeState를 확인하고 Success가 있다면 더 이상 진행하지 않고 그 순간 해당 Action을 진행 한 후 반환.

    public NodeState Evaluate()
    {
        if(childs == null)
        {
            return NodeState.Failure;
        }

        // If Success return Success. If Fail Keep Going.
        foreach (INode child in childs)
        {
            switch(child.Evaluate())
            {
                case NodeState.Success:
                    return NodeState.Success;
                case NodeState.Running:
                    return NodeState.Running;
            }
        }

        return NodeState.Failure;
    }

 


플레이어를 따라다니는 오브젝트 구현

    [SerializeField] GameObject player;
    [SerializeField] float moveSpeed;
    [SerializeField] float nearBoundary;

    BehaviorTreeRunner btRunner;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        btRunner = new BehaviorTreeRunner(SettingFollowBT());
    }

    // Update is called once per frame
    void Update()
    {
        btRunner.Operate();
    }

    INode SettingFollowBT()
    {
        INode root = new SelectorNode(
            new List<INode>()
            {
                new SequenceNode(
                    new List<INode>()
                    {
                        new ActionNode(IsNearPlayer),
                        new ActionNode(Wait)
                    }
                ),
                new ActionNode(FollowPlayer)
            }
        );

        return root;
    }

    NodeState IsNearPlayer()
    {
        if(Vector3.Magnitude(player.transform.position - transform.position) < nearBoundary)
        {
            return NodeState.Success;
        }

        return NodeState.Failure;
    }

    NodeState Wait()
    {
        return NodeState.Success;
    }

    NodeState FollowPlayer()
    {
        if(Vector3.Magnitude(player.transform.position - transform.position) < nearBoundary)
        {
            return NodeState.Success;
        }
        transform.position = Vector3.MoveTowards(transform.position , player.transform.position, Time.deltaTime * moveSpeed);
        
        return NodeState.Running;
    }

 

728x90
반응형
Comments