问题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<ULyraExperienceManagerComponent>();
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<ULyraDeveloperSettings>()->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<ALyraWorldSettings>(GetWorldSettings()))
{
ExperienceId = TypedWorldSettings->GetDefaultGameplayExperience();
ExperienceIdSource = TEXT("WorldSettings");
}
}
ULyraAssetManager& AssetManager = ULyraAssetManager::Get();
FAssetData Dummy;
if (ExperienceId.IsValid() && !AssetManager.GetPrimaryAssetData(ExperienceId, /*out*/ 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是手动调用的
---------------------------------------------------------------------------------------------------------------------------------
在play L_DefaultEditorOverview场景中如果按下esc键 就会出现退出ui 那么这个ui是在哪设置的呢,具体是什么逻辑呢
我先查了 project setting中的input 设置 没有关于esc的设置 在character和controller中也没有相关处理esc的处理逻辑
有个很有用的工具 能查看当ui的相关逻辑,当你无处下手的时候 可以用用这个工具
通过这个工具 我们很快查到了 退出 界面的ui蓝图叫
然后通过引用查看工具 我们看到 最终引用它的是
这个ui蓝图 是在 L_DefaultEditorOverview 地图的 exprerience数据中设置的
一进场景 就把 这个w_DefaultHUDLayout加载进来了
在w_DefaultHUDLayout中有一个设定
这就跟退出ui联系起来了
看看代码中怎么写的
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_LAYER_MENU, "UI.Layer.Menu");
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_ACTION_ESCAPE, "UI.Action.Escape");
ULyraHUDLayout::ULyraHUDLayout(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void ULyraHUDLayout::NativeOnInitialized()
{
Super::NativeOnInitialized();
RegisterUIActionBinding(FBindUIActionArgs(FUIActionTag::ConvertChecked(TAG_UI_ACTION_ESCAPE), false, FSimpleDelegate::CreateUObject(this, &ThisClass::HandleEscapeAction)));
}
void ULyraHUDLayout::HandleEscapeAction()
{
if (ensure(!EscapeMenuClass.IsNull()))
{
UCommonUIExtensions::PushStreamedContentToLayer_ForPlayer(GetOwningLocalPlayer(), TAG_UI_LAYER_MENU, EscapeMenuClass);
}
}
在初始化的时候就注册了 退出监听
当退出的时候就调用handleEscapeAction 就把退出界面给调用出来了
疑问解决了
------------------------------------------------------------------------------------------------------------------
问题
在默认地图
按下play键 会发现有一个蓝色的界面出现一下就消失了,
那么这个界面出现是什么逻辑呢?
我们探索一下
想用widget reflector 发现 这个界面出现时间太短 根本截取不到
那就现在content 中找到 这个界面吧
用Type=WidgetBlueprint 搜索所有的widget
发现有好几个看起来都一样
而且从名字上来看 这三个都挺有可能的 只能一个一个先查看引用
看来看去 觉得这个很像啊
全局搜索一下 看看W_LoadingScreen_host在哪创建的
全局蓝图搜索
没找到结果,路线断了
难道是在c++中创建的?搜一下c++工程
果然找到了 写的这么隐蔽 竟然在config文件中配置 点进去看看
意思是说 在CommonLoadingScreen插件下的CommonLoadingScreenSettings文件中的LoadingScreenWidget这个变量赋值了,去看看
这么一看 这个类是个配置类 应该 没啥逻辑 继承于UDeveloperSettingsBackedByCVars
那应该在project setting中找到 相关的配置ui界面 打开 project setting 看看
果然是在这个界面配置 最后保存在defaultgame.ini中,同时还有common input settings可以随手看一下。
然后仅仅是配置了值 还是没有找到 在哪生成,找c++那个变量就能找到
接着往上层找
首先 这个 manager类是个gameinstancesubsystem ,所以 游戏一运行这个manager就创建了
并且生命周期跟随gameinstance的生命周期
在tick中每帧都刷 就看shouldshowloadingscreen条件满足了就创建这个蓝色的loading画面
那就看 这个条件是如何满足的
bool ULoadingScreenManager::ShouldShowLoadingScreen()
{
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
// Check debugging commands that force the state one way or another
#if !UE_BUILD_SHIPPING
static bool bCmdLineNoLoadingScreen = FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen"));
if (bCmdLineNoLoadingScreen)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommandLine has 'NoLoadingScreen'"));
return false;
}
#endif
// Check for a need to show the loading screen
const bool bNeedToShowLoadingScreen = CheckForAnyNeedToShowLoadingScreen();
// Keep the loading screen up a bit longer if desired
bool bWantToForceShowLoadingScreen = false;
if (bNeedToShowLoadingScreen)
{
// Still need to show it
TimeLoadingScreenLastDismissed = -1.0;
}
else
{
// Don't *need* to show the screen anymore, but might still want to for a bit
const double CurrentTime = FPlatformTime::Seconds();
const bool bCanHoldLoadingScreen = (!GIsEditor || Settings->HoldLoadingScreenAdditionalSecsEvenInEditor);
const double HoldLoadingScreenAdditionalSecs = bCanHoldLoadingScreen ? LoadingScreenCVars::HoldLoadingScreenAdditionalSecs : 0.0;
if (TimeLoadingScreenLastDismissed < 0.0)
{
TimeLoadingScreenLastDismissed = CurrentTime;
}
const double TimeSinceScreenDismissed = CurrentTime - TimeLoadingScreenLastDismissed;
// hold for an extra X seconds, to cover up streaming
if ((HoldLoadingScreenAdditionalSecs > 0.0) && (TimeSinceScreenDismissed < HoldLoadingScreenAdditionalSecs))
{
// Make sure we're rendering the world at this point, so that textures will actually stream in
//@TODO: If bNeedToShowLoadingScreen bounces back true during this window, we won't turn this off again...
UGameViewportClient* GameViewportClient = GetGameInstance()->GetGameViewportClient();
GameViewportClient->bDisableWorldRendering = false;
DebugReasonForShowingOrHidingLoadingScreen = FString::Printf(TEXT("Keeping loading screen up for an additional %.2f seconds to allow texture streaming"), HoldLoadingScreenAdditionalSecs);
bWantToForceShowLoadingScreen = true;
}
}
return bNeedToShowLoadingScreen || bWantToForceShowLoadingScreen;
}
bool ULoadingScreenManager::CheckForAnyNeedToShowLoadingScreen()
{
// Start out with 'unknown' reason in case someone forgets to put a reason when changing this in the future.
DebugReasonForShowingOrHidingLoadingScreen = TEXT("Reason for Showing/Hiding LoadingScreen is unknown!");
const UGameInstance* LocalGameInstance = GetGameInstance();
if (LoadingScreenCVars::ForceLoadingScreenVisible)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommonLoadingScreen.AlwaysShow is true"));
return true;
}
const FWorldContext* Context = LocalGameInstance->GetWorldContext();
if (Context == nullptr)
{
// We don't have a world context right now... better show a loading screen
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("The game instance has a null WorldContext"));
return true;
}
UWorld* World = Context->World();
if (World == nullptr)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have no world (FWorldContext's World() is null)"));
return true;
}
AGameStateBase* GameState = World->GetGameState<AGameStateBase>();
if (GameState == nullptr)
{
// The game state has not yet replicated.
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("GameState hasn't yet replicated (it's null)"));
return true;
}
if (bCurrentlyInLoadMap)
{
// Show a loading screen if we are in LoadMap
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("bCurrentlyInLoadMap is true"));
return true;
}
if (!Context->TravelURL.IsEmpty())
{
// Show a loading screen when pending travel
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have pending travel (the TravelURL is not empty)"));
return true;
}
if (Context->PendingNetGame != nullptr)
{
// Connecting to another server
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are connecting to another server (PendingNetGame != nullptr)"));
return true;
}
if (!World->HasBegunPlay())
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("World hasn't begun play"));
return true;
}
if (World->IsInSeamlessTravel())
{
// Show a loading screen during seamless travel
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are in seamless travel"));
return true;
}
// Ask the game state if it needs a loading screen
if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
// Ask any game state components if they need a loading screen
for (UActorComponent* TestComponent : GameState->GetComponents())
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
// Ask any of the external loading processors that may have been registered. These might be actors or components
// that were registered by game code to tell us to keep the loading screen up while perhaps something finishes
// streaming in.
for (const TWeakInterfacePtr<ILoadingProcessInterface>& Processor : ExternalLoadingProcessors)
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
// Check each local player
bool bFoundAnyLocalPC = false;
bool bMissingAnyLocalPC = false;
for (ULocalPlayer* LP : LocalGameInstance->GetLocalPlayers())
{
if (LP != nullptr)
{
if (APlayerController* PC = LP->PlayerController)
{
bFoundAnyLocalPC = true;
// Ask the PC itself if it needs a loading screen
if (ILoadingProcessInterface::ShouldShowLoadingScreen(PC, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
// Ask any PC components if they need a loading screen
for (UActorComponent* TestComponent : PC->GetComponents())
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
}
else
{
bMissingAnyLocalPC = true;
}
}
}
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
const bool bIsInSplitscreen = GameViewportClient->GetCurrentSplitscreenConfiguration() != ESplitScreenType::None;
// In splitscreen we need all player controllers to be present
if (bIsInSplitscreen && bMissingAnyLocalPC)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("At least one missing local player controller in splitscreen"));
return true;
}
// And in non-splitscreen we need at least one player controller to be present
if (!bIsInSplitscreen && !bFoundAnyLocalPC)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("Need at least one local player controller"));
return true;
}
// Victory! The loading screen can go away now
DebugReasonForShowingOrHidingLoadingScreen = TEXT("(nothing wants to show it anymore)");
return false;
}
我 ca 太多条件了 原来 她把所有应该出现的loading的条件都写这里了,我们游戏刚开始出现只是她众多条件中的某一个条件
疑问解决
总结一下 在project setting中配置loading界面 在 一个loading subsystem中 每帧都tick 当条件满足 就出现loading界面 不满足就隐藏loading界面。
-------------------------------------------------------------------------------------
关于这个地图里面的东西 我感觉都已经探索完了
篇幅过长 再开一个2
这个角色也没有什么ability所以 这篇就结束了 太长了
开个新的
更多推荐
ue5 lyra探索分析1
发布评论