디자인패턴
- 복잡하고 난해한 문제를 패턴화해서 풀기위해 사용
- 다른 개발자와 소통할때, 디자인패턴을 통해 간단하게 소통할 수 있음
주의사항
- 무작정 사용하는것은 지양해야함. 적절한 위치에 적절하게 사용
- 문제를 풀기위해 사용해야함. 디자인패턴을 쓰기위해 문제를 늘리면 안됨
※ 왜 디자인패턴을 사용했는지 설명할 수 있도록 사용해야함
싱글톤 패턴
- 중요하고, 유일하게 존재하는 대상에게 쉽게 접근하기위해 사용함
- 접근이 잦은 핵심기능에 대한 전역접 접근을 허용하기위해 사용함
- ex) Manager 계열 클래스들 - 하나만 존재해야하며, 다양한 위치에서 자주 접근함
구현
- 유일해야한다 : 싱글톤 패턴이 사용된 클래스가 추가로 생성되는것을 막을 방법이 필요함
- 전역적으로 접근 가능해야한다 : public static 을 사용하여 전역접근 구현
public class CharacterManager : MonoBehaviour
{
private static CharacterManager _instance;
//전역접근지점 제공
public static CharacterManager Instance
{
get
{
if(_instance == null)
{
_instance = new GameObject("CharacerManager").AddComponent<CharacterManager>();
}
return _instance;
}
}
//하나만 존재해야한다 -> instnace가 존재할경우, 새로 생성된 오브젝트 파괴
private void Awake()
{
if(_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
if(_instance != this)
{
Destroy(gameObject);
}
}
}
}
제네릭 싱글톤
- 싱글톤 객체간 유사성이 높음
- 단순한 상속으로 상위클래스로 접근할경우, 중복되는 코드가 많음
- 제네릭으로 싱글톤을 상속하여 하나의 클래스로 다수의 싱글톤 클래스를 만들 수 있음
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T instance;
public static T Instance { get { return instance; } }
public static bool InstanceExist { get { return instance != null; } }
protected virtual void Awake()
{
if(InstanceExist)
{
Destroy(gameObject);
return;
}
instance = (T)this;
}
protected virtual void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}
}
※제네릭
- 일반화 프로그래밍
- 클래스를 가변형으로 선언하여 사용
- List<>, GetComponent<> 등, <> 사이에 들어가는것이 제네릭으로 선언된 클래스 공간
- ※개인적인 생각 : 클래스를 매개변수처럼 사용할 수 있음
주의사항
- 라이프 사이클에 주의해야함
- Awake에서 인스턴스를 선언하는것, 인스턴스가 선언되지 않은 상태에서 호출시 오류가 발생
- Awake에서 자신과 관련된 내용을 초기화하고, Start에서 외부 참조를 하는방법으로 나누어주어 해결 할 수 있음
- 모든 싱글톤이 OnDestroyOnLoad 할 필요는 없음
- 씬이 넘어갈때 public 을 통해 외부에서 가져온 객체를 잃게됨(missing)
- 이 삭제된 참조를 되돌리는작업이, 씬을 넘겨줘서 얻는 이득보다 귀찮을 가능성이 높음
오브젝트풀 패턴
- 할당, 해제에 걸리는 성능낭비, 메모리낭비를 줄이기 위해 사용
- 생성, 파괴가 반복되는 오브젝트를 재사용하여 생성,파괴를 줄임
- pool 은 오브젝트를 묶은 단위
- ※ 게임중 로딩하면서 렉이 생기는것보다, 로딩중에 필요한것을 미리 로딩하여 렉의 가능성을 줄이는것
구현
- 필요한만큼 생성한뒤, 비활성화
- 오브젝트가 필요할때, 비활성화된 오브젝트를 활성화하여 사용
- 미리 생성된 양 이상 요구시 추가 생성
- 파괴대신 오브젝트 비활성화 (추가 생성된것은 경우에따라 비활성화 or 파괴)
public class ObjectPool : MonoBehaviour
{
// 오브젝트 풀 데이터를 정의할 데이터 모음 정의
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
public List<Pool> Pools;
public Dictionary<string, Queue<GameObject>> PoolDictionary;
private void Awake()
{
// 인스펙터창의 Pools를 바탕으로 오브젝트풀을 만들 것.
// 오브젝트풀은 오브젝트마다 따로이며, pool개수를 넘어가면 강제로 끄고 새로운 오브젝트에게 할당.
PoolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (var pool in Pools)
{
// 큐는 FIFO(First-in First-out) 구조로서, 줄을 서는 것처럼 가장 오래 줄 선(enqueue) 객체가 가장 먼저 빠져 나올(dequeue) 수 있는 구조
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
// Awake하는 순간 오브젝트풀에 들어갈 Instantitate 일어나기 때문에 터무니없는 사이즈 조심
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
// 줄의 가장 마지막에 세움.
objectPool.Enqueue(obj);
}
// 접근이 편한 Dictionary에 등록
PoolDictionary.Add(pool.tag, objectPool);
}
}
public GameObject SpawnFromPool(string tag)
{
// 애초에 Pool이 존재하지 않는 경우
if (!PoolDictionary.ContainsKey(tag))
return null;
// 제일 오래된 객체를 재활용
GameObject obj = PoolDictionary[tag].Dequeue();
PoolDictionary[tag].Enqueue(obj);
obj.SetActive(true);
return obj;
}
}
※ UnityEngine.Pool
- 유니티에서 지원하는 오브젝트풀 구조
- new ObjectPool<플될 클래스>(생성로직, 꺼낼때 로직, 반환로직,최대갯수 초과 로직, 자동검사, 초기크기, 최대크기)
public class PoolTest : MonoBehaviour
{
public GameObject prefab;
private ObjectPool<GameObject> pool;
private void Start()
{
pool = new ObjectPool<GameObject>(OnCreate, ActivePoolObject, DisablePoolObject, DestroyPoolObject, false, 10, 100);
}
//생성시 로직
private GameObject OnCreate()
{
return Instantiate(prefab);
}
//활성화시 로직
private void ActivePoolObject(GameObject poolObject)
{
poolObject.SetActive(true);
}
//비활성화시 로직
private void DisablePoolObject(GameObject poolObject)
{
poolObject.SetActive(false);
}
//최대갯수 초과시 로직
private void DestroyPoolObject(GameObject poolObject)
{
Destroy(poolObject);
}
}
- 이벤트로 이루어져있음
- 생성은 Func 으로, 활성화, 비활성화, 파괴는 Action 으로 이루어져있음
- 생성시 return 으로 생성할 클래스를 반환해야함
- 활성화, 비활성화, 파괴는 매개변수로 클래스를 받아서 활용하게됨
- 시작시 초기 크기만큼 오브젝트를 생성하며, 초기크기를 넘을경우 최대크기까지 오브젝트를 파괴하지 않고 비활성화함
- 최대크기를 넘을경우 추가로 추가로 오브젝트를 생성하며, 추가 생성된 오브젝트는 반환시 파괴된다.
주의사항
- 씬 로드시 오브젝트풀의 최소 생성갯수만큼 오브젝트를 생성해야함
- 로딩 시간이 길어질 수 있음
- 필요한만큼만 생성하는것이 좋음
전략패턴
- 원본클래스를 건드리지 않고, 다양하게 추가되는 방식에 대응하기 위해 사용
- 상황에 맞게 로직을 바꿔주는 방식
- 전략 : 로직들의 군을 정의하고, 필요에따라 로직군을 교체 가능하게함(코드들을 묶어서 사용)
구현
- 세부내용이 구현된 전략 인터페이스 정의
- 여러 방식을 전략인터페이스에서 사용 가능하도록 구현
상태패턴
- 여러 상태들의 실행되는 로직이 달라지는것을 체계적으로 관리하기 위해 사용
- 대표적인 상태패턴 : 애니메이터
유한상태기계 FSM
- 여러개의 상태들을 상태 기계로 묶어서 관리
- 하나의 현재 상태와, 현재상태에서 갈 수 있는 다른 상태을 연결
- 상태는 추상클래스를 상속받아서 구현
/// <summary>
/// Enemy 상태 부모 추상 클래스
/// </summary>
public abstract class State
{
protected StateMachine stateMachine; //현재 상태를 관리하는 상태머신
protected Enemy enemy; //상태머신을 가지고있는 Enemy
//생성자
public State() { }
//State를 세팅 : stateMachine, enemy
public void SetState(StateMachine _stateMachine, Enemy _enemy)
{
this.stateMachine = _stateMachine;
this.enemy = _enemy;
//상태 초기화
OnInitialize();
}
//상태 초기화 함수
public virtual void OnInitialize()
{
}
//상태 들어가기 (1회 호출)
public virtual void OnEnter()
{
}
public abstract void OnUpdate(float deltaTime); //추상메서드
//상태 나오기(1회 호출)
public virtual void OnExit()
{
}
}
/// <summary>
/// Enemy Ai 상태머신
/// </summary>
public sealed class StateMachine
{
private Enemy enemy; //상태머신을 가진 Enemy(부모 클래스)
private State m_CurrentState; //현재 상태
public State CurrentState => m_CurrentState;
private State m_PreviousState; //이전 상태
public State PreviousState => m_PreviousState;
private float m_ElapseTime = 0; //현재상태에 진행된 누적 시간 카운팅
public float ElapseTime => m_ElapseTime;
//상태를 저장하는 변수
private Dictionary<System.Type, State> states = new Dictionary<System.Type, State> ();
//생성자
public StateMachine(Enemy _enemy, State initialState)
{
enemy = _enemy; //상태머신을 가진 Enemy
//초기상태 등록, 초기화
RegisterState(initialState);
//현재 상태 처리
m_CurrentState = initialState;
m_CurrentState.OnEnter(); //상태 들어가기
m_ElapseTime = 0f; //누적시간 초기화
}
//상태를 등록하는 함수
public void RegisterState(State state)
{
//상태를 세팅
state.SetState(this, enemy); //상태를 등록하면서, 머신과 머신을 가진 Enemy 세팅
//상태를 등록
states[state.GetType()] = state;
}
//현재 상태를 업데이트
public void Update(float deltaTime)
{
m_ElapseTime += deltaTime; //현재 진행중인 상태의 누적 진행 시간
m_CurrentState.OnUpdate(deltaTime); //
}
//상태 변경
public State ChangeState(State newState)
{
//현재상태 체크
var newType = newState.GetType();
if(newType == m_CurrentState?.GetType()) //? => m_CurrnetState가 Null 일 경우 호출하지 않음
{
return m_CurrentState;
}
//현재상태에서 빠져나오기
if(m_CurrentState != null)
{
m_CurrentState.OnExit();
}
//현재상태를 새로운 상태로 세팅
m_PreviousState = m_CurrentState;
m_CurrentState = states[newType];
//상태 들어가기
m_CurrentState.OnEnter();
m_ElapseTime = 0f; //누적 시간 초기화
return m_CurrentState; //상태 변경
}
}
계층형 유한상태기계 HFSM
- 큰 개념의 상태와, 실제행동을 결정하는 세부상태로 구분하여 유한상태기계 구현
- 큰 개념은 애니메이터의 Sub - State Machine, 세부상태는 일반 State
'내일배움캠프 > TIL' 카테고리의 다른 글
2차 디자인패턴 특강 정리 - 이벤트기반 프로그래밍, 명령패턴 (0) | 2024.10.30 |
---|---|
유니티 숙련주차 개인프로젝트 마무리? (0) | 2024.10.29 |
내일배움캠프 28일차 TIL - 시야각 내의 오브젝트 판단하기 (0) | 2024.10.25 |
내일배움캠프 27일차 TIL - 3D 게임에서 지상 확인하기 (0) | 2024.10.24 |
내일배움캠프 26일차 TIL - 자료구조 특강 정리 (0) | 2024.10.23 |