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

news2024/11/23 15:20:29

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 experience
	if (!ExperienceId.IsValid())
	{
		if (TryDedicatedServerLogin())
		{
			// This will start to host as a dedicated server
			return;
		}

		//@TODO: Pull this from a config setting or something
		ExperienceId = 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_SERVER
	ULyraSettingsLocal::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 widget
	TSubclassOf<UUserWidget> LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass<UUserWidget>();
	if (UUserWidget* UserWidget = UUserWidget::CreateWidgetInstance(*LocalGameInstance, LoadingScreenWidgetClass, NAME_None))
	{
		LoadingScreenWidget = UserWidget->TakeWidget();
	}
	...
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue进阶-elementPlus

Element Plus官网 Element Plus 基于 Vue 3&#xff0c;面向设计师和开发者的组件库。减少开发者关注css&#xff0c;重心关注业务逻辑。 1. 入门 1.1 安装 npm install element-plus --save1.2 快速开始 1、main.js 引入并 use element-plus import { createApp } from …

C#,数值计算——灰色码(Gray Codes)的计算方法与源代码

一个 n位灰色码 序列&#xff0c;就是2的n次方 个 整数&#xff1b; 第一个数字为0&#xff1b; 相邻两个数字的二进制只有一位不一样&#xff1b; 第一个数字和最后一个数字的二进制也只有一位不一样。 using System; namespace Legalsoft.Truffer { /// <summary&…

ARM Exynos4412 硬件中断和GIC管理、PWM控制 6.28

day7 1.中断 硬件中断&#xff1a;直接让外部的硬件产生中断&#xff0c;CPU获取中断源并执行异常处理流程 1.需求&#xff1a;&#xff08;中断的原理一样&#xff0c;但外设是按键&#xff09;按键产生中断&#xff0c;并在中断处理中串口发送消息 2.原理图&#xff1a;U…

Mysql 5.6使用配置文件my.ini来设置长时间连接数据库

对于已经安装了mysql和未安装都是同样的步骤。在C:\Program Files (x86)\MySQL\MySQL Server 5.6下生成一个my.ini文件。然后删除或者修改my-default.ini的名字。 一、my.ini配置文件如下 [mysqld] basedirC:\Program Files (x86)\MySQL\MySQL Server 5.6 datadirC:\Program F…

STM32F407 基本定时器使用

介绍STM32F407基本定时器的配置方法&#xff0c;分别介绍轮询方式、中断方式使用定时器完成定时。 【1】定时器介绍 定时器相关的章节在STM32F4xx参考手册第14、15、16、17章节。 【2】基本定时器配置示例 增加一个Timer.c文件&#xff0c;代码如下 #include "timer.h…

【sql】SQL回顾总结,超级全

SELECT&#xff1a;语句用于从数据库中选取数据 从 "Websites" 表中选取 "name" 和 "country" 列 SELECT name,country FROM Websites 从 "Websites" 表中选取所有列 SELECT * FROM Websites; SELECT DISTINCT&#xff1a;用于返…

Nacos(一):简介 如何安装 服务注册与发现 集群 权重 与Eureka区别

一、简介 1、应用场景 当服务调用越来越多&#xff0c;服务的地址需要管理起来&#xff0c;并实现动态调用而不是硬编码在接口中。此时需要一个注册中心来帮助我们管理服务。 流程如下&#xff1a; 商品微服务注册IP和端口到注册中心订单微服务先从注册中心获取到商品微服务…

45 # 实现文件拷贝功能

下面实现边读边写的文件拷贝功能&#xff0c;这样不会淹没系统的可用内存&#xff0c;合理读写 const fs require("fs"); const path require("path");function copy(source, target, callback) {const SIZE 3;const buffer Buffer.alloc(SIZE);let r…

OPENCV 训练分类器一

第一步&#xff0c;安装OPENCV 见最新openCV-Python安装教程(opencv-python版本4.4.0, Python版本: 3.9)_python安装opencv_这个人不是画家的博客-CSDN博客 第二步&#xff0c;下面是修正过后的Python 将文件夹下面所有的图片转换成灰色小图像&#xff0c;用于存正片用的。…

Python几种字符串格式化方法

Python 字符串格式化方法 文章目录 Python 字符串格式化方法1.python中的字符串格式化--百分号 %2. 字符串格式化-数字精度控制 m.n3. 字符串格式化--快速写法 f"{变量}"4. 案例&#xff1a;股价计算小程序 1.python中的字符串格式化–百分号 % %的主要作用将数据转换…

android switch的使用

一、前言&#xff1a;很多app都有开关这个功能&#xff0c;开关控件的使用跟checkbox好像也差不多。 二、上代码 创建一个activity:SwitchDefaultActivity public class SwitchDefaultActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListene…

vue(脚手架创建)代理解决跨域问题

目录 为什么会出现跨域问题 什么是跨域 Vue CLI Vue2解决跨域问题 不重写路径 重写路径 vue.config.js代码 Vue3解决跨域问题 ViteVue解决跨域问题 vite.config.ts代码 总结 为什么会出现跨域问题 出于浏览器的同源策略的限制。同源策略是一种约定&#xff0c;它是…

【新版系统架构】第七章-系统架构设计基础知识(基于架构的软件开发方法)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…

【go】数据表转csv

文章目录 1 基本结构1.1 数据1.2 数据结构 2 代码3 tip 1 基本结构 1.1 数据 1.2 数据结构 2 代码 代码&#xff1a; package mainimport ("database/sql""encoding/csv""fmt"_ "github.com/go-sql-driver/mysql""log"&q…

element ui table某个单元格添加点击事件

1.创建表格 <el-table ref"multipleTable" :data"tableData" border > <el-table-column fixed type"selection" align"center"></el-table-column> <div v-for"(item,index) in columns" :key"i…

在开源经历中成长,让开发者成为创新主体

本文背景 积极推进“满天星”行动计划&#xff0c;促进开发者成长&#xff0c;重庆软件园产业学院“成长营”将聚焦“技术提升”“职业成长”“价值提升”等话题开展系列主题活动&#xff0c;赋能软件技术开发者更好发展、更快成长。本次直播访谈&#xff0c;聚焦在开源经历中成…

车载 Android开发面试习题

随着车联网技术的不断发展和普及&#xff0c;越来越多的汽车厂商开始使用 Android 操作系统作为车载娱乐和信息娱乐系统的核心。在这个趋势下&#xff0c;车载 Android 应用开发程序员的需求也日益增加。 像一些车企大厂不惜给出 30K~60K的高资&#xff0c;去广招这方面的技术人…

为未来“点亮”想象空间:无镉量子点

量子点电视的诞生引发了行业内的色彩科技革命&#xff0c;量子点电视使用色彩纯净的量子点背光技术&#xff0c;能够最真实地还原图像色彩&#xff0c;是虚拟增强现实技术和智能可穿戴显示设备的理想之选。 量子点又可称为纳米晶&#xff0c;是一种由 II-VI 族或 III-V 族元素…

matlab绘制栅格地图

学习了一下栅格地图在matlib中的表示方法&#xff0c;其实和很多都是差不多的。 参考 清除命令 % clc&#xff1a;清除命令窗口的内容&#xff0c;对工作环境中的全部变量无任何影响 % close all:关闭所有的Figure窗口 % clear all&#xff1a;清除工作空间的所有变量&#xff…