- 오늘은 화요일에 진행한 챌린지반 특강 내용정리!
<출처 : 챌린지반 강의노트>
Generic
Generic?
- Generic이란 타입을 일반화 하는것을 의미한다. 유니티는 GetComponet, List와 같이 일반화 프로그래밍이 잘 되어있지만.. C언어는 이게 잘되어 있지 않다.
- 타입을 일반화 한다는것은 다양한 데이터타입에 대해 하나의 함수나 클래스를 정의할 수 있도록 하는 프로그래밍 기법을 의미한다.
- 보통 데이터 형식에 의해 동일한 로직을 적용해야 할 때 많이 활용이 되고 게임컨텐츠 로직보단 시스템 로직에 좀 더 많이 활용이 된다.
- 다만 코드의 유연성과 재사용성을 높일 수 있지만 남용하면 가독성이 떨어지게 되므로 조심해서 사용해야한다.
제약조건 where
- 제약을 주면서 특정 조건 혹은 클래스만 활용할 수 있게 만드는 키워드이다. where 구문을 사용
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
- 이런식으로 마지막에 where T : () 를 통해 제약조건을 설정할 수 있다.
Generic Class
- 기본적으로 메소드에서도 사용이 가능하지만 보통 클래스에서 제네릭이 많이 사용된다.
- 예전에 한번 정리한 제네릭 싱글톤 코드
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null)
{
GameObject singletonObject = new GameObject();
instance = singletonObject.AddComponent<T>();
}
}
return _instance;
}
}
public void Awake()
{
DontDestroyOnLoad(instance);
}
}
- class 선언에 MonoBehaviour 타입을 받게 해서 제네릭으로 각각의 매니저를 사용하는 방식.
- 제네릭을 이용한 리소스 관리 매니저
private void LoadResourcesByType<T>(string path) where T : UnityEngine.Object
{
T[] resources = Resources.LoadAll<T>($"{path}");
if (resources.Length > 0)
{
if (!resourceMap.ContainsKey(typeof(T)))
{
resourceMap[typeof(T)] = new Dictionary<string, UnityEngine.Object>();
}
foreach (T resource in resources)
{
if (resourceMap[typeof(T)].ContainsKey(resource.name))
{
Debug.LogWarning($"Resource of type {typeof(T).Name} with name {resource.name} already exists.");
continue;
}
resourceMap[typeof(T)][resource.name] = resource;
}
}
else
{
Debug.LogWarning($"No resources of type {typeof(T).Name} found in {path}.");
}
}
public T GetResource<T>(string name) where T : UnityEngine.Object
{
Type type = typeof(T);
if (resourceMap.ContainsKey(type) && resourceMap[type].ContainsKey(name))
{
return resourceMap[type][name] as T;
}
Debug.LogWarning($"No resources of type {type.Name} found with name {name}");
return null; // 리소스가 없는 경우 null 반환
}
- 이것 역시 적절한 타입을 받아와서 Resources.Load를 통해 적절한 타입을 자유롭게 받아오는 코드이다.
BT(행동트리)
BT란?
- 행동트리는 인공지능을 설계하는데 사용되는 계층적 모델을 의미한다.
- 이름처럼 Tree 나무처럼 되어있어 각 노드들이 있어 노드가 순서대로 실행되거나 특정 조건을 만족하면 실행되는 구조로 이루어져 있다.
- 게임에서 보통 AI의 행동, 보스 전투 순서를제어하는 데 많이 사용된다.
- 탐색은 깊이 우선 탐색으로 로직이 실행
- 유연하고 확장성이 뛰어나 복잡한 행도패턴을 쉽게 관리할 수 있다.
- 보통 언리얼엔진에 이런 행동트리가 구현되어 있어서 예전에 언리얼 공부했을때 한번 사용해본 기억이 있다..( 그당시에도 엄청 복잡했던것으로 기억)
FSM(상태기계)와의 비교
- FSM은 상태와 전이의 집합으로 각 상태에서 다른상태로 전이할 때마다 특정행동을 수행한다.
- 구조가 단순하여 이해하기 쉽지만 복잡한 행동을 구현할 때는 상태가 기하급수적으로 늘어나 관리가 어렵다.
- 다만 BT의 경우 처음에는 어려워 학습할 필요가 있고 모든 조건을 확인해야 하는경우에는 리소스가 많이 소모된다.
BT의 노드들
- 루트 노드 : 제일 위에있는 뿌리
- 흐름 제어 노드
- Sequence node : AND 역할을 하는 노드
- Selector node : OR 역할을 하는 노드
- leaf 노드 : 조건이나 실제 행동이 들어가 있는 노드
노드 로직
- 전체 로직은 내용이 많아서 노드를 구현 로직만 간단하게 정리
//노드의 상태를 표시
public enum NodeState
{
Running,
Failure,
Success
}
public abstract class Node
{
protected NodeState state;
public Node parentNode;
protected List<Node> childrenNode = new List<Node>();
public Node()
{
parentNode = null;
}
public Node(List<Node> children)
{
foreach(var child in children)
{
AttatchChild(child);
}
}
public void AttatchChild(Node child)
{
childrenNode.Add(child);
child.parentNode = this;
}
public abstract NodeState Evaluate();
}
// Sequence Node 왼쪽부터 실행 하는데 처음으로 실패가 나올떄까지 진행
public class SequenceNode : Node
{
public SequenceNode() : base() {}
public SequenceNode(List<Node> children) : base(children) {}
public override NodeState Evaluate()
{
bool bNowRunning = false;
foreach (Node node in childrenNode)
{
switch (node.Evaluate())
{
//실패 케이스
case NodeState.Failure:
return state = NodeState.Failure;
case NodeState.Success:
continue;
case NodeState.Running:
bNowRunning = true;
continue;
default:
continue;
}
}
// 다만 Running 상태일 떄에는 그 상태를 유지해야 하기때문에 다음 노드로 이동하면 안되고 다음 프레임일때도 평가를 해줘야함.
return state = bNowRunning ? NodeState.Running : NodeState.Success;
}
}
//SelectorNode는 자식중에 처음으로 Success 혹은 Running 상태를 가진 노트가 있을떄까지 진행한다.
public class SelectorNode : Node
{
public SelectorNode() : base(){}
public SelectorNode(List<Node> children) : base(children){}
public override NodeState Evaluate()
{
foreach(Node node in childrenNode)
{
switch(node.Evaluate())
{
//실패라면 contine로 다음 노드 탐색
case NodeState.Failure:
continue;
case NodeState.Success:
return state = NodeState.Success;
case NodeState.Running:
return state = NodeState.Running;
default:
continue;
}
}
return state = NodeState.Failure;
}
}
오늘의 회고
- 오늘 팀프로젝트 포트리스 지형파괴를 했는데 어려우면서도 생각보다 재미있어서 팀원분들과 계속 회의하면서 코드진행을 했다. 복잡한 로직이다보니 정리하기 힘들어서 좀 코드를 잘 봐야겠다. 그래도 오랜만에 재미있는 로직을 봐서 학구열이 불타올랐다.
- 나름 이번 팀프로젝트는 생각보다 수월한 느낌이다. 자유주제라 그런가 하고싶은것을 맘껏 펼치고 있는기분이다. 남은 기간도 잘 진행해 봐야겠다.