Lyra学习笔记2 GFA_AddComponents与ULyraPlayerSpawningManagerComponent

前言

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_EDITOR
							if (!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));
							}
#endif
							Result = 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_EDITOR
		if (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 it
			if (PlayerState->IsOnlyASpectator())
			{
				if (!StarterPoints.IsEmpty())
				{
					return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)];
				}

				return nullptr;
			}
		}//若是Spectators在数组内随机一个位置返回

		AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints);
		//返回nullptr
		
		if (!PlayerStart)//若为nullptr,暂时必然为nullptr
		{
			PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, StarterPoints);
			//若有未占用,则在其中随机一个,若没有就在已经占用的随机一个
		}

		if (ALyraPlayerStart* LyraStart = Cast<ALyraPlayerStart>(PlayerStart))
		{
			LyraStart->TryClaim(Player);
			//尝试占用
		}

		return PlayerStart;
	}

	return nullptr;
}

待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值