Unity/개발연습

[Unity] Behaviour Tree Editor 제작기 - 8

민트초밥 2024. 10. 21. 18:05

최적화

 

어차피 실행 중에는 노드가 추가되거나 삭제되지 않으므로 자식 노드들을 매 프레임 찾을 필요가 없는 것 같다.

현재 아래와 같이 실행하고 있으므로 미리 캐싱을 해서 조금이나마 최적화를 진행

// Tree가 평가될 때마다 실행 (매 프레임 실행되는 함수)
public override NodeStates Evaluate()
{
    foreach (string nodeGuid in childNodeGuidList)
    {
    	// 매번 Node를 찾을 필요가 없다
        var node = tree.FindNode(nodeGuid);

        if (node == null)
        {
            Debug.LogError($"{nameof(SelectorNode)} : Child Node Not Found");
            continue;
        }

        node.Evaluate();
    }

    return NodeState = NodeStates.Success;
}

 

 

 

 

Node들이 처음 실행될 때 자식 노드들을 미리 찾아서 캐싱

protected List<BehaviourNode> childNodes;

// 한 번만 실행됨
public virtual void Init(BehaviourTree tree)
{
    this.tree = tree;
    NodeState = NodeStates.None;

    foreach (string nodeGuid in childNodeGuidList)
    {
        BehaviourNode node = tree.FindNode(nodeGuid);

        if (node == null)
        {
            Debug.LogError($"{nameof(SelectorNode)} : Child Node Not Found");
            continue;
        }

        childNodes.Add(node);
    }
}

 

 

 

 

Blackboard 데이터 저장 기능 변경

 

간편하기 쓰기 위해서 Dictionary안에 Dictionary가 들어가는 구조로 만들었는데, 반복적으로 실행된다면 형변환 과정에서 성능이 떨어지지 않을까??라는 생각에 수정을 했다.

 

 

변경 전

public class BehaviourTreeBlackboard
{
    private Dictionary<Type, IDictionary> blackboardDictionary = new Dictionary<Type, IDictionary>();

    public void SetData<T>(string keyName, T value)
    {
        if (!blackboardDictionary.ContainsKey(typeof(T)))
            blackboardDictionary.Add(typeof(T), new Dictionary<string, T>());

        IDictionary dic = blackboardDictionary[typeof(T)];

        if (dic.Contains(keyName))
        {
            dic[keyName] = value;
        }
        else
        {
            dic.Add(keyName, value);
        }
    }


    public T GetData<T>(string keyName)
    {
        if (!blackboardDictionary.ContainsKey(typeof(T)))
            return default(T);

        IDictionary dic = blackboardDictionary[typeof(T)];

        if (!dic.Contains(keyName))
            return default(T);

        return (T)dic[keyName];
    }


    public void ClearDatas()
    {
        blackboardDictionary.Clear();
    }
}

 

 

 

 

하드코딩이긴 하지만 직관적이고 형변환 과정이 필요 없으므로 이렇게 변경

더 필요한 데이터가 발생하면 Get, Set 함수만 추가해서 사용

 

Component는 어차피 참조형이라 개별 노드에서 한번만 참조해서 사용해도 되기 때문에 업캐스팅/다운캐스팅이 발생해도 크게 상관없을 것 같아서 하나로 처리

 

int, float, string 등은 매 프레임 값을 변경할 여지가 있을 수도 있어서 따로 처리했다. (쓸 필요가 없을 수도 있지만...)

public class BehaviourTreeBlackboard
{
    private Dictionary<string, string> stringDic = new Dictionary<string, string>();
    private Dictionary<string, int> intDic = new Dictionary<string, int>();
    private Dictionary<string, float> floatDic = new Dictionary<string, float>();

    private Dictionary<string, Component> componentDic = new Dictionary<string, Component>();


    public void Clear()
    {
        stringDic.Clear();
        intDic.Clear();
        floatDic.Clear();

        componentDic.Clear();
    }


    public void SetBBString(string key, string value) => SetData(stringDic, key, value);
    public void SetBBInt(string key, int value) => SetData(intDic, key, value);
    public void SetBBFloat(string key, float value) => SetData(floatDic, key, value);
    public void SetBBComponent(string key, Component value) => SetData(componentDic, key, value);

    public void GetBBString(string key) => GetData(stringDic, key, null);
    public void GetBBInt(string key) => GetData(intDic, key, default);
    public void GetBBFloat(string key) => GetData(floatDic, key, default);
    public void GetBBComponent(string key) => GetData(componentDic, key, default);



    private void SetData<T>(Dictionary<string, T> dictionary, string key, T value)
    {
        if (!dictionary.ContainsKey(key))
            dictionary.Add(key, value);
        else
            dictionary[key] = value;
    }

    private T GetData<T>(Dictionary<string, T> dictionary, string key, T defauleValue)
    {
        return dictionary.GetValueOrDefault(key, defauleValue);
    }
}

 

 

 

 

Context, Blackboard의 기능 중복

 

개별 노드에서 컴포넌트(ex. Rigidbody, Collider, Animator Controller 등)의 접근이 필요하기 때문에 컴포넌트들을 관리하기 위한 목적으로 Context를 만들었었는 데 사용하다 보니 Blackboard의 기능과 중복이 되고 Blackboard로 대체를 할 수 있을 것 같다는 생각이 들었다.

 

현재 Context는 아래와 같이 되어있고 Tree를 생성할 때 Context도 같이 생성을 하고 있다.

public class BehaviourTreeContext
{
    public GameObject GameObject { get; private set; }
    public Transform Transform { get; private set; }
    public Animator Animator { get; private set; }

    public BehaviourTreeContext(GameObject gameObject)
    {
        GameObject = gameObject;
        Transform = gameObject.transform;
        Animator = gameObject.GetComponent<Animator>();
    }
}
// Tree 생성 시 실행되는 함수
public void Init(GameObject gameObject)
{
    this.Blackboard = new BehaviourTreeBlackboard();
    this.Context = new BehaviourTreeContext(gameObject);

    if (string.IsNullOrEmpty(RootNodeGuid))
        Debug.LogError($"{nameof(BehaviourTree)} : Root Node Guid is Empty");

    rootNode = FindNode(RootNodeGuid);

    InitRecursive(rootNode);
}

 

 

 

 

이런 느낌이면 되지 않을까??

public class BehaviourTreeController : MonoBehaviour
{
    public BehaviourTree BehaviourTree;

    private void Awake()
    {
        AddBlackboardDatas();

        BehaviourTree.Init(gameObject);
    }

    private void Update()
    {
        BehaviourTree.Evaluate();
    }

    private void AddBlackboardDatas()
    {
        
    }
}

 

 

 

 

사실 Blackboard의 용도가 명확하지 않아서 이게 맞는 건지는 잘 모르겠다.

Unreal의 Behaviour Tree를 사용해보고 만들고 있는 기능이 아니라 문서만 읽어보고 참고하는 중이라...

 

 

 

 

Condition Node 추가

 

단순 조건 비교를 위한 Condition Node 타입도 추가했다.

 

 

 

 

 

 

 

GitHub - mintchobab/Unity_Practice_Editor

Contribute to mintchobab/Unity_Practice_Editor development by creating an account on GitHub.

github.com

 

반응형