개요
언리얼 엔진 5의 GameFeature Plugin 시스템을 활용하면, 게임의 핵심 로직을 건드리지 않고도 새로운 컨텐츠를 런타임에 추가하거나 제거할 수 있다. 이 글에서는 Data Registry와 결합하여 완전히 모듈화된 상점 및 DLC 시스템을 구축한 사례를 다룬다.1. 요약
핵심은 코어 게임 로직과 콘텐츠를 완전히 분리하고, Data Registry Source만으로 확장하는 것.
1. ItemShop 게임 피처 플러그인 (상점 시스템)
Add Data Registry:DR_ShopItems레지스트리 생성 및 기본 아이템 데이터 등록Add Widgets: 상점 UI 활성화 (커스텀 GameFeatureAction)- 프로젝트 코어에서 상점 시스템을 제거하고 플러그인으로 관리 가능
2. DLC_SpecialEdition 게임 피처 플러그인 (DLC 콘텐츠)
Add Data Registry Source: 기존DR_ShopItems에 DLC 아이템 데이터 소스 추가- 추가 콘텐츠를 완전히 독립적으로 관리
2. 동작 흐름
1. 기본 상점
1. ItemShop 게임 피처 플러그인 활성화
- DR_ShopItems 레지스트리 추가 (DT_Default_Consumable 데이터 테이블이 등록되있음)
- Add Widgets 액션(커스텀 액션) 실행
2. 상점 UI 표시
UDataRegistrySubsystem을 이용해 ShopItems 레지스트리 모든 아이템 조회- 조회된 아이템 데이터로 UI 초기화
- 기본 소비 아이템들이 상점에 표시됨
2. DLC 추가
1. DLC_SpecialEdition 게임 피처 플러그인 활성화
- 기존 ShopItems Data Registry에 Data Registry Source로 DT_DLC_Consumable 추가
- 코어 시스템 수정 없이 순수하게 데이터만 추가
2. 자동으로 상점의 콘텐츠 확장
- ShopItems 레지스트리가 자동으로 DLC 아이템 포함
- 상점 UI 오픈 시 기본 아이템 + DLC 아이템 모두 표시
- 런타임 소스 확인 시 데이터 테이블이 추가된 것을 확인 가능
3. 핵심 메커니즘
0. Add Data Registry Source 는 대상 레지스트리에 Meta Source 가 필요
Meta Source 가 필요한 이유 (언리얼 공식 문서 참고)
- 런타임에 다른 데이터 소스를 생성하고 소유
- 모든 데이터 소스에 규칙을 적용하여 해당되는 에셋을 찾습니다
- Meta Source가 발견한 데이터 소스는 런타임에 데이터 레지스트리에 로드됨
먼저 데이터 레지스트리 소스를 등록하고 후에 레지스트리를 등록해도
Meta Source 가 찾아서 로드할 수 있기 때문에 문제가 없다!
DataTable 을 DataRegistry에 등록하기 위해 DataSource 로 생성해 런타임에 로드해줌
런타임에 등록할 데이터 소스들을 관리해주는 역할!
1. 게임 피처에 Data Registry 가 등록되는 타이밍은?
기본 GameFeatureAction_Add DataRegistry 은 Registered 단계에서 이미 게임에 등록된다.
그래서 Activate 단계에서 데이터에 접근해도 문제가 생기지 않는다.
Installed ->Registered → Loaded → Activated
→
Data Registry 등록 완료
1-1. DLC 게임 피처는 Installed 상태로 시작하는 것이 좋음.
Registered 단계에서 데이터 레지스트리나 소스가 이미 엔진에 등록되어 검색과 데이터 접근이
가능한 상태이기 때문에, 원치 않게 DLC 콘텐츠가 바로 노출될 수 있음.
1-2. 게임 피처를 DeActivate 해도 데이터 레지스트리에 접근 가능한가?
Registered, Loaded 상태에서도 데이터 레지스트리가 검색과 접근이 가능하다고 했었다.
그럼 게임 피처를 비활성화해도 똑같다면, 비활성화하는 의미가 없는 것 아닐까?
아래 코드를 보자
메모리에는 남아 있지만, Deactivating 시점에 해당 레지스트리 경로를 검색하지 못하게 한다.
void UGameFeatureAction_DataRegistry::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) { Super::OnGameFeatureDeactivating(Context); if (FoundRegistry) // false { // 실행 안됨 } ↓ return false // 실패 ↓ UE_LOG(LogGameFeatures, Log, TEXT("failed to unregister")) ```
결과:
- UnregisterSpecificAsset 실패
- 로그 출력: "DT_DLC_Consumable failed to unregister"
구체적인 코드는 해당 언리얼 소스파일을 확인하면 된다.
하지만, 추가한 데이터 레지스트리 소스도 DeInitialize 가 되므로, 플레이에서 문제는 발생하지 않는다.
(단지 "사용 불가" 상태 마킹)
void UDataRegistrySource::Deinitialize() { if (IsInitialized() && GetRegistry()) { bIsInitialized = false; } }
최종 결론 :
DLC 게임 피처가 Loaded 상태이기 때문에 DT_DLC_Consumable 은 메모리에 로드된 상태이며,
UDataRegistrySubsystem 을 통한 접근은 차단 되었다. (DR_ShopItems 를 통해 접근하지 못하는 상태가 된다.)
물론 DT_DLC_Consumable 은 직접 참조는 가능하다.
(DataRegistrySource 로써 DeInitialize 된 것뿐이다)
3. 비동기 아이템 로딩
void UBSItemShopSystem::LoadShopItems(FGameplayTagContainer FilterTags, const FDataRegistryItemAcquiredCallback& InCallback) { // 모든 아이템 ID 가져오기 TArray<FDataRegistryId> AllItemIds; DRSubsystem->GetPossibleDataRegistryIdList(FName("ShopItems"), AllItemIds); // 비동기로 각 아이템 로드 for (const FDataRegistryId& ItemId : AllItemIds) { DRSubsystem->AcquireItem(ItemId, FDataRegistryItemAcquiredCallback::CreateLambda([...](...) { // 필터링 및 캐싱 if (FilterTags.IsEmpty() || ItemData->ItemTag.HasAny(FilterTags)) { CachedItemIds.Add(AssetId); } }) ); } }
4. UI 갱신 흐름
// 1. UI 생성 시 아이템 로드 요청 ShopSubSystem->LoadShopItems(FilterTags, Callback); // 2. 모든 아이템 로드 완료 후 UI 구성 void UBSShopWidget::OnShopItemsLoaded(const FDataRegistryAcquireResult& Result) { for (const auto& ItemData : ShopSubSystem->GetAllItems()) { // ShopItemWidget 생성 및 추가 ItemWidget->SetItemData(ItemData); ItemListScrollBox->AddChild(ItemWidget); } }
4. 장단점
모듈형 게임 플레이로써의 장점
- 완전히 선택적으로 기능을 넣을 수 있다.
- 독립적으로 기능들을 개발할 수 있다.
- 게임은 피처에 의존하지 않아, 홀로 실행이 가능하다.
현재 [Core Game] - 최소한의 기능만 [ItemShop Plugin] - 상점 전체 시스템 [DLC Plugin] - 현재는 데이터만 추가
협업에서의 장점
팀에서 개발하면서 팀원이 개발한 기능이 문제가 생겨 고쳐달라고 하는 요청하는 상황이 생기더라..
이런 문제들을 정말 쉽게 해결할 수 있다.
- ItemShop 팀 완전 독립
- Core 팀은 ItemShop 모름
- ItemShop 없이도 게임 실행 됨.
테스트에서의 장점
- Feature별 테스트 가능
- 특정 Feature만 활성화하여 디버깅
- QA 팀에서도 테스트가 용이
단점
- 알아야 할 초기 학습 비용 크고, 구조 설계에 시간이 많이 든다…
- 게임 피처부터 시작해서, 피처 액션, 데이터 레지스트리, 소스, 데이터 에셋 등등
- 초기 학습 비용과 팀원들이 모두 활용할 수 있는 경우에는 정말 좋은 기능이라고 생각된다
- 버전 관리에서도 문제를 일으킬 일이 크게 없을 가능성이 크다.
- 생각보다 이쪽 관련 학습 문서가 없다
- 처음부터 Lyra 를 분석하는 것이 제일 도움이 된다.
- Lyra 가 단순히 기능 시연용을 목적으로 만든 게 아니고, 이렇게 해야 되는구나라는게 분석하면서 느껴진다.
- 데이터 레지스트리 비활성화 문제
- 위에서 설명했던 데이터 레지스트리 비활성화 순서 문제 같은 경우
- 언리얼에서 이 쪽 문제를 이미 테스트하고, 정리를 잘하는 같긴 하다.
5. 후기
처음에는 "DLC DataTable 1개 추가하는 건데 이렇게까지 해야 하나?" 싶었다. GameFeature Plugin, Data Registry, Meta Source 는 뭐고 초기 설정도 복잡했다.
하지만 막상 구축하고 나니, 재미있었고 되게 효과적이었다. 코드 한 줄 없이 데이터를 추가하고 다시 빼는 것도 가능했다.
중~대규모 팀에서 장기적인 프로젝트에 DLC 까지 계획중이라면 안 쓸 이유가 없는 구조인 것 같다 초기 비용이 좀 들더라도 이전 프로젝트에서 활용했으면 좋았을텐데 하는 아쉬움이 남는다.
그리고 데이터 레지스트리 관련 문제를 좀 디깅하면서 이 쪽 코드가 참 복잡했다. 타고 타고 타고 들어가는 언리얼 디버깅...