[TOC]
- 推荐打开方式:Typora (Github主题)> Gitlab > PDF
- 推荐阅读方式:略读【工程简析】->跟着【关卡解析】自定义关卡->结合源码,再回头看工程简析。
把Lyra的大致流程简单过了一遍,架构中比较深刻的是:
- Lrya通过覆盖UE默认的引擎类,为射击游戏提供了专门的事件及属性插槽,GameFeature中实现射击游戏的核心逻辑,并以插件的形式制作游戏的扩展,最终挂载到游戏主逻辑上。
在Editor中,可以在项目设置中看到如下默认选项:
上述配置对应Lyra/Config/DefaultEngine.ini
的条目:
该配置文件将影响引擎工程的构建,UE在执行时会根据配置文件的覆盖原有的引擎类,Lyra中通过覆盖这些引擎默认类来实现自己的项目配置,以GameMode为例:
LyraGameMode的构造函数中绑定了各个状态对应的元类型(StaticClass),之后将根据这些绑定的StaticClass使用函数
SpawnActor
创建相应的实例。
简单点说,SubSystem就是官方推荐的单例方案,相比传统的C++单例,它主要有以下好处:
-
依附在引擎中的已有的单例上(比如GameInstance,Engine,Editor等),SubSystem的生命周期由其同步和维护。
-
无需修改引擎代码,UE预留了接口,在引擎执行时,会根据反射信息得到上述单例的派生类(DerivedClass)的元对象(StaticClass),创建所有的SubSystem,而SubSystem通过引擎提供的事件插槽进行构造并提供接口。
UE中很多结构都体现了这种插件式的架构思路
使用时只需继承自对应的SubSystem即可,其中UE支持Subsystem的类有:
- Engine:
UEngineSubsystem
- Editor:
UEditorSubsystem
- GameInstance:
UGameInstanceSubsystem
- World:
UWorldSubsystem
- LocalPlayer:
ULocalPlayerSubsystem
以UEngineSubsystem为例:
UCLASS()
class MySubsystem : public UEngineSubsystem{
GENERATED_BODY()
}
而在类UEngine的定义中,拥有成员变量:
TUniqueObj<FSubsystemCollection<UEngineSubsystem>> EngineSubsystemCollection;
在函数UEngine::Init()
中,将会调用:
EngineSubsystemCollection->Initialize(this);
其中该函数的实现如下:
对于支持Subsystem的类,都具有成员变量FSubsystemCollection,在类初始化时,将调用函数FSubsystemCollectionBase::Initialize,该函数会根据反射信息,创建所有Subsytem的子类。
官方的说法是:如果你觉得需要一个Manager,那么这就是使用SubSystem的时机。
-
UCommonSessionSubsystem : public UGameInstanceSubsystem
处理托管和加入在线游戏的请求。
-
UCommonUserSubsystem : public UGameInstanceSubsystem
处理查询和更改用户身份和登录状态。
-
UGameplayMessageSubsystem : public UGameInstanceSubsystem
该系统允许事件引发器和侦听器注册消息,而不必直接了解对方,尽管它们必须就消息的格式(作为USTRUCT()类型)达成一致。
-
ULyraAudioMixEffectsSubsystem : public UWorldSubsystem
该子系统旨在自动参与默认和用户控制总线混合,以检索以前保存的用户设置,并将它们应用到激活的用户混合。此外,该子系统将根据用户对HDR音频的偏好自动应用HDR/LDR音频Submix效果链覆盖。Submix效果链覆盖在天琴座音频设置中定义。
-
ULyraContextEffectsSubsystem : public UWorldSubsystem
-
ULyraExperienceManager : public UEngineSubsystem
主要用于多个PIE会话之间的仲裁
-
ULyraGamePhaseSubsystem : public UWorldSubsystem
-
ULyraGlobalAbilitySystem : public UWorldSubsystem
-
ULyraLoadingScreenSubsystem : public UGameInstanceSubsystem
用于显示和隐藏Loading界面
-
ULyraPerformanceStatSubsystem : public UGameInstanceSubsystem
子系统允许访问性能统计数据以进行显示
-
ULyraTeamSubsystem : public UWorldSubsystem
用于方便地访问基于团队的参与者的团队信息(例如角色状态)
-
UGameUIManagerSubsystem : public UGameInstanceSubsystem
- ULyraUIManagerSubsystem : public UGameUIManagerSubsystem
-
UCommonMessagingSubsystem : public ULocalPlayerSubsystem
- ULyraUIMessaging : public UCommonMessagingSubsystem
-
UPocketCaptureSubsystem : public UWorldSubsystem
-
UPocketLevelSubsystem : public UWorldSubsystem
-
USubtitleDisplaySubsystem : public UGameInstanceSubsystem
-
UUIExtensionSubsystem : public UWorldSubsystem
Lyra中大量使用C++派生UPrimaryDataAsset并公开特定的Property,然后在编辑器中派生蓝图进行配置,以供内部C++使用。
- ULyraAbilitySet:定义技能集合,供Lyra内置的GameplayAbilitySystem使用。
- ULyraAimSensitivityData:定义一组对浮点值的手柄灵敏度
- ULyraExperienceDefinition:定义Lyra的GameFeature数据,包括插件列表,Action操作等
- ULyraExperienceActionSet:存放具有关联性的Actions
- ULyraGameData:定义全局游戏数据
- ULyraLobbyBackground:定义Lyra中的加载背景(关卡)
- ULyraPawnData:默认的角色数据,其中包括:角色类的指定,技能集合,标签映射,输入配置,相机模式。定义如下:
- ULyraUserFacingExperienceDefinition:用于在UI中显示体验并开始新会话的设置描述
详细的使用过程请仔细查阅下方的ULyraExperienceDefinition
提前阅读:
https://www.bilibili.com/video/BV1dL4y1h7YW?spm_id_from=333.337.search-card.all.click
Lyra中项目配置中覆盖了GameFeaturePolicy,用于追踪游戏中的内置及外部插件(例如,通过web服务或其他终端)。
Lyra中覆盖了引擎的WorldSetting,并新增了ULyraExperienceDefinition属性,用于配置GameFeature及相关的行为
其中ULyraExperienceDefinition的定义如下:
-
GameFeaturesToEnable:需要开启的GameFeature(名称数组)
-
DefaultPawnData:默认的角色数据,其中包括:角色类的指定,技能集合,标签映射,输入配置,相机模式。定义如下:
-
Actions:单个元素可以是UGameFeatureAction的子类,在Lyra的目录
Lyra\Source\LyraGame\GameFeatures
中,派生了许多Action -
ActionSet:单个元素为 具有关联性的一组Action(包含GameFeature),Lyra中通过使用蓝图派生ULyraExperienceActionSet来进行配置
该类仅仅是为了在编辑器模式下处理多个PIE会话
ULyraExperienceDefinition做数据的定义,ULyraExperienceManagerComponent才是真正管理GameFeature的角色
ULyraExperienceManagerComponent的创建及管理位于ALyraGameState中
由ALyraGameMode负责加载
请务必提前阅读:
- 作用:监控所有的ULyraAbilitySystemComponent(下文简称ASC),并对全体ASC的Ability或Effect进行设置。
-
解析:上面的代码可以看出ULyraGlobalAbilitySystem使用了一种常见的对象监控管理手段:
在对象(创建/初始化/激活)时添加到全局的管理器中(注意添加时会应用当前管理器的设置),在对象(销毁/卸载/休眠)时,从全局管理器中移除,这样可以通过全局管理器对其中的所有对象进行统一操作。
很明显,RegisterASC和UnregisterASC将由ULyraAbilitySystemComponent在恰当时机调用,这两个函数主要修改的目标是成员变量RegisteredASCs
能力系统组件(
UAbilitySystemComponent
) 是演员和游戏能力系统之间的桥梁。任何打算与 Gameplay 能力系统交互的 Actor 都需要自己的能力系统组件,或访问另一个 Actor 拥有的能力系统组件。
Lyra中也是覆盖默认的UAbilitySystemComponent做了扩展实现。
在源码中拥有ULyraAbilitySystemComponent的类型有:
-
ALyraGameState
-
ALyraPlayerState
-
ALyraCharacterWithAbilities
特别注意
虽然ALyraCharacter包括了ULyraPawnExtensionComponent,它里面有ULyraAbilitySystemComponent*,但是值为
nullptr
,需要调用函数ULyraPawnExtensionComponent::InitializeAbilitySystem(ULyraAbilitySystemComponent*, AActor*)
对其进行赋值,Lyra中使用的Character蓝图为B_Hero_ShooterMannequin:它还包括了ULyraHeroComponent,其中就包含了以下操作,使用ALyraPlayerState中的ASC对PawnExtComp的ASC初始化:
数据资产
路径为:
Lyra\Content\System\FrontEnd\Maps\L_LyraFrontEnd.umap
-
Lyra覆盖了引擎的WorldSetting,并新增了ULyraExperienceDefinition属性,用于管理GameFeature,在当前关卡,它的值为B_LyraFrontEnd_Experience
其路径为
Game/System/FrontEnd/B_LyraFrontEnd_Experience
-
其中B_LyraFrontEnd_Experience继承自C++类ULyraExperienceDefinition:
-
B_LyraFrontEnd_Experience中具有以下的Actions,它们会在程序开始时执行对应操作(比如Add Components,Add Widgets...)
-
界面中的背景由蓝图
Lyra/Content/Environments/B_LoadRandomLobbyBackground
提供:其本质是加载关卡作为背景,其中加载的关卡为:
Lyra/Plugins/GameFeatures/ShooterMaps/Content/Maps/L_ShooterFrontendBackground.umap
-
初始界面的前置菜单由蓝图类
/Game/UI/B_LyraFrontendStateComponent
提供,它继承自Lyra/Source/LyraGame/UI/Frontend/LyraFrontendStateComponent
-
UI文件位于:
-
源码中会依次加载UI
-
菜单中的按钮对应如下事件
-
单击按钮StartGame 将跳转到界面
Lyra/Content/UI/Menu/Experiences/W_ExperienceSelectionScreen
-
点击事件如下:
-
其中加载游戏的主要操作在节点Quick Play Session中完成,主要运行步骤如下:
该节点由
Lyra\Plugins\CommonUser\Source\CommonUser\UCommonSessionSubsystem
提供-
执行
UCommonSessionSubsystem::QuickPlaySession()
,查找Session。 -
查找结束将调用
UCommonSessionSubsystem::HandleQuickPlaySearchFinished()
,如果找到Session则加入,否则创建Session: -
对于
UCommonSessionSubsystem::HostSession()
,将执行以下逻辑,默认情况下会走**CreateOnlineSessionInternal(LocalPlayer, Request)**的分支 -
其中
CreateOnlineSessionInternal()
会对PendingTravelURL赋值,并创建Session当前PendingTravelURL的值是:L"/ShooterMaps/Maps/L_Expanse?listen?Experience=B_ShooterGame_Elimination"
-
Session创建完毕将调用以下函数,通过**GetWorld()->ServerTravel(PendingTravelURL);**切换场景。
-
切换场景时,ULoadingScreenManager(public UGameInstanceSubsystem)的Tick函数会验证是否要显示LoadingScreen,上述的逻辑将导致以下分支:
关卡蓝图中,仅有一个附加粒子的逻辑,且并未生效
该关卡的Experience为B_LyraShooterGame_ControlPoints
其路径为
Lyra/Plugins/GameFeatures/ShooterCore/Content/Experiences/B_LyraShooterGame_ControlPoints
可以从上面的Actions看出场景运行时将加载:
- UI
- ControlPointScoring:控制点计分机制
- PickRandomCharacter:随机角色(男性模型或女性模型)
- TeamSetup_TwoTeams:队伍生成机制(该组件的作用是分为红蓝两队)
- TeamSpawningRules:队伍出生机制
- MusicManagerComponent:音频管理组件
- ShooterBotSpawner:控制机器人的生成
该关卡的游戏方式是:占领控制点,控制点多的队伍会持续加分,当分数累加到一定程度时,该队伍获胜。
蓝图B_ControlPointScoring的逻辑如下
位于
Lyra/Plugins/GameFeatures/ShooterCore/Content/ControlPoint/B_ControlPointScoring
- 事件说明:
- 事件开始运行:开启一个名为Scroing的定时器。
- GameStart (游戏开始后执行):根据游戏人数来确定获胜所需的分数。
- CapturePoint(占领控制点后执行):根据控制点及当前控制点中的角色(0)获取到对应的TeamId,修改ControlPointOwnerTeams的值为对应的ID,并更新响应的Tag。
- RegisterControlPoint(注册(创建)控制点时执行):将控制点添加到数组Control Points中,并将ControlPointOwnerTeams对应index的元素置为**-1**表示中立。
- Scroing(由上方的定时器触发):用于定时去更新当前的比分,并判断游戏的胜利条件,结束时进行结算。
此Actor蓝图位于
Lyra/Plugins/GameFeatures/ShooterCore/Content/Blueprint/B_ControlPointVolume
,
- 事件说明:
- 事件开始运行:触发事件RegisterControlPoint
- 组件开始重叠时(Cube):触发事件RecomputeContest及EnterAudio
- 组件结束重叠时(Cube):触发事件RecomputeContest及ExitAudio
- RecomputeContest:将当前覆盖控制点体积的所有Actor的TeamID加入(AddUnique)到OverlappingTeams,如果只有一个Team,则开始占领控制点,此时会开启一个时间轴,并设置材质及Niagara粒子的颜色。
该模块的作用是:随机生成男性(Manny)或女性(Quinn)角色。
该蓝图位于:Lyra/Content/Characters/Cosmetics/B_PickRandomCharacter
其中C++类主要在BeginPlay时做如下操作:
上述代码的作用是在BeginPlay时,将当前的Pawn,或者之后生成的Pawn,应用该蓝图的设置
Lyra中默认使用B_TeamSetup_TwoTeams来定义队伍规模,其蓝图参数为:
位于
Lyra/Plugins/GameFeatures/ShooterCore/Content/Game/B_TeamSetup_TwoTeams
B_TeamSetup_TwoTeams继承自C++类ULyraTeamCreationComponent,其主要逻辑如下:
位于
Lyra\Source\LyraGame\Teams\ULyraTeamCreationComponent.h
可以看出该代码的作用是:当创建Experience或BeginPlay时,在服务器上根据参数TeamsToCreate去生成队伍。
该组件用于控制如何在出生点 生成 团队角色
蓝图B_TeamSpawningRules的继承关系是
B_TeamSpawningRules->
UTDM_PlayerSpawningManagmentComponent(C++)->
ULyraPlayerSpawningManagerComponent(C++)
其中主要的操作是:
上述代码的逻辑是:
- 加载关卡时,把Level中的所有ALyraPlayerStart存起来
- 在World中生成Actor时,如果是ALyraPlayerStart,就存起来
- 把当前World中的所有ALyraPlayerStart存起来
该组件提供了接口ChoosePlayerStart,该接口将根据所有的ALyraPlayerStart挑选Player的出生点
其中挑选逻辑的位于函数OnChoosePlayerStart()中,该函数为虚函数,其中子类UTDM_PlayerSpawningManagmentComponent的实现为:
需要注意的是接口ChoosePlayerStart,将由ALyraGameMode::ChoosePlayerStart_Implementation()调用,它又是由GameModeBase提供的接口:
在Lyra中,它的触发时机主要是C++内部调用AGameModeBase::RestartPlayer(AController* NewPlayer),部分用例如下:
蓝图B_ShooterBotSpawner有以下参数:
可以看出上面的参数指定了:
- 机器人生成的数量
- 机器人类
- 机器人随机名称池
其C++类中的挂载操作为:
可以看出,代码的逻辑是:加载Experience后在服务器上创建对应数量的机器人
-
在如下目录新建Level——L_MyLevel
-
搭建基础场景
-
用网格简单搭建关卡地形,这里简单加了个地板,中间加了个立方体
-
在地图的四个角落放置LyraPlayerStart
注意不是普通的PlayerStart,否则Lyra覆盖的WorldSetting将报错:
-
在文件夹Plugins\ShooterCore\Experiences下新建蓝图类,继承自C++类LyraExperienceDefinition,命名为B_MyExperience
-
构建MyExperience
-
添加GameFeature—ShooterCore
-
设置Pawn Data
-
设置Action Set
-
设置Experience的加载操作
-
添加计分板UI
绑定到HUD中
-
添加技能
包括角色的血量,伤害,复活机制
-
添加组件
-
加入音频管理组件
-
加入控制点计分组件
-
加入出生点控制组件
-
加入团队分组组件
-
自定义角色模型生成组件
-
加入AI生成器—B_ShooterBotSpawner_ControlPoint
-
配置完毕
-
-
-
加入导航体积,并包裹住场景
Waiting
Waiting