UE5.2 LyraDemo源码阅读笔记(二)

UE5.2 LyraDemo源码阅读笔记(二)

创建了关卡中的体验玩家Actor和7个体验玩法入口之后。
接下来操作关卡中的玩家与玩法入口交互,进入玩法入口,选择进入B_LyraFrontEnd_Experience玩法入口,也就是第3个入口。触发以下请求方法,切换到关卡L_LyraFrontEnd:
(为啥选择L_LyraFrontEnd入口?因为这个流程比前两个独立的直接关卡玩法流程更完善)
在这里插入图片描述
可以看到GetWorld()->ServerTravel(url)里url的值为:/Game/System/FrontEnd/Maps/L_LyraFrontEnd?Experience=B_LyraFrontEnd_Experience。
其中,L_LyraFrontEnd是要进入的关卡,?Experience=B_LyraFrontEnd_Experience则是自定义数据,明显这是一个蓝图类和蓝图实例名称。
这个自定义参数将会传到C++类LyraGameMode的OptionsString字段,然后在进入新关卡L_LyraFrontEnd时,会重新执行LyraGameMode::InitGame方法,后面再在ALyraGameMode::HandleMatchAssignmentIfNotExpectingOne()里加载对于的蓝图类,即B_LyraFrontEnd_Experience:
在这个方法里,在各种场景方式下,决定了进入新关卡时创建哪个蓝图类,包括在编辑器启动游戏时的默认蓝图角色B_LyraDefaultExperience。
ALyraGameMode.cpp:

void ALyraGameMode::HandleMatchAssignmentIfNotExpectingOne()
{...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");}...// Final fallback to the default experienceif (!ExperienceId.IsValid()){if (TryDedicatedServerLogin()){// This will start to host as a dedicated serverreturn;}//@TODO: Pull this from a config setting or somethingExperienceId = FPrimaryAssetId(FPrimaryAssetType("LyraExperienceDefinition"), FName("B_LyraDefaultExperience"));ExperienceIdSource = TEXT("Default");}OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource);
}

然后在下面的OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource)方法里,获取GameState里的ExperienceComponent组件来设置其Experience。

void ALyraGameMode::OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource)
{if (ExperienceId.IsValid()){UE_LOG(LogLyraExperience, Log, TEXT("Identified experience %s (Source: %s)"), *ExperienceId.ToString(), *ExperienceIdSource);ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();check(ExperienceComponent);ExperienceComponent->SetCurrentExperience(ExperienceId);}...
}

为什么要设置到GameState里的ExperienceComponent组件呢?因为GameMode是控制整个关卡里的游戏流程,GameState则处理游戏状态,而游戏流程的数据则保存在Component里,而且,各个客户端的游戏GameState数据同步是通过Component的属性来同步(Replicated)的。可以打开ULyraExperienceManagerComponent.h查看CurrentExperience定义,带了ReplicatedUsing标签,所以它是会同步到其它客户端的。
ULyraExperienceManagerComponent.h:

class ULyraExperienceManagerComponent final : public UGameStateComponent, public ILoadingProcessInterface...
private:UPROPERTY(ReplicatedUsing=OnRep_CurrentExperience)TObjectPtr<const ULyraExperienceDefinition> CurrentExperience;...

根据ReplicatedUsing标签,当CurrentExperience被赋值时,则会触发OnRep_CurrentExperience()方法,从而开始加载资源。
Experience数据蓝图加载完成后,开始加载其中的Actions插件:

ULyraExperienceManagerComponent.cpp:

void ULyraExperienceManagerComponent::OnExperienceLoadComplete()
{...// Load and activate the features	NumGameFeaturePluginsLoading = GameFeaturePluginURLs.Num();if (NumGameFeaturePluginsLoading > 0){LoadState = ELyraExperienceLoadState::LoadingGameFeatures;for (const FString& PluginURL : GameFeaturePluginURLs){ULyraExperienceManager::NotifyOfPluginActivation(PluginURL);UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(PluginURL, FGameFeaturePluginLoadComplete::CreateUObject(this, &ThisClass::OnGameFeaturePluginLoadComplete));}}...
}

等Experience插件加载完成后,开始激活:
ULyraExperienceManagerComponent.cpp

void ULyraExperienceManagerComponent::OnExperienceFullLoadCompleted()
{...auto ActivateListOfActions = [&Context](const TArray<UGameFeatureAction*>& ActionList){for (UGameFeatureAction* Action : ActionList){if (Action != nullptr){...Action->OnGameFeatureRegistering();Action->OnGameFeatureLoading();Action->OnGameFeatureActivating(Context);}}};ActivateListOfActions(CurrentExperience->Actions);for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets){if (ActionSet != nullptr){ActivateListOfActions(ActionSet->Actions);}}LoadState = ELyraExperienceLoadState::Loaded;OnExperienceLoaded_HighPriority.Broadcast(CurrentExperience);OnExperienceLoaded_HighPriority.Clear();OnExperienceLoaded.Broadcast(CurrentExperience);OnExperienceLoaded.Clear();OnExperienceLoaded_LowPriority.Broadcast(CurrentExperience);OnExperienceLoaded_LowPriority.Clear();// Apply any necessary scalability settings
#if !UE_SERVERULyraSettingsLocal::Get()->OnExperienceLoaded();
#endif
}

Component里插件加载完成激活后,回调到ALyraGameMode::OnExperienceLoaded()方法,如果数据资产里Default Pawn Data里设置有LyraPawnData,那么将会根据此Data在关卡中生成对应的Actor。明显,B_LyraFrontEnd_Experience只是一个入口UI。没有设置。
ALyraGameMode.cpp:

void ALyraGameMode::OnExperienceLoaded(const ULyraExperienceDefinition* CurrentExperience)
{...for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator){APlayerController* PC = Cast<APlayerController>(*Iterator);if ((PC != nullptr) && (PC->GetPawn() == nullptr)){if (PlayerCanRestart(PC)){RestartPlayer(PC);}}}
}

与此同时,B_LyraFrontEnd_Experience数据蓝图里定义的Actions被加载完成激活时,这个Action同时被触发。
这里主要关注AddComponents这个Action,这里往LyraGameState这个类里添加了2个状态组件。这个Action会找到GameMode里的GameState,如果没有则新实例化一个,然后添加组件。
在这里插入图片描述

在添加的B_LyraFrontendStateComponent里,主要显示了LoadingUI界面和游戏入口菜单。看组件代码ULyraFrontendStateComponent.cpp:

void ULyraFrontendStateComponent::OnExperienceLoaded(const ULyraExperienceDefinition* Experience)
{FControlFlow& Flow = FControlFlowStatics::Create(this, TEXT("FrontendFlow")).QueueStep(TEXT("Wait For User Initialization"), this, &ThisClass::FlowStep_WaitForUserInitialization).QueueStep(TEXT("Try Show Press Start Screen"), this, &ThisClass::FlowStep_TryShowPressStartScreen).QueueStep(TEXT("Try Join Requested Session"), this, &ThisClass::FlowStep_TryJoinRequestedSession).QueueStep(TEXT("Try Show Main Screen"), this, &ThisClass::FlowStep_TryShowMainScreen);Flow.ExecuteFlow();FrontEndFlow = Flow.AsShared();
}

组件里监听玩法数据资产加载完成后,开始根据玩家状态显示对应的UI,这里对游戏入口菜单进行显示:

void ULyraFrontendStateComponent::FlowStep_TryShowMainScreen(FControlFlowNodeRef SubFlow)
{if (UPrimaryGameLayout* RootLayout = UPrimaryGameLayout::GetPrimaryGameLayoutForPrimaryPlayer(this)){constexpr bool bSuspendInputUntilComplete = true;RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(FrontendTags::TAG_UI_LAYER_MENU, bSuspendInputUntilComplete, MainScreenClass,[this, SubFlow](EAsyncWidgetLayerState State, UCommonActivatableWidget* Screen) {switch (State){case EAsyncWidgetLayerState::AfterPush:bShouldShowLoadingScreen = false;SubFlow->ContinueFlow();return;case EAsyncWidgetLayerState::Canceled:bShouldShowLoadingScreen = false;SubFlow->ContinueFlow();return;}});}
}

这里显示游戏菜单。
至此:
在这里插入图片描述
但最后还有一个:
在这些之前还有一个LoadingUI和大厅背景加载,他们是在什么时候进行加载的呢?
来看到关卡L_LyraFrontEnd下的蓝图节点B_LoadRandomLobbyBackground,打开它的蓝图,这个蓝图功能比较容易看得出来了,这里对数据资产ShooterGameLobbyBG里的BackgroundLevel字段里的大厅关卡进行加载,并打开LoadingUI。
那么打开LoadingUI的Widget是哪里设置的?打开显示UI的蓝图节点查看:
在这里插入图片描述
其静态方法定义,这里创建了一个ULoadingProcessTask实例并返回给蓝图持有引用,用于关闭LoadingUI:
ULoadingProcessTask.cpp

/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason)
{UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem<ULoadingScreenManager>() : nullptr;if (LoadingScreenManager){ULoadingProcessTask* NewLoadingTask = NewObject<ULoadingProcessTask>(LoadingScreenManager);NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason);LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask);	return NewLoadingTask;}return nullptr;
}

而这里的LoadingUI引用的Widget则在ULoadingScreenManagerl类里,跟进去看
,里面定义了LoadingScreenWidget:
ULoadingScreenManager.h

	...
class COMMONLOADINGSCREEN_API ULoadingScreenManager : public UGameInstanceSubsystem, public FTickableGameObject
{...
private:.../** A reference to the loading screen widget we are displaying (if any) */TSharedPtr<SWidget> LoadingScreenWidget;...
};

LoadingScreenWidget被定义了private,所以它是在内部.cpp赋值:
ULoadingScreenManager.h

void ULoadingScreenManager::ShowLoadingScreen()
{...const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();...// Create the loading screen widgetTSubclassOf<UUserWidget> LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass<UUserWidget>();if (UUserWidget* UserWidget = UUserWidget::CreateWidgetInstance(*LocalGameInstance, LoadingScreenWidgetClass, NAME_None)){LoadingScreenWidget = UserWidget->TakeWidget();}...
}

所以它是去读取了UCommonLoadingScreenSettings里的设置。
这个设置在UE编辑器下的:编辑>项目设置>游戏/CommonLoadingScreen下
在这里插入图片描述
至此,整个游戏大厅加载环节流程完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/7977.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web学习1--maven--项目管理工具

写在前面&#xff1a; 这学期搞主攻算法去了&#xff0c;web的知识都快忘了。开始复习学习了。 文章目录 maven介绍功能介绍maven安装jar包搜索仓库 pom文件项目介绍父工程依赖管理属性控制可选依赖构建 依赖管理依赖的传递排除依赖可选依赖 maven生命周期分模块开发模块聚合…

算法的时间复杂度

算法的时间复杂度 什么是时间复杂度 时间复杂度是衡量算法执行时间随输入规模增长而增长的度量标准。它描述了算法运行时间与问题规模之间的关系&#xff0c;用于评估算法的效率和性能。 通常情况下&#xff0c;时间复杂度表示为大O符号&#xff08;O&#xff09;&#xff0…

限时等待的互斥量

本文结束一种新的锁&#xff0c;称为 timed_mutex 代码如下&#xff1a; #include<iostream> #include<mutex> #include<thread> #include<string> #include<chrono>using namespace std;timed_mutex tmx;void fun1(int id, const string&a…

C/C++的发展历程和未来趋势

文章目录 C/C的起源C/C的应用C/C开发的工具C/C未来趋势 C/C的起源 C语言 C语言是一种通用的高级编程语言&#xff0c;由美国计算机科学家Dennis Ritchie在20世纪70年代初期开发出来。起初&#xff0c;C语言是作为操作系统UNIX的开发语言而创建的。C语言的设计目标是提供一种功…

基于Springboot+Vue的手机商城(源代码+数据库)081

基于SpringbootVue的手机商城(源代码数据库)081 一、系统介绍 本项目前后端分离&#xff08;该项目还有ssmvue版本&#xff09; 本系统分为管理员、用户两种角色 用户角色包含以下功能&#xff1a; 登录、注册、商品搜索、收藏、购物车、订单提交、评论、退款、收货地址管…

【C++】vector模拟实现

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f681; 个人主页&#xff1a;不 良 &#x1f525; 系列专栏&#xff1a;&#x1f6f8;C &#x1f6f9;Linux &#x1f4d5; 学习格言&#xff1a;博观而约取&#xff0…

比亚迪车载Android开发岗三面经历~

前言 首先&#xff0c;我想说一下我为什么会想去比亚迪这样的车企做车载Android开发。我是一名有5年经验的Android开发工程师&#xff0c;之前一直在互联网软件公司工作&#xff0c;做过移动端App和IoT产品的开发。但我一直对汽车领域很感兴趣&#xff0c;也希望自己的技术能应…

(并查集) 685. 冗余连接 II ——【Leetcode每日一题】

并查集基础 并查集&#xff08;Union-find Sets&#xff09;是一种非常精巧而实用的数据结构&#xff0c;它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的Kruskal算法和求最近公共祖先&#xff08;LCA&#xff09;等。 并查集的基本操作主…

【python】jupyter notebook的快捷键

Jupyter Notebook 提供了许多快捷键以方便操作。这些快捷键分为两类&#xff1a;命令模式快捷键和编辑模式快捷键。 命令模式快捷键&#xff1a; 在命令模式下&#xff0c;细胞边框是蓝色的。这些快捷键主要用于操作单元格。 Enter&#xff1a;进入编辑模式Shift Enter&…

如何用Airtest脚本无线连接Android设备?

1. 前言 之前我们已经详细介绍过如何用AirtestIDE无线连接Android设备&#xff1a; 手把手教你用AirtestIDE无线连接手机&#xff01; &#xff0c;它的关键点在于&#xff0c;需要先 adb connect 一次&#xff0c;才能点击 connect 按钮无线连接上该设备&#xff1a; 但是有很…

当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用

第一章 理论基础与研究热点分析 1. 生态系统服务与生态系统服务价值介绍 ​ 2. 生态系统服务价值研究方法 3. 生态系统服务价值研究热点 Citespace文献可视化分析 VOSviewer文献可视化分析 第二章 空间数据来源及预处理 1. 空间数据简介 2. ArcGIS Pro数据采集与分析 数…

Mybaits-Plus学习5 代码生成器

引入代码生成器依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version> </dependency> <dependency><groupId>org.freemarker</groupId…