경일 메타버스 20220719 16주차 2일 수업내용. 옵저버 패턴, 유니티 - 유니런 : 오브젝트 풀링, 이벤트
어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지 받아 자동으로 갱신될 수 있게 만드는 패턴
폴링(Polling) :
객체의 변화를 감지하고 싶을 때 ⇒ 주기적으로 객체에 변화가 이뤄졌는지 검사한다.
한 객체가 다른 객체에 종속적일 때
한 객체의 상태의 변경으로 다른 객체의 상태를 변경해야 하고, 프로그래머가 변경해야 하는 객체 수를 몰라도 될 때
어떤 객체가 다른 객체에 자신의 변화를 통지할 수 있는데, 그 변화에 관심 있어 하는(관련이 있는) 객체가 무엇인지에 대한 가정이 없어도 될 때
Subject :
변화가 이뤄지는 주체
Observer :
변화를 관찰하는 관찰자 객체
Push Model :
변화된 상태를 Subject가 보내주는 구조
Pull Model :
Observer가 상태를 꺼내는 구조
핵심은 변화가 이뤄질 때, Subject의 변화를 관찰하고 있는 대상들에게 통지 해주는 것
함수형(Functional)을 이용하여 구현되기도 한다. Ex) C#의 이벤트(UnityEvent)
Subject와 Observer가 강하게 결합되지 않는다.
브로드캐스트(Broadcast)가 가능하다.
예측하지 못한 데이터를 갱신할 수 있다.
무효 참조(Dangling Reference)가 일어날 수 있다.
Subject로부터 통지를 받으려면 등록을, 더 이상 받지 않으려면 해제를 해야 한다.
여기서 사라진 객체의 해제를 잊었을 때, 통지를 하면서 이미 사라져 무효한 객체에 대해 참조를 하는 무효 참조 이슈가 발생할 수 있다.
상태의 자체 일관성(Self-Consistency)이 깨질 수 있다.
서브 클래스가 슈퍼 클래스의 연산을 사용하는 과정에서 통지가 일어날 수 있다.|
즉, 통지가 두 번 이상 일어날 수 있고, 사용하는 데이터에 손상이 갈 수 있다.
이에 대한 주의가 필요하다는 의미.
통지에 따른 갱신이 오래 걸릴 수 있다.
통지하는 시점이 병목이 될 수 있다.
이를 완화하기 위해 비동기 방식을 사용하거나,
큐잉(Queueing)을 이용할 수 있다.
- 식 본문 (Expression Body)
구문이 하나인 경우 아래와 같이 쓸 수 있다.
int Add(int a, int b = 10) => a + b;
반복된 메모리의 할당과 해제는 자원을 많이 사용한다.
이러한 할당과 해제의 반복을 피하고 최적화 하기 위해,
사용하고자 하는 오브젝트를 미리 생성하고 디액티브 / 액티브하여 사용한다.
// 1. 배열
GameObject[] _objectPool = new GameObject[10];
// 각 원소는 적절하게 초기화 되었다고 가정
for (int i = 0; i < _objectPool.Length; ++i)
{
if (_objectPool[i].active == false)
{
_objectPool[i].SetActive(true);
}
return;
}
// 2. Stack, Queue 등 자료구조 활용
Stack<GameObject> _objectPool = new Stack<>();
void Awake()
{
for (int i = 0; i < 10; ++i)
{
_objectPool.Push(new GameObject("object" + i));
}
}
...
유니티 2020부터 이 기능을 지원한다.
ObjectPool
1. 선언
public UnityEvent<int> OnScoreChanged = new UnityEvent<int>(); // 객체 필요
public event UnityAction<int> OnScoreChanged2;
2. 통지
OnScoreChaned.Invoke(_currentScore);
OnScoreChaned2?.Invoke(_currentScore); // Null 체크
3. 구독과 구독 해제
GameManager.Instance.OnScoreChanged.AddListener(함수명); // 구독
GameManager.Instance.OnScoreChanged.RemoveListener(함수명); // 구독 해제
GameManager.Instance.OnScoreChanged2 += 함수명; // 구독
GameManager.Instance.OnScoreChanged2 -= 함수명; // 구독 해제
// 단, 함수명 => 함수포인터
이벤트 선언
유니티 제공 이벤트는 객체를 만들어야 하는 반면,
C# 이벤트는 만들지 않아도 된다.
.Invoke() 사용 시
유니티 제공 이벤트는 null체크를 안 하지만,
C# 이벤트는 해야 한다.
null ⇒ 구독자가 있는가
구독과 구독 해제
유니티 제공 이벤트는 메소드로,
C# 이벤트는 연산자로 한다.
주의
유니티 제공 이벤트는 외부에서 Invoke()를 호출할 수 있고
C# 이벤트는 해당 변수를 선언한 클래스 내부에서만 Invoke()를 호출할 수 있다.
유니티 에디터
유니티 제공 이벤트는 보이고
C# 이벤트는 안 보인다.