
위 그림과 같이 적들이 플레이어를 공격하는데 한 번에 공격할 수 있는 적의 수를 제한하고 싶고 또 아래와 같이 공격자가 죽으면 대기 중인 적 중 하나가 그 자리를 채우는 시스템을 구현하려한다. 또한 플레이어가 죽으면 모든 적이 원래의 위치로 돌아가야 한다.


이 모든 것들, 적들이 서로 조율하고 누가 공격하고 누가 대기 중인지 추적하고 플레이어의 상태를 확인하는 등의 복잡한 구현을 의존성 없이 어떻게 구현할 수 있을까?
그 답은 바로 중재자 패턴(Mediator Pattern)이다.
Mediator 패턴은 다음 질문에 답하기 위해 설계되었다.
게임 내 다양한 액테들이 서로 직접 참조나 의존 없이 어떻게 소통할 수 있을까?
초반에 든 예시를 다음과 같은 구조로 구현한다고 해보자.

많은 사람들이 범하는 실수인데 적과 플레이어, 그리고 적들끼리 직접 소통하게 만드는 방식의 구조이다.
이런식으로 구현하면 다음과 같은 문제가 발생한다.
- 강한 참조가 생겨 메모리 사용량이 증가한다.
- 게임 내 다양한 액터들 간의 의존성이 생긴다. 이는 각 액터가 서로 없으면 제대로 동작하지 못할 수 있다.
그렇다면 Mediator 패턴은 어떤 구조로 구현을 하는 것일까?
Mediator 패턴은 액터들 간의 직접적인 소통을 제거하고, 중재자를 통해서 소통하도록 한다.

실제 세계에서 이 패턴을 활용하는 사례로는 항공 교통 관제 시스템이 있다.

위와 같이 비행기가 공항에 착륙하려 할 때, 서로 소통하며 활주로를 배분하지는 않는다.

대신 항공 교통 관제 타워를 통해 소통하고, 타워가 착륙 과정을 조율한다. 이것이 중재자 패턴의 사례이다.
문제 정의
- 여러 적이 플레이어를 공격하려고 한다.
- 한 번에 공격할 수 있는 적의 수를 제한해야 한다.
- 공격 중인 적이 죽으면 대기 중인 적이 그 자리를 차지해야 한다.
- 플레이어가 죽으면 모든 적이 원래의 위치로 돌아가야 한다.
이 모든 논리를 플레이어와 적 클래스에 넣는 대신, 전투 관리자(Combat Manager)를 중재자로 만들어 이 논리를 처리한다.
전투 관리자 구현
- 새 액터 생성
- 새 액터를 만들고 이름을 Combat Manager로 설정합니다.
- 이 액터를 월드에 배치합니다.
- 필요한 변수 추가
- 공격 대상(Attack Target): 액터 참조(플레이어나 적 등).
- 공격 중인 적 리스트(Attackers): 액터 배열.
- 대기 중인 적 리스트(Waiting Attackers): 액터 배열.
- 인터페이스 생성
- 공격자(Attacker) 인터페이스:
Attack: 공격 시작.
Wait: 대기.
Retreat: 후퇴(플레이어 사망 시 호출).- 공격 대상(Attack Target) 인터페이스:
GetMaxAttackersCount: 동시에 공격할 수 있는 최대 적 수를 반환.
- 중재자 통신 구현
- 적이 전투에 참여하려 할 때, 전투 관리자에게 공격 가능 여부를 물어봅니다.
- 전투 관리자는 현재 공격 중인 적 수를 확인한 뒤, 적에게 공격을 시작하라고 지시하거나 대기하도록 합니다.
자세한 구현 과정은 글 맨 아래 참고 영상을 확인하자.
Observer 패턴을 잘 모른다면 이 글을 참고하자. Observer 패턴이란?
저번 Observer 패턴을 설명할 때 들었던 예시에선 적 객체가 죽을 때마다 Ondeath 이벤트를 발행하고 이를 UI 위젯과 문에서 구독하여 이벤트를 처리했었다.
하지만 이 방식엔 여전히 문제가 있다.
- 구독자(위젯과 문)는 특정 적 객체에 직접 바인딩해야한다.
- 이는 구독자가 발행자(적)에 의존하게 되어, 레벨 전체에서 적 객체를 참조해야 했다.
이를 해결하기 위해 이벤트 관리자(Event Manager)를 도입한다.
이는 모든 이벤트 발행과 구독을 중앙에서 처리하는 역할을 하게 된다.
이벤트 관리자 구현
- 커스텀 Game State Object 생성
이벤트 관리자는 글로벌하게 접근 가능해야 하므로, 커스텀 Game State Object에 이벤트 로직을 구현한다.
- 이벤트 디스패처 추가
Game State Object에
OnEnemyDeath이벤트 디스패처 추가
이제 적이 죽을 때마다 Game State Object를 통해 이벤트를 발행한다.
- 위젯 및 문 업데이트
위젯과 문은 더 이상 적 객체를 직접 참조하지 않고, Game State Object의
OnEnemyDeath이벤트에 구독한다.
적 카운트 제거
이전 방식에선 남아있는 적의 수를 구하기 위해 레벨의 모든 적 객체를 loop하며 확인해야했다.
이를 제거하기 위해, 새로운 OnEnemySpawned 이벤트를 Game State Object에 추가한다.
- 적 객체가 생성될 때마다
OnEnemySpawned이벤트를 발행하여 초기 적 카운트를 설정한다.
- 구독자는 더 이상 적 객체를 참조하지 않고도 동적으로 카운트를 업데이트할 수 있다.
중재자 패턴은 다음과 같은 이점을 제공한다.
1. 의존성 제거: 객체 간의 직접 참조를 없애고, 코드의 결합도를 줄인다.
2. 확장성과 유지 보수성 향상: 새로운 기능 추가나 변경 작업이 훨씬 쉬워진다.
3. 독립성: 각 객체는 자신의 역할에만 집중하며, 서로의 내부 구현에 영향을 받지 않는다.
위에서 확인했듯이 이 둘을 조합하여 사용하면 객체간의 의존성을 완전히 제거 할 수 있다.
이를 통해 코드의 재사용성과 가독성이 대폭 향상된다.