
위 그림과 같이 맵에 적이 있고 UI로 남아있는 적의 수를 표현해주는 게임이 있다고 가정해보자.
플레이어가 적들을 한명씩 무찌를 때마다 UI에 남아있는 적의 수가 줄어들고 전부 줄어들 시 문이 열리는 것을 구현해야한다.
즉, 모든 객체 (UI, 적, 문, 레벨, 플레이어)는 서로 소통해야한다.
이를 어떻게 구현할 수 있을까?
위와 같은 문제를 해결해야 할 때 흔히 저지르는 실수가 적 객체의 Death 함수에서 UI를 참조하여 적의 숫자를 변경하고 문 객체를 참조하여 문을 여는 방식을 사용한다.
듣기엔 문제가 없어 보이나 이 방식엔 큰 문제가 있다. 바로 객체 간 결합(coupling), 즉 의존성을 만들어낸다.
이 coupling이 어떤 문제가 있을까?
- 적 클래스에 UI 관련 로직이 들어가기 때문에 추후 UI를 변경하고 싶으면 적 클래스를 수정해야 하고, 그 반대의 경우도 성립한다.
- 이런 coupling을 계속 만들면, 단순한 변경이 게임 코드의 큰 부분을 다시 작성해야하는 상황까지 가게 된다.
정리하자면 즉, 코드가 금방 복잡해지고 유지 보수가 어려워진다.
Observer 패턴은 위 방식을 해결할 수 있는 방안이다.
이는 구독-알림 모델로 특정 이벤트가 발생하면 신호를 보내고 게임 내 다른 객체가 이를 수신하여 처리하는 구조이다.
Unreal Engine에서는 이를 (블루프린트 기준) 이벤트 디스패처를 정의하여 구현한다. (C++는 델리게이트)
먼저 적 객체가 사망할 시 게임 내 다른 객체에게 신호를 보내도록 적 객체를 발행자로 만든다.
Ondeath 라는 이름의 이벤트 디스패처를 적 블루프린트에 추가한다.
위젯 블루프린트에서 적 객체의 Ondeath 이벤트를 구독하여 이와 관련된 이벤트를 정의해준다.
적이 전부 사망할 시 문이 열리도록 구현해야하기 때문에 문 객체도 Ondeath 이벤트를 구독하여 이와 관련된 이벤트를 정의해준다.
위에서 구현했듯이 문 관련된 이벤트는 문 블루프린트에서 작업하고 UI 관련된 이벤트는 위젯 블루프린트에서 작업한다. 초반에 본 것처럼 각 객체가 서로 얽혀있지 않아서 유지 보수도 쉽고 기능 확장도 쉬워진다.
만약 추후 적이 줄어들 때마다 게임의 노래를 변경하고 싶으면 그냥 단순히 그와 관련된 객체를 정의하고 이를 구독자로 만들어주면 된다.
저번 SoulShard 게임을 제작할 땐 이 내용을 몰라서 각 객체가 너무 얽혀있었다. 그때는 그냥 계속 그 게임만 개발 하니까 어떤 기능이 어디 객체에있고 뭘 수정해야하는지 대략적으로 알았지만 지금 와서 기능을 수정하라고 하면 절대 못 할 것 같다.
본인에겐 단순히 디자인 패턴 한 가지를 배운 것이 아니라 저번 게임을 개발할 때 실제로 문제 되는 상황을 직면하고 이에 대한 해결법을 배운 것이라 더 크게 와닿는 것 같다.