[UE-5] Mediator 디자인 패턴

‍박성령·2025년 1월 25일

언리얼엔진

목록 보기
3/4
post-thumbnail

Mediator 패턴

개요

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

이 모든 것들, 적들이 서로 조율하고 누가 공격하고 누가 대기 중인지 추적하고 플레이어의 상태를 확인하는 등의 복잡한 구현을 의존성 없이 어떻게 구현할 수 있을까?

그 답은 바로 중재자 패턴(Mediator Pattern)이다.


Mediator 패턴

Mediator 패턴은 다음 질문에 답하기 위해 설계되었다.

게임 내 다양한 액테들이 서로 직접 참조나 의존 없이 어떻게 소통할 수 있을까?

초반에 든 예시를 다음과 같은 구조로 구현한다고 해보자.

많은 사람들이 범하는 실수인데 적과 플레이어, 그리고 적들끼리 직접 소통하게 만드는 방식의 구조이다.

이런식으로 구현하면 다음과 같은 문제가 발생한다.

  1. 강한 참조가 생겨 메모리 사용량이 증가한다.
  1. 게임 내 다양한 액터들 간의 의존성이 생긴다. 이는 각 액터가 서로 없으면 제대로 동작하지 못할 수 있다.

그렇다면 Mediator 패턴은 어떤 구조로 구현을 하는 것일까?

Mediator 패턴은 액터들 간의 직접적인 소통을 제거하고, 중재자를 통해서 소통하도록 한다.


이 방식은 각 액터가 단 하나의 중재자 클래스에만 의존하고, 다른 액터가 무엇을 하는지는 신경 쓰지 않는다.


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

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

대신 항공 교통 관제 타워를 통해 소통하고, 타워가 착륙 과정을 조율한다. 이것이 중재자 패턴의 사례이다.


실제 구현

문제 정의

  • 여러 적이 플레이어를 공격하려고 한다.
  • 한 번에 공격할 수 있는 적의 수를 제한해야 한다.
  • 공격 중인 적이 죽으면 대기 중인 적이 그 자리를 차지해야 한다.
  • 플레이어가 죽으면 모든 적이 원래의 위치로 돌아가야 한다.

이 모든 논리를 플레이어와 적 클래스에 넣는 대신, 전투 관리자(Combat Manager)를 중재자로 만들어 이 논리를 처리한다.


전투 관리자 구현

  1. 새 액터 생성
    • 새 액터를 만들고 이름을 Combat Manager로 설정합니다.
    • 이 액터를 월드에 배치합니다.
  1. 필요한 변수 추가
    • 공격 대상(Attack Target): 액터 참조(플레이어나 적 등).
    • 공격 중인 적 리스트(Attackers): 액터 배열.
    • 대기 중인 적 리스트(Waiting Attackers): 액터 배열.
  1. 인터페이스 생성
    • 공격자(Attacker) 인터페이스:
      Attack: 공격 시작.
      Wait: 대기.
      Retreat: 후퇴(플레이어 사망 시 호출).
    • 공격 대상(Attack Target) 인터페이스:
      GetMaxAttackersCount: 동시에 공격할 수 있는 최대 적 수를 반환.
  1. 중재자 통신 구현
    • 적이 전투에 참여하려 할 때, 전투 관리자에게 공격 가능 여부를 물어봅니다.
    • 전투 관리자는 현재 공격 중인 적 수를 확인한 뒤, 적에게 공격을 시작하라고 지시하거나 대기하도록 합니다.

자세한 구현 과정은 글 맨 아래 참고 영상을 확인하자.


Observer 패턴과 Mediator 패턴의 결합

Observer 패턴을 잘 모른다면 이 글을 참고하자. Observer 패턴이란?

문제점

저번 Observer 패턴을 설명할 때 들었던 예시에선 적 객체가 죽을 때마다 Ondeath 이벤트를 발행하고 이를 UI 위젯과 문에서 구독하여 이벤트를 처리했었다.

하지만 이 방식엔 여전히 문제가 있다.

  • 구독자(위젯과 문)는 특정 적 객체에 직접 바인딩해야한다.
  • 이는 구독자가 발행자(적)에 의존하게 되어, 레벨 전체에서 적 객체를 참조해야 했다.

이를 해결하기 위해 이벤트 관리자(Event Manager)를 도입한다.

이는 모든 이벤트 발행과 구독을 중앙에서 처리하는 역할을 하게 된다.


구현

이벤트 관리자 구현

  1. 커스텀 Game State Object 생성

    이벤트 관리자는 글로벌하게 접근 가능해야 하므로, 커스텀 Game State Object에 이벤트 로직을 구현한다.

  1. 이벤트 디스패처 추가

    Game State Object에 OnEnemyDeath 이벤트 디스패처 추가
    이제 적이 죽을 때마다 Game State Object를 통해 이벤트를 발행한다.

  1. 위젯 및 문 업데이트

    위젯과 문은 더 이상 적 객체를 직접 참조하지 않고, Game State Object의 OnEnemyDeath 이벤트에 구독한다.


적 카운트 제거

이전 방식에선 남아있는 적의 수를 구하기 위해 레벨의 모든 적 객체를 loop하며 확인해야했다.

이를 제거하기 위해, 새로운 OnEnemySpawned 이벤트를 Game State Object에 추가한다.

  • 적 객체가 생성될 때마다 OnEnemySpawned 이벤트를 발행하여 초기 적 카운트를 설정한다.
  • 구독자는 더 이상 적 객체를 참조하지 않고도 동적으로 카운트를 업데이트할 수 있다.

결론

Mediator 패턴의 장점

중재자 패턴은 다음과 같은 이점을 제공한다.

1. 의존성 제거: 객체 간의 직접 참조를 없애고, 코드의 결합도를 줄인다.

2. 확장성과 유지 보수성 향상: 새로운 기능 추가나 변경 작업이 훨씬 쉬워진다.

3. 독립성: 각 객체는 자신의 역할에만 집중하며, 서로의 내부 구현에 영향을 받지 않는다.

Observer 패턴과 Mediator 패턴의 조합

위에서 확인했듯이 이 둘을 조합하여 사용하면 객체간의 의존성을 완전히 제거 할 수 있다.

이를 통해 코드의 재사용성과 가독성이 대폭 향상된다.

참고 영상

profile
게임 개발을 좋아하는 개발자입니다.

0개의 댓글