언리얼 버전 업데이트에 따라 GameMode는 점점 무거워졌고 그것을 대체할 수 있는 것이 Experience다.
UserFacingExperience는 프라이머리 에셋으로 Experience의 단위라고 볼 수 있다.
FPS모드가 있고 AOS모드가 있다면 모드별로 UserFacingExperience로 정의한다.
UserFacingExperience는 Map과 ExperienceDefinition으로 이루어져 있고
ExperienceDefinition안에는 PawnData가 있다.
PawnData는 어떤 Pawn을 생성하는지, 입력을 어떻게 하는지, 어떤 능력을 사용 가능한지 정보를 갖는다.
Character, PlayerController, PlayerState, GameState 클래스를 만들고 이것들은 바뀌거나 추가되지 않는다.
몬스터가 있다고 EnemyCharacter 클래스가 추가되거나 하지 않는다는 말이다.
새로운 몬스터를 만드려면 클래스를 추가하지 않고 클래스에 붙이는 부품들을 변경하는 식으로 만든다.
GameState: GameMode와 1대1 대응
ExperienceManagerComponent가 Experience 로딩을 담당한다.
PlayerState: 안에 PawnData를 캐싱
PawnData는 PawnClass, InputConfig, CameraMode를 포함
GameMode
핵심함수
HandleAssignmentIfNotExpectingOne()
ExperienceManager에게 Experience를 로딩하도록 던진다
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne);
GameMode::InitGame에서 이 함수를 한 프레임 뒤에 실행하는데 이유는
GameMode초기화가 ExperienceManager초기화보다 빨리 일어나기 때문이다.
ExperienceManagerComponent
ExperienceManagerComponent에는 CurrentExperience로 현재 경험을 갖고 있는데 갖고 있다고 해서
항상 로딩이 완료된 것은 아니다. 그래서 ExperienceLoadState로 로딩 상태를 판별한다.
또 Delegate를 가지는데 로딩이 완료된 후에 완료된 ExperienceDefinition을 BroadCast한다.
GameMode에서 CallOrRegister_OnExperienceLoaded함수를 통해 로딩이 완료되면
OnExperienceLoaded 함수가 실행된다.
엔진 초기화 시에 InitNewPlayer 등 플레이어 초기화를 하는데 이 때 Experience 로드 전이므로
로드 이후 RestartPlayer로 플레이어 초기화 함수를 재실행해야 한다. 로드 전에는 플레이어를 스폰하는 로직의 시작 함수
HandleStartingNewPlayer_Implementation을 Experience가 로드되고 나서 실행하도록 막아놓는다.
PlayerState에는 PawnData를 캐싱해놓는데 AbilitySystemComponent에서 PawnData를 참조하기 위함이다.
이제 로딩을 실제로 시작하는 부분이다. 로딩을 할 때 기본적으로 동기 로딩을 하지만 추가적인 비동기 로딩 방식이 있다.
수많은 프라이머리에셋을 전부 로딩하는 것은 너무 느리기 때문에 원하는 것만 로딩할 때 ChangeBundleStateForPrimaryAssets를 사용한다. BundlesToLoad와 동일하게 설정되어 있는 에셋만 로딩한다. \
로딩이 완료되면 GameMode에서 PlayerControllerIterator을 순회하며 RestartPlayer로 Pawn을 소환한다.
GetDefaultPawnClassForController_Implementation을 오버라이딩해서 현재 Experience의 PawnData를 넣는다.
Experience Load할 때 CDO를 Current Experience로 저장하는 이유?
CDO는 객체화된 클래스, 그리고 기본 값을 가지고 있다. 그래서 CDO가 있으면 기본값과 비교해서 변경된 값만 서버에 전달함으로써 메모리를 효율적으로 사용할 수 있다. 프라이머리 에셋을 로딩할 때 프라이머리 에셋의 타입과 이름을 ID로 갖는다. 그런데 타입이 Native C++ class라면 그대로 타입이 되므로 문제가 없지만 블루프린트일 경우, 블루프린트의 이름이 타입이 되므로 문제가 발생한다. 그래서 CDO라면 블루프린트의 부모를 타고 올라가서 처음으로 만나는 Native C++ class를 프라이머리 에셋 ID의 타입으로 하기 때문에 CDO를 Current Experience로 저장하는 것이다.
'Unreal' 카테고리의 다른 글
| [Unreal] Lyra 프로젝트 분석 - 04. Camera (0) | 2025.02.07 |
|---|---|
| [Unreal] Lyra 프로젝트 분석 - 03. Modular Gameplay Actor (0) | 2025.02.06 |
| [Unreal] Lyra 프로젝트 분석 - 01. AssetManager (0) | 2025.02.05 |
| [Unreal] Unfallen 플레이 영상 (0) | 2022.06.17 |
| [Unreal] BTTaskNode에서 ExecuteTask, TickTask 사용법 (0) | 2022.04.05 |