问题1:
这个地图的worldsetting中并没有指定gamemode,那么游戏运行的时候角色的类型是怎么指定的呢?
有人说在projectsetting中有一个全局的gamemode
但是这个角色的类型也不是真的运行起来角色的类型
真的运行的时候角色的类型是 如上图所示
找一下吧,看看具体是在哪里设定的,过程是如何的。茫茫大海如何着手呢,我先在lyracharacter的构造函数打个断点看看
从堆栈中找到一个有用的信息
从图上看spawn的时候已经指定好了,而且这个函数是重载基类的方法,继续跟代码。
UClass* ALyraGameMode::GetDefaultPawnClassForController_Implementation(AController* InController)
{
if (const ULyraPawnData* PawnData = GetPawnDataForController(InController))
{
if (PawnData->PawnClass)
{
return PawnData->PawnClass;
}
}
return Super::GetDefaultPawnClassForController_Implementation(InController);
}
重载了父类 的方法,这里出现了ULyraPawnData这个数据类型,继续跟代码
发现首先从gamestate上找到一个叫ULyraExperienceManagerComponent组件
从组件上获得一个ULyraExperienceDefinition数据类型的数据。
继续找
const ULyraExperienceDefinition* ULyraExperienceManagerComponent::GetCurrentExperienceChecked() const
{
check(LoadState == ELyraExperienceLoadState::Loaded);
check(CurrentExperience != nullptr);
return CurrentExperience;
}
void ALyraGameMode::OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource)
{
#if WITH_SERVER_CODE
if (ExperienceId.IsValid())
{
UE_LOG(LogLyraExperience, Log, TEXT("Identified experience %s (Source: %s)"), *ExperienceId.ToString(), *ExperienceIdSource);
ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass();
check(ExperienceComponent);
ExperienceComponent->ServerSetCurrentExperience(ExperienceId);
}
else
{
UE_LOG(LogLyraExperience, Error, TEXT("Failed to identify experience, loading screen will stay up forever"));
}
#endif
}
void ALyraGameMode::HandleMatchAssignmentIfNotExpectingOne()
{
FPrimaryAssetId ExperienceId;
FString ExperienceIdSource;
// Precedence order (highest wins)
// - Matchmaking assignment (if present)
// - URL Options override
// - Developer Settings (PIE only)
// - Command Line override
// - World Settings
// - Default experience
UWorld* World = GetWorld();
if (!ExperienceId.IsValid() && UGameplayStatics::HasOption(OptionsString, TEXT("Experience")))
{
const FString ExperienceFromOptions = UGameplayStatics::ParseOption(OptionsString, TEXT("Experience"));
ExperienceId = FPrimaryAssetId(FPrimaryAssetType(ULyraExperienceDefinition::StaticClass()->GetFName()), FName(*ExperienceFromOptions));
ExperienceIdSource = TEXT("OptionsString");
}
if (!ExperienceId.IsValid() && World->IsPlayInEditor())
{
ExperienceId = GetDefault()->ExperienceOverride;
ExperienceIdSource = TEXT("DeveloperSettings");
}
// see if the command line wants to set the experience
if (!ExperienceId.IsValid())
{
FString ExperienceFromCommandLine;
if (FParse::Value(FCommandLine::Get(), TEXT("Experience="), ExperienceFromCommandLine))
{
ExperienceId = FPrimaryAssetId::ParseTypeAndName(ExperienceFromCommandLine);
ExperienceIdSource = TEXT("CommandLine");
}
}
// see if the world settings has a default experience
if (!ExperienceId.IsValid())
{
if (ALyraWorldSettings* TypedWorldSettings = Cast(GetWorldSettings()))
{
ExperienceId = TypedWorldSettings->GetDefaultGameplayExperience();
ExperienceIdSource = TEXT("WorldSettings");
}
}
ULyraAssetManager& AssetManager = ULyraAssetManager::Get();
FAssetData Dummy;
if (ExperienceId.IsValid() && !AssetManager.GetPrimaryAssetData(ExperienceId, Dummy))
{
UE_LOG(LogLyraExperience, Error, TEXT("EXPERIENCE: Wanted to use %s but couldn't find it, falling back to the default)"), *ExperienceId.ToString());
ExperienceId = FPrimaryAssetId();
}
// Final fallback to the default experience
if (!ExperienceId.IsValid())
{
//@TODO: Pull this from a config setting or something
ExperienceId = FPrimaryAssetId(FPrimaryAssetType("LyraExperienceDefinition"), FName("B_LyraDefaultExperience"));
ExperienceIdSource = TEXT("Default");
}
OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource);
}
void ALyraGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
Super::InitGame(MapName, Options, ErrorMessage);
//@TODO: Eventually only do this for PIE/auto
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne);
}
从代码中我们发现 ,当gamemode初始化game的时候,下一帧就调用
HandleMatchAssignmentIfNotExpectingOne()这个方法
这个方法中有多个获得
ExperienceId的方法,不同的情况分别命中一个
而默认地图用的是最后一个
对就是硬编码,写死的读取一个叫
B_LyraDefaultExperience的资源我们看看这个资源是什么
真是绕啊,不过lyra也遵循了数据驱动的原则,这些UPrimaryDataAsset就是数据配置。
--------------------------------------------------------------------------------------------------------
问题2:我们进这个地图会是什么流程
首先看看这个control 特效这一疙瘩是怎么生成的。
刚开始默认的地图中,有一个这个蓝图actor,里面有逻辑。
在beginplay里面获得了所有类型是LyraUserFacingExperienceDefinition的资源primary asset id
然后有几个LyraUserFacingExperienceDefinition这个就生成几个B_TeleportToUserFacingExperience
我们先看看LyraUserFacingExperienceDefinition这个都定义了什么
通过上图可以看到最重要的是定义了那张地图和一个数据这个数据定义了角色是哪个还有各种gamefeature的 action
生成B_TeleportToUserFacingExperience的时候把对应的LyraUserFacingExperienceDefinition带进去
在B_TeleportToUserFacingExperience里面有一个碰撞体,看看碰撞方法里面的逻辑
当一个角色碰撞到这个粒子特效的时候
1是生成了request
然后
我们一直用的是standalong模式所以目前的流程就是server的流程
从外面向里面分析先到这里,我们从里面向外面分析,最后肯定会汇合到这里
我们断点到这里
同一个函数,control这个地图进的是这个,并且把experienceid得到了
我们看看 这个optionsString是怎么赋值的
看看对上了,就是servertravel的时候把nextmap记录下来
然后在
UEngine::TickWorldTravel
中判断是不是空,非空就loadmap。
---------------------------------------------------------------------------------------------------
问题3:lyra的gamefeature and ULyraExperienceDefinition中的actions
在看 lyra的过程中你会发现:
既有正常的gamefeature的action
又有在ULyraExperienceDefinition中定义的action
感觉好乱
先看正常的gamefeature
lyra工程中也有继承
UDefaultGameFeaturesProjectPolicies
而来的
ULyraGameFeaturePolicy
但是看了源码,并没有对plugin里面的gamefeature做策略性的加载,所以plugin里面的gamefeature会随着gamefeaturesubsystem的创建和init方法,而导入到内存中,并且自动active,所以gamefeaturedata里面设置的action都会自动运行。
而ULyraExperienceDefinition中定义的action是手动调用的



