최적화
어차피 실행 중에는 노드가 추가되거나 삭제되지 않으므로 자식 노드들을 매 프레임 찾을 필요가 없는 것 같다.
현재 아래와 같이 실행하고 있으므로 미리 캐싱을 해서 조금이나마 최적화를 진행
// 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
'Unity > 개발연습' 카테고리의 다른 글
[Unity] Behaviour Tree Editor 제작기 - 7 (2) | 2024.10.21 |
---|---|
[Unity] Behaviour Tree Editor 제작기 - 6 (2) | 2024.09.04 |
[Unity] Behaviour Tree Editor 제작기 - 5 (0) | 2024.09.01 |
[Unity] Behaviour Tree Editor 제작기 - 4 (1) | 2024.08.30 |
[Unity] Behaviour Tree Editor 제작기 - 3 (0) | 2024.08.30 |