내일배움캠프/TIL

[내배캠][Unity6기][TIL] Behavior Tree 구조 설계 (2)

binary는 호남선 2024. 11. 30. 22:00

오늘은 개인 일정이 있어 거의 진행을 못했다 ㅠ

어제 작성해 둔 코드 로직 한번 더 검토하고 다시 나가봐야한다...

조건 분기가 많아 Condition을 주로 사용하게 될 줄 알았는데 직접 구현해보니 Sequence와 Selector만 사용하게 되었다.

이부분은 테스트 진행하면서 제대로 로직상 상태가 변경되어야 하는 부분 있으면 트리 구조 스크립트만 수정하면 되므로 차차 다듬어나가면 될 것 같다.

 

기본 노드는 튜터님이 참고하라고 공유해주신 블로그 자료를 베이스로 작성되었다.

Condition과 Action은 지금 당장 사용하지는 않는데, 혹시 나중에 사용될 수도 있어서 만드는 김에 미리 작성했다.

INode

public enum NodeState { Success, Failure, Running }

public interface INode
{
    // 현재 노드 어떤 상태인지 반환
    NodeState Evaluate();
}

SelectorNode

using System.Collections.Generic;

// 자식 노드에서 처음으로 Success나 Running 상태 노드 발생하면
// 그 노드까지 진행하고 멈추는 노드
public class SelectorNode : INode
{
    private List<INode> _children = new List<INode>();

    public void AddChild(INode child)
    {
        _children.Add(child);
    }

    public NodeState Evaluate()
    {
        if (_children == null)  return NodeState.Failure;
        
        foreach (INode child in _children)
        {
            switch (child.Evaluate())
            {
                case NodeState.Success:
                    return NodeState.Success;
                case NodeState.Running:
                    return NodeState.Running;
                // 자식 상태가 Failure 면 다음 자식으로 이동
            }
        }
        return NodeState.Failure;
    }
}

SequenceNode

using System.Collections.Generic;

// 자식 노드를 순서대로 진행하면서 Failure 상태가 나올 때까지 진행하는 노드
public class SequenceNode : INode
{
    private readonly List<INode> _children = new List<INode>();

    public void AddChild(INode child)
    {
        _children.Add(child);
    }

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

        foreach (INode child in _children)
        {
            switch (child.Evaluate())
            {
                case NodeState.Success:
                    continue;   // 자식 상태 Success 면 다음 자식으로 이동
                case NodeState.Running:
                    return NodeState.Running;
                case NodeState.Failure:
                    return NodeState.Failure;
            }
        }
        return NodeState.Failure;
    }
}

ConditionNode

using System;

// 조건문 확인 후 true면 하위 노드 실행, false면 failure 반환
public class ConditionNode : INode
{
    private INode _node;
    private Func<bool> _condition;

    public ConditionNode(Func<bool> condition, INode node)
    {
        _condition = condition;
        _node = node;
    }

    public NodeState Evaluate()
    {
        bool result = _condition.Invoke();
        return result ? _node.Evaluate() : NodeState.Failure;
    }
}

ActionNode

using System;

// 실제로 행위를 하는 노드
public class ActionNode : INode
{
    private Func<NodeState> _onUpdate;

    public ActionNode(Func<NodeState> onUpdate)
    {
        _onUpdate = onUpdate;
    }
    
    public NodeState Evaluate() => _onUpdate?.Invoke() ?? NodeState.Failure;
}

BehaviorTree

public class BehaviorTree
{
    private readonly INode root;

    public BehaviorTree(INode rootNode)
    {
        root = rootNode;
    }

    public void Update()
    {
        root.Evaluate();
    }
}

 

참고자료:

https://unity-programming-study.tistory.com/14
https://leekangw.github.io/posts/91/
https://husk321.tistory.com/412

 

https://learn.unity.com/tutorial/2d-game-kit-advanced-topics#64883466edbc2a498872116c
https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work
https://lifeisforu.tistory.com/327
https://maplestoryworlds-creators.nexon.com/ko/docs?postId=562