FloatingDamage 구조 문제
🧩 왜 FloatingDamage를 GameInstanceSubsystem에서 처리했는가
처음에는 Floating Damage를 일반적인 UI처럼 처리하려고 했다. FloatingDamageManager라는 매니저 클래스를 만들고, 이를 HUD에서 관리하도록 했다.
🚧 구조적 한계
적 캐릭터가 데미지를 받을 때, FloatingDamageManager를 통해 화면에 데미지를 띄워야 한다. 그런데 FloatingDamageManager는 플레이어의 HUD에 종속되어 있어서, 적 캐릭터의 BeginPlay()
에서는 직접 접근이 불가능하다.
결국 EnemyCharacter에서 아래와 같은 경로로 접근해야 한다.
EnemyCharacter
└── GetWorld()->GetFirstPlayerController()
└── GetHUD()
└── GetFloatingDamageManager()
이 방식은 접근 경로가 지나치게 복잡하고, 적 캐릭터가 플레이어 컨트롤러나 HUD에 의존해야 한다는 점에서 구조적으로 부적절하다.
📡 멀티플레이 환경 고려
현재는 싱글 플레이 환경이라 직접적인 문제는 없지만, 멀티플레이 게임으로 확장할 경우 PlayerController와 HUD가 여러 개 존재하게 된다.
그 상황에서 모든 적 캐릭터가 각 플레이어의 HUD를 탐색하며 FloatingDamageManager를 호출하는 구조는 말이 되지 않는다. 객체 간 참조 관계도 복잡해지고, 게임 전반의 유지보수에도 부정적인 영향을 준다.
✅ GameInstanceSubsystem으로 대체
Floating Damage는 단순한 UMG 위젯이 아닌, WidgetComponent를 가진 액터로 따로 만들었다. 그리고 이 액터는 URMFloatingDamageSubsystem
이라는 GameInstanceSubsystem에서 관리하도록 변경했다.
이렇게 설계한 이유는 다음과 같다:
- Subsystem은 전역 시스템이기 때문에, 적 캐릭터든 플레이어든 어디서든 접근이 가능하다.
- Floating Damage는 특정 플레이어나 HUD에 귀속된 기능이 아니며, 게임 전역에서 통합적으로 관리되어야 할 시각적 연출이다.
- 멀티플레이에서도 구조적인 일관성을 유지할 수 있다.
URMFloatingDamageSubsystem* FloatingSubsystem = GetGameInstance()->GetSubsystem<URMFloatingDamageSubsystem>();
🏗️ 구조 비교
기존 방식 | 최종 방식 |
---|---|
PlayerController → HUD → FloatingDamageManager → Widget (우회적 접근 필요) |
Enemy → GameInstanceSubsystem 직접 접근 → Floating Damage Actor 생성 및 표시 |
📌 정리
처음에는 데미지를 띄우는 기능이니까 HUD에 넣는 것이 자연스럽다고 판단했다. 하지만 실제로 구현하려다 보니 참조 흐름이 복잡해졌고, 멀티플레이 환경까지 고려하면 이 구조는 한계가 분명했다.
결국 Floating Damage를 독립된 액터로 분리하고, 전역에서 관리할 수 있도록 GameInstanceSubsystem을 활용했다.