目錄
- 前言
- GameFeatureAction_AddComponents
- ULyraPlayerSpawningManagerComponent
- 緩存所有PlayerStart位置
- 選擇位置
前言
1.以control模式為例
2.比較散,想單獨拿出一篇梳理下Experience的流程
GameFeatureAction_AddComponents
這部分建議看
《InsideUE5》GameFeatures架構(五)AddComponents
這里寫的內容相當于是我跟著走了一遍
Lyra中角色基本類ALyraCharacter類似于一個框架,負責轉發事件給其組件,組件實現相關功能,所以ALyraCharacter甚至只有幾百行(在這個不算小的項目里這真的算短了(我覺得))。
在研究生成角色流程之前,我想先研究一下GameFeatureAction中的AddComponents,幫助之后的理解。
在加載Experience的過程中,執行到ULyraExperienceManagerComponent::OnExperienceLoadComplete時會激活GameFeature,以AddComponents為例,一直到OnGameFeatureActivating的調用棧如圖:
而在OnGameFeatureActivating中都會調用到AddToWorld
前面經過一些World,客戶端服務端之類的判斷后,執行邏輯在如下的一行:
void UGameFeatureAction_AddComponents::AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles) {...
Handles.ComponentRequestHandles.Add(GFCM->AddComponentRequest(Entry.ActorClass, ComponentClass, static_cast<EGameFrameworkAddComponentFlags>(Entry.AdditionFlags)));
...
}
GFCM::AddComponentRequest
TSharedPtr<FComponentRequestHandle> UGameFrameworkComponentManager::AddComponentRequest(const TSoftClassPtr<AActor>& ReceiverClass, TSubclassOf<UActorComponent> ComponentClass, const EGameFrameworkAddComponentFlags AdditionFlags)
{// You must have a receiver and component class. The receiver cannot be AActor, that is too broad and would be bad for performance.if (!ensure(!ReceiverClass.IsNull()) || !ensure(ComponentClass) || !ensure(ReceiverClass.ToString() != TEXT("/Script/Engine.Actor"))){return nullptr;}//一些檢查FComponentRequestReceiverClassPath ReceiverClassPath(ReceiverClass);UClass* ComponentClassPtr = ComponentClass.Get();FComponentRequest NewRequest;NewRequest.ReceiverClassPath = ReceiverClassPath;NewRequest.ComponentClass = ComponentClassPtr;// Add a request if there is not an already existing one. Note that it will only uses the receiver and component class to check for uniqueness, not the addition flags.int32& RequestCount = RequestTrackingMap.FindOrAdd(NewRequest);RequestCount++;if (RequestCount == 1)//第一次匹配{EGameFrameworkAddComponentResult Result = EGameFrameworkAddComponentResult::Failed;auto& RequestInfoSet = ReceiverClassToComponentClassMap.FindOrAdd(ReceiverClassPath);RequestInfoSet.Add({ ComponentClassPtr, AdditionFlags } );if (UClass* ReceiverClassPtr = ReceiverClass.Get()){UGameInstance* LocalGameInstance = GetGameInstance();if (ensure(LocalGameInstance)){UWorld* LocalWorld = LocalGameInstance->GetWorld();if (ensure(LocalWorld)){for (TActorIterator<AActor> ActorIt(LocalWorld, ReceiverClassPtr); ActorIt; ++ActorIt)//遍歷場景中的Actor{if (ActorIt->IsActorInitialized())//調用過BeginPlay{
#if WITH_EDITORif (!ReceiverClassPtr->HasAllClassFlags(CLASS_Abstract)){ensureMsgf(AllReceivers.Contains(*ActorIt), TEXT("You may not add a component request for an actor class that does not call AddReceiver/RemoveReceiver in code! Class:%s"), *GetPathNameSafe(ReceiverClassPtr));}
#endifResult = CreateComponentOnInstance(*ActorIt, ComponentClass, AdditionFlags);//創建組件}}}}}else{// Actor class is not in memory, there will be no actor instances}return MakeShared<FComponentRequestHandle>(this, ReceiverClass, ComponentClass);}return nullptr;
}
EGameFrameworkAddComponentResult UGameFrameworkComponentManager::CreateComponentOnInstance(AActor* ActorInstance, TSubclassOf<UActorComponent> ComponentClass, const EGameFrameworkAddComponentFlags AdditionFlags)
{...UActorComponent* NewComp = NewObject<UActorComponent>(ActorInstance, ComponentClass, NewComponentName);...
}
一些細節原文寫的很好《InsideUE5》GameFeatures架構(五)AddComponents
ULyraPlayerSpawningManagerComponent
總結一下目前為止的流程:
讀取WordSetting中的Experience->
GameState中的ULyraExperienceManagerComponent加載Experience->
激活Experience中配置的GameFeature->
執行Actions
Control關卡中的B_LyraShooterGame_ControlPoints(Experience)的Actions中,
有添加組件的Action,其中配置了在LyraGameState中添加ULyraPlayerSpawningManagerComponent,這個組件負責了管理生成位置。
緩存所有PlayerStart位置
void ULyraPlayerSpawningManagerComponent::InitializeComponent()
{Super::InitializeComponent();UE_LOG(LogTemp,Warning,TEXT("LAPI: ULyraPlayerSpawningManagerComponent::InitializeComponent"));FWorldDelegates::LevelAddedToWorld.AddUObject(this, &ThisClass::OnLevelAdded);UWorld* World = GetWorld();World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &ThisClass::HandleOnActorSpawned));for (TActorIterator<ALyraPlayerStart> It(World); It; ++It){if (ALyraPlayerStart* PlayerStart = *It){CachedPlayerStarts.Add(PlayerStart);}}
}
直接看后半部分可知緩存了場景中所有的PlayerStart。
再來看OnLevelAdded這個函數:
void ULyraPlayerSpawningManagerComponent::OnLevelAdded(ULevel* InLevel, UWorld* InWorld)
{if (InWorld == GetWorld()){for (AActor* Actor : InLevel->Actors){if (ALyraPlayerStart* PlayerStart = Cast<ALyraPlayerStart>(Actor)){ensure(!CachedPlayerStarts.Contains(PlayerStart));CachedPlayerStarts.Add(PlayerStart);}}}
}
這里是新的Level被AddToWorld的時候調用的,以實現更新PlayerStart,從L_DefaultEditorOverview到L_Convolution_Blockout并不會觸發,因為是加載組件之后才綁定的。
同理HandleOnActorSpawned是負責處理動態生成的PlayerStart的。
選擇位置
加載完地圖資源后,GameMode會將ChoosePlayerStart轉到SpawningManagerComponent的ChoosePlayerStart函數:
Choose函數比較長
AActor* ULyraPlayerSpawningManagerComponent::ChoosePlayerStart(AController* Player)
{if (Player){
#if WITH_EDITORif (APlayerStart* PlayerStart = FindPlayFromHereStart(Player)){return PlayerStart;}
#endif//這部分處理編輯器中PlayFromHere的PlayerStartPIE的特殊情況TArray<ALyraPlayerStart*> StarterPoints;for (auto StartIt = CachedPlayerStarts.CreateIterator(); StartIt; ++StartIt){if (ALyraPlayerStart* Start = (*StartIt).Get()){StarterPoints.Add(Start);}else{StartIt.RemoveCurrent();}}//處理完后StarterPoints存的是安全的強引用if (APlayerState* PlayerState = Player->GetPlayerState<APlayerState>()){// start dedicated spectators at any random starting location, but they do not claim itif (PlayerState->IsOnlyASpectator()){if (!StarterPoints.IsEmpty()){return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)];}return nullptr;}}//若是Spectators在數組內隨機一個位置返回AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints);//返回nullptrif (!PlayerStart)//若為nullptr,暫時必然為nullptr{PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, StarterPoints);//若有未占用,則在其中隨機一個,若沒有就在已經占用的隨機一個}if (ALyraPlayerStart* LyraStart = Cast<ALyraPlayerStart>(PlayerStart)){LyraStart->TryClaim(Player);//嘗試占用}return PlayerStart;}return nullptr;
}
待續…