내일배움캠프 45일차 TIL, Generic + BT

황오영·2024년 6월 20일
0

TIL

목록 보기
45/56
post-thumbnail
  • 오늘은 화요일에 진행한 챌린지반 특강 내용정리!
    <출처 : 챌린지반 강의노트>

Generic

Generic?

  • Generic이란 타입을 일반화 하는것을 의미한다. 유니티는 GetComponet, List와 같이 일반화 프로그래밍이 잘 되어있지만.. C언어는 이게 잘되어 있지 않다.
  • 타입을 일반화 한다는것은 다양한 데이터타입에 대해 하나의 함수나 클래스를 정의할 수 있도록 하는 프로그래밍 기법을 의미한다.
  • 보통 데이터 형식에 의해 동일한 로직을 적용해야 할 때 많이 활용이 되고 게임컨텐츠 로직보단 시스템 로직에 좀 더 많이 활용이 된다.
  • 다만 코드의 유연성과 재사용성을 높일 수 있지만 남용하면 가독성이 떨어지게 되므로 조심해서 사용해야한다.

제약조건 where

  • 제약을 주면서 특정 조건 혹은 클래스만 활용할 수 있게 만드는 키워드이다. where 구문을 사용
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour 
  • 이런식으로 마지막에 where T : () 를 통해 제약조건을 설정할 수 있다.

Generic Class

  • 기본적으로 메소드에서도 사용이 가능하지만 보통 클래스에서 제네릭이 많이 사용된다.
  1. 예전에 한번 정리한 제네릭 싱글톤 코드
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 타입을 받게 해서 제네릭으로 각각의 매니저를 사용하는 방식.
  1. 제네릭을 이용한 리소스 관리 매니저
    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의 노드들

  • 루트 노드 : 제일 위에있는 뿌리
  • 흐름 제어 노드
  1. Sequence node : AND 역할을 하는 노드
  2. 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;
    }
}

오늘의 회고

  • 오늘 팀프로젝트 포트리스 지형파괴를 했는데 어려우면서도 생각보다 재미있어서 팀원분들과 계속 회의하면서 코드진행을 했다. 복잡한 로직이다보니 정리하기 힘들어서 좀 코드를 잘 봐야겠다. 그래도 오랜만에 재미있는 로직을 봐서 학구열이 불타올랐다.
  • 나름 이번 팀프로젝트는 생각보다 수월한 느낌이다. 자유주제라 그런가 하고싶은것을 맘껏 펼치고 있는기분이다. 남은 기간도 잘 진행해 봐야겠다.
profile
게임개발을 꿈꾸는 개발자

0개의 댓글