World War Z와 같은 캐릭터 클래스 중심의 좀비 게임을 개발할 때 Lyra를 참고해 설계해 보려고 한다.
Lyra의 게임 장르 변경을 위한 Experience 시스템을 캐릭터 클래스 변경 중심의 시스템으로 재설계 했다.
좀비 게임이라는 장르로 고정했을 때 Lyra의 Experience 는 필요하지 않다.
그래서 CharacterDefinition 으로 바꾸고 런타임에 캐릭터 클래스를 변경할 수 있게 하는게 좋아보인다.
기존 Lyra 시스템과의 차이점
Lyra는 하나의 프로젝트에서 여러 게임 장르를 구현할 수 있도록 설계되었다. 예를 들어, 일반적인 FPS 슈터와 탑다운 뷰 게임을 Experience라는 단위로 전환할 수 있다. 하지만 장르가 고정되어 있으며, 대신 플레이어가 선택할 수 있는 다양한 캐릭터 클래스가 존재하도록 했다.
따라서 Experience → CharacterDefinition으로, 게임 장르 변경 → 캐릭터 클래스 변경
으로 개념을 전환했다.
폴더 구조
Source 폴더 (핵심 시스템)
Source 폴더에는 게임의 뼈대가 되는 핵심 시스템들을 배치한다. 이곳에는 모든 시스템이 의존하는 기본 클래스들과 게임 없이는 작동할 수 없는 필수 요소들을 포함하면 된다. 플레이어어가 캐릭터 클래스를 선택하기 전에 기본으로 적용되는 DefaultCharacterDefinition 도 포함된다.
Plugins 폴더 (독립 기능 모듈)
Plugins 폴더에는 독립적으로 개발하고 테스트할 수 있는 기능들을 배치한다. 각각 켜고 끌 수 있는 모듈형 기능들로, 예를 들어 워킹 데드의 Carl이나 Rick 같은 특정 캐릭터의 CharacterDefinition들이 여기에 들어간다 (워킹 데드를 좋아해서 등장인물로 이름을 선정해봤다). 이렇게 분리함으로써 팀 단위 개발 시 충돌을 최소화하고, 필요에 따라 특정 캐릭터 기능을 쉽게 추가하거나 제거할 수 있다.
결국 테스트에서 가장 많은 시간을 보내게 될 텐데, 이 때 모듈 단위로 로딩, 언로딩할 수 있도록 해서 직관적이며 협업에서도 모듈 단위로 분담할 수 있고, Slack 에서도 모듈 이름으로 소통해 직관적이다. 당연히 버젼 관리도 좋을 것 같다.
단점
계속 장점만 이야기 했지만 단점도 존재한다.
- 초기 설정 비용이 정말 많이 든다. 간단한 프로토타입을 만들고 싶어도 모든 모듈 구조와 의존성을 먼저 설계해야 하므로, 개발 초기 단계에서 오버헤드가 크다고 느낀다. Lyra의 Experience 관련 매니저, 컴포넌트만 봐도 복잡하고 클래스들이 많다는 걸 알 수 있다.
- Lyra 는 테크 데모지, 실제 프로젝트에 적용하기에는 오버 엔지니어링이라고 생각한다.
- 다음은 의존성 문제이다. 모듈끼리 최대한 커플링을 피하는 것이 옳지만, 개발하다보면 시간에 쫓기고 마감이 닥치면 실수하게 된다. 그 이후는… 유지 보수가 점점 어려워 진다.
CharacterDefinition
이는 크게 2가지로 구성된다.
- 플레이어의 캐릭터 정보
- 플레이어에게 적용될 게임 시스템
- 플레이어 or 캐릭터에게 적용되는 기본 시스템
- 해당 플레이어 or 캐릭터 만 적용되는 시스템
DefaultCharacterDefinition
* Default Pawn Data
* Pawn Class
* InputConfig
* InputAction 과 GamePlayTag 바인딩
* AbilitySet
* GamePlayAbility 와 GamePlayTag(InputTag) 바인딩
* GrantedGamePlayEffect
* GrantedGamePlayAttributes
* Default Game Feature (이 캐릭터가 로드할 기능들)
* *전체 게임 시스템 ex)*
* WeaponSystem (무기 관리 전체)
* InventorySystem (인벤토리 UI + 로직)
* QuestSystem (퀘스트 UI + 데이터)
* ShopSystem (상점 기능 전체)
예시
CarlCharacterDefinition
* PawnData ├── Pawn Class: BP_Carl
├── Ability Set: AbilitySet_Carl (기본 능력들)
│ ├── GA_Move (이동) → InputTag.Move
│ ├── GA_Jump (점프) → InputTag.Jump
│ ├── GA_Death (죽음 처리)
│ ├── GA_WeaponFire (무기 발사) → InputTag.WeaponFire
│ ├── GE_IsPlayer
│ ├── CarlAttributesSet
└── Input Config: InputData_Carl
├── WASD → InputTag.Move
├── Mouse → InputTag.Weapon.Fire
├── Space → InputTag.Ability.Jump
├── 1~9 → InputTag.Weapon.Select
└── R → InputTag.Weapon.Reload
* Game Feature Actions
├── Add Component: SaveGameComponent (세이브/로드)
├── Add Component: ZombieModeComponent(기본좀비모드)
├── Add Component: DialogueComponent (컷신/대화)
├── Add Ability: GA_EagleEye (Carl 전용 Ability)
├── Add Ability: GA_SuperJump (Carl 전용 Ability)
├── Add UI: Widget_SinglePlayerHUD
Ability Set 와 Game Feature 차이는
- AbilitySet은 "개인 능력"
- Game Feature 는 “시스템/기능 모듈"
- 런타임에 캐릭터 변경이 쉬워진다.
- 모듈화의 장점을 포함한다.
- 코드를 수정하지 않고도 데이터 에셋만으로 캐릭터의 특성을 조정할 수 있다
결국 유지 보수와 업데이트가 쉬워질 수 있다는 것이다.
언리얼에서 모듈화 기능의 핵심인 Game Feature 를 사용하는 이유는 다음과 같다.- 메모리 효율성. Carl을 플레이할 때는 Rick 관련 기능들이 메모리에 로드되지 않는다. 불필요한 시스템들을 메모리에서 제외할 수 있다.
- 똑같은 얘기지만 각 기능별로 독립적으로 개발하고 테스트할 수 있어서, 팀의 여러 개발자가 동시에 작업할 때 충돌이 줄어든다. Carl 전담 개발자는 Carl 관련 시스템만, Rick 전담 개발자는 Rick 관련 시스템만 개발하는 식이 가능하다.