0%

InsideUE5阅读笔记GamePlay

InsideUE5阅读笔记GamePlay架构篇

InsideUE5阅读笔记GamePlay架构篇

《InsideUE4》基础概念 - 知乎 (zhihu.com)

UE5文件目录

  • Binaries:存放编译生成的二进制文件(gitignore)

  • Config:配置文件

  • Content:蓝图、资源等

  • DervedDataCache(DDC):引擎针对平台特化后的资源版本

  • Intermediate:各种中间文件

    • Build中间文件
    • UHT预处理的.generated.h/.cpp文件
    • VS项目文件
    • AssetRegistryCache缓存文件,uasset资源注册表
  • Saved:自动保存文件,日志,烘焙数据等

  • Source:代码文件

 

命名约定

  • T:模板类TArray,TMap,TSet
  • U:UObject派生类
  • A:AActor派生类
  • S:SWidget派生类
  • I:抽象接口
  • E:枚举类
  • b:bool变量
  • F:其他类型——FString,FName

 

UObject

UE游戏世界的所有类基类,提供元数据、反射生成、GC垃圾回收、序列化、编辑器可见,Class Default Object等。动态创建。

一句话介绍:大多数与游戏逻辑、资源管理相关的类都继承自UObject,UObject为这些类提供了反射、序列化、GC等功能,AActor就是其中之一。Actor通过组装Component来实现一些具体的功能,同时也可以通过UChildActorComponent实现父子嵌套(SceneComponent有transform,所以理论上SC会被当成RootComponent,而且父子嵌套的实质也是SceneComponent之间的嵌套)。同样继承于UObject的ULevel中含有所有Actor的引用,Level也就是关卡,也可以理解为大地图上的一块区域,多个Level组成一个World。再往上走,World由WorldContext来进行切换,切换world的本质是切换PersisitentLevel。world之上就是GameInstance了。

 

AActor

继承自UObject,额外提供Replication(网络复制),Spawn(生命周期),Tick(更新)功能。此外,Actor提供TArray存储子Actor。

Actor不提供Transform,因为有许多不需要带有坐标的object也是Actor,比如世界设置对象,游戏模式对象等,UE不需要这些没有坐标的对象独立划出空间来存储“坐标”来浪费空间。

 

Component

UActorComponent的简称,是所有Compnents的基类。

在AActor中,有一个TSet<UActorComponent*> OwnedComponents,保存着这个Actor所拥有的所有Component,一般其中会有一个SceneComponent作为RootComponent。

Actor中支持包含多个SceneComponent,也就是一个Actor其实可以代表多个实体

TArray<UActorComponent*> InstanceComponents,存储被实例化的Components。

如果Actor要想被放进Level中,必须要有实例化USceneComponent(源码中声明为“RootComponent”),在这个Component中,包含TransformSceneComponent相互嵌套功能。

虚幻引擎遵循组合优于继承的概念,所以只有带有Transform的SceneComponent拥有嵌套功能,其他组件不能相互继承嵌套。

Actor之间有父子关系的概念,但是Actor并不关心父子关系,Actor只负责对象的网络同步,创建销毁等功能,父子关系由SceneComponent负责,因为他又Transform,在3D坐标世界上就能理解了。所以Actor的类“AddChildren”的方法“Child:AttachToActor“是间接通过调度”Child:AttachToComponent“来创建父子关系的。

 

Level

由多个Level组成一个World,是Actor的容器,供玩家活动的场景,包含静态网格体(Static Mesh)、体积(Volume)、光源(Light)、蓝图(Blueprint)。

Level的释放和加载是动态的,类似于Unity引擎中的”场景“,Level可以视为闯关类游戏中的会切换场景的关卡,也可以视为吃鸡游戏中将大地图动态进行划分的区域(不可视区域没必要加载)。这样的粒度也方面团队协作。

  • ALevelScriptActor:顾名思义即Level自己的脚本,可以在这个脚本内获取关卡内所有的Actor,可以编写这个Level的相关游戏逻辑。
  • AWorldSettings:AInfo类的类都不直接放入world中,这个worldsetting只和本Level相关,内含各种Level相关的配置设置,例如GameMode等

世界场景设置(World Settings) | Unreal Engine (52vr.com)

  • AInfo:那些再world中没有物理展示的Actor基类,保存world中设置数据的管理类,也可以作为实现复制意图的Actor使用。

ALevelScriptActor意思是关卡的脚本Actor,用于执行Level中的逻辑操作,而WorldSetting继承于AInfo,用于操作游戏世界的设置。

在Level中,WorldSetting记录本Level中的所有规则属性,Level记录了Level内的所有Actor。而Actor内也存储WorldSettings和LevelScriptActor。


ULevel内的Actor排序

ULevel内有一个TArray<AActor*> Actors字段存储关卡内所有Actor,遍历Actors会很花时间,所有Level内有一个排序Actor的方法ULevel::SortActorList(),他会单独把网络对象和非网络对象分开,网络对象放在后面,并使用索引进行标记,可以加速网络复制的检测速度。(AWorldSettings是静态数据提供者,是放在最前列的)

于Unity的异同

Unity中和Level很像的场景概念叫Scene

  • Scene之间独立,一个Scene包含它自己需要的对象、组件、光照等,切换场景时完全卸载;Level可以由多个Sublevel组成,多个Sublevel流式加载到同一个主关卡。虽然Unity也支持流式加载,但Unity的Scene加载需要在代码中手动管理控制,没有向UE那样直观。

UE5中被World Partition替换,自动将世界分块,根据玩家视角与距离动态加载卸载

  • Scene本身没有逻辑,要完成一些关卡相关的逻辑必须单独挂GameObject加脚本逻辑;Level有单独的关卡蓝图脚本系统,如ALevelScriptActor。

 

World

在World中用SubLevel的方式将Level拼接起来。

PersisitentLevel:一开始就加载进来的Level

StreamingLevels:动态加载进来的Level

Levels:当前已经加载进来的Level

当然可以一口气把单独的Level放进PersisitentLevel里,也可以按照流式加载进行区分,如果是后者则World里有一个叫CurrentLevel的引用,在编辑器里指向其他Level,而运行时只指向PersistentLevel。

相关WorldSetting以PersistentLevel为主,也有单独为特定Level配置的设置。

Level之间不会共享Actor的引用,但是碰撞时全局的,也就是说所有的Actor物理实体是在World当中的。

 

WorldContext

namespace EWorldType
{
    enum Type
    {
        None,		// An untyped world, in most cases this will be the vestigial worlds of streamed in sub-levels
        Game,		// The game world
        Editor,		// A world being edited in the editor
        PIE,		// A Play In Editor world
        Preview,	// A preview world for an editor tool
        Inactive	// An editor world that was loaded but not currently being edited in the level editor
    };
}

各种不同的World类型

WorldContext就是管理这些不同World的工具,负责World之间切换的上下文,也负责Level之间切换的操作信息。

 

GameInstance

继承于UObject。

保存WorldContext和其他的整个游戏的信息,贯穿游戏始终,在整个游戏内保持一致。

惯常的做法是继承GameInstance,并编写那些能作用于整个游戏范围的逻辑,比如玩家的单机分数之类,这些是不随关卡退出而刷新的,比如联网逻辑或者全局游戏设置等。以此来”扩展“GameInstance的功能,可以在UE中配置自定义的GameInstance。

 

Engine

分化为两个子类:UGameEngine和UEditorEngine。

UGameEngine指游戏运行时逻辑的引擎类,当游戏打包启动的时候负责游戏的核心逻辑。

UEditorEngine指编辑器状态的逻辑引擎类,负责创建和编辑游戏内容。

启动PIE状态由UEditorEngine负责,但是启动之后的PIE状态本身就是由UGameEngine负责。

 

GamePlayStatics

蓝图静态方法的暴露类,方便操作蓝图数据库。

 

Pawn

Possess然后传递Input

继承于AActor。即“可操控的棋子”。

pawn定义了三个模板方法接口:

  • 可被Controller控制
  • PhysicsCollision表示
  • MovementInput基本响应接口

对于Input,Actor本身能接受input事件,如wasd事件,但是接受事件之后如何进行响应Actor就没有继续进行处理了。而Pawn额外包装了一层MovementInput,可以理解为默认地增加了“按下w之后会向前移动”的逻辑接口。

一句话介绍:Pawn就是一个特殊的Actor,它具备“可被控制”、“物理Collision表示”、“移动响应MovementInput”接口三个基本功能,可以对这三个功能进行定制化,派生出“默认的DefaultPawn”、”观战SpectatorPawn“、”人型Character“。AController也是特殊的Actor,用于控制Pawn,可以派生出APlayerController和AAIController。APlayerState继承于AInfo,AInfo是没有物理表示的AActor,APlayerState于AController绑定在一群,充当网络复制的数据状态。

 

DefaultPawn

默认的Pawn,带有默认的DefaultPawnMovementComponent、spherical CollisionComponent和StaticMeshComponent。

 

SpectatorPawn

继承于DefaultPawn,将Movement改为了“USpectatorPawnMovement(不带重力漫游)”,关闭了StaticMesh显示,定义了一个“观战摄像机”行为。

 

Character

继承于Pawn,人型pawn。

相当于带人型骨架的Pawn,同样带有移动、碰撞体、mesh三部分,不过是人型的。

 

Controller

继承于AActor

Controller于Pawn是一一对应的,也就是说如果碰上需要一次性操控多个Pawn,或者多个Ctr控制单个Pawn的情况,需要进行特殊处理。有种鼓励人们去进行扩展的意思,毕竟一对一的模式更加方便引擎自身进行项目管理和错误筛查。

和Unity的不同

Unity是GameObject+Component的模式,自由组合,对此并没有任何定义,也就是说控制器、角色本体、甚至MVC模式设计模式本身都需要从0开发。这给了设计者非常大的自由性。

UE规定好UObject、Actor、Component的各自职责,衍生出了Pawn+Controller的角色控制模式,更加强调类之间的层级关系。

Controller与Pawn

  • Pawn与Controller都掌管Actor“运动“的概念,其中Pawn更强调”运动的能力“,例如碰撞检测、动画播放、位置更新等;而Controller更强调”运动的决策“,也就是运动自身的逻辑,什么时候该走,该往哪个方向走。
  • Pawn更强调能力,也就是说Pawn更适合编写特地的一些功能逻辑,例如战车与坦克,坦克能开炮,战车不能,所以坦克Pawn需要编写开跑功能;而他们两者都能驾驶,所以可以使用同一个Controller来控制他们两个。
  • Controller更面向玩家,Pawn因为某些情况”死掉了“,例如血条清空等,那么Pawn自身就析构了,而Controller还存在,毕竟实际上玩家还是有权利控制控制器的,只不过可能得不到反馈罢了。所以如果有持久性的例如玩家得分需要进行记录,那么可以放在Controller当中进行逻辑处理。

 

APlayerState

继承于AInfo。AInfo应该不陌生,也就是”没有实体的Actor“。

Controller可以存取玩家状态,就是通过其内部引用的APlayState实现的。

APlayerState与Pawn、Controller是平级的关系,虽然和Controller绑定,但其实有多少实际的玩家才会生成多少APlayerState。

APlayerState独立成一个Actor还能保证网络同步时的数据稳定性,比如UE的网络架构中,如果断开连接,那么Controller就消失了,再次连接上时重新使用APlayerState的数据进行重连就好了。

APlayerState在切换关卡的时候也会被释放掉,所以和GameInstance作区分,照开发实际需求来就好。

 

APlayerController

继承于AController

  • Camera管理——可以知道Camera是挂在Controller上而不是Pawn了
  • Input系统
  • UPlayer关联(LocalPlayer或者UNetConnection)
  • HUD显示
  • Level切换(网络切换Level通过PCtr进行RPC调用后转发到自己的World)
  • Voice

 

 

AAIController

继承于AController

  • Navigation导航
  • AI行为树等其他AI组件
  • Task系统

 

GameMode

由AInfo派生。管控Level相关逻辑。

  • Class登记与实体Spawn:反射出的UClass类型信息就登记在此,用于方便Spawn出新对象。
  • 游戏进度:重启游戏、暂停游戏的支持。SetPause、ResartPlayer函数进行逻辑控制。
  • Level切换:可以指定是否播放开场动画。
  • 多人游戏状态:MatchState指定开始状态结束状态。

Level Blueprint与GameMode:

  • 前者注重关卡内特殊表现,后者注重游戏本体逻辑
  • 简而言之就是通用玩法可以放GameMode里
  • GameMode只在Server中有
  • GameMode与PlayerController职责很像,都是负责游戏玩法,连着和不冲突

 

一句话介绍:除了PlayerState,同样也有其他派生自AInfo的类,对应Level的State叫做”AWorldSetting“,对应一局游戏的State叫做”GameState“。GameMode更侧重于管控Level的逻辑,比如游戏进度等。每个Level都可以独立设置GameMode,也可以直接配置全局的GameMode。GameState代表了一局游戏中的数据,例如玩家一局中的得分等,而GameInstance表示的是贯穿一整个游戏程序的数据。

 

GameState

由AInfo派生,和APlayerState对应,保存当前游戏的状态数据,比如任务数据等。

与GameInstance做区分,GS是一局游戏的数据,而GI是整个游戏程序的数据

 

UPlayer

看到这里的时候不由得感到震惊,感觉UE做的GamePlay框架真的很多

继承于UObject。是Actor与Game层之间的中间层。

级别比World更高,因为哪怕World不断切换,玩家是一成不变的。UPlayer当中置有PlayerController的引用,所以可以理解为两者是相互关联的(即引擎会为Player做输入相应)。UPlayer不代表游戏中的存在,而是确确实实代表玩家本身在Gameplay中的类。

一句话介绍:GameInstance不仅保存World,也存储Player。LocalPlayer代表本地玩家,NetConnection代表远端的连接,负责同步远端的PlayerState。LocalPlayer再GameInstance除四害的适合就默认创建出来,而远程的玩家通过远程同步数据,并不具备输出能力。

ULocalPlayer

UPlayer的派生,即本地玩家。GameInstance具有ULocalPlayer的列表,支持遍历访问实现本地玩家相关操作。

为了顺从UE越来越多的网络联机需求,单独分出来Local的玩家类,比UPlayer多出来了Viewport的功能逻辑,在本地屏幕上给出操作反馈。

LocalPlayer创建流程:

1.初始化GameInstance默认创建GameViewportClient(管理渲染窗口、处理用户输入,与游戏视图和渲染系统交互)。

2.内部转发到GI的CreateLocalPlayer创建LocalPlayer,再创建PlayerController。

3.调用Ctr内部的InitPlayerState,将PlayerState与LocalPlayer对应上来。

4.网络联机中远程同步过来的PlayerState通过Replicated过来。

 

UNetConnection

UPlayer的派生,代表远程同步过来的玩家对象。

相比于ULocalPlayer,少了输出相关的逻辑。

pA8qUSJ.webp

 

GameInstance

继承于UObject。在GameEngine里创建。

  • 引擎初始化,init、shutDown
  • Player的创建,CreateLocalPlayer,GetLocalPlayers
  • GameMode的重载
  • OnlineSession的管理

GameInstance可以被上层的Engine实例出多个的。(理论上)

  • PIE:指在编辑器中按下“Play”后在编辑器的上下文当中运行的游戏,可以实现动态更改场景蓝图变量等。(存有GI)
  • 独立启动游戏:指脱离编辑器运行的游戏,通过编译/打包后运行的可执行文件。(存有GI)
  • 编辑器模式:直接在编辑器中进行操作,可以做到对场景、关卡、甚至动画等的实时浏览功能,但是没有输入相应。(不存有GI)

GI一般能做的业务逻辑:

  • 切换Level相关
  • 自定义生成Player
  • UI相关(一般开发都是由专门的Actor负责管理UMG,但是使用GameInstance管理也是一种思路)
  • 全局配置(游戏设置)
  • 从游戏启动到游戏结束中需要存储的全局变量

 

SaveGame

GameInstance适合存储关卡外存储的数据变量,如果由需要存进内存中的持久性数据,可以考虑继承USaveGame,通过UObject的序列化机制来实现属性字段的序列化保存。

相关接口:

  • CreateSaveGameObject
  • LoadGameFromSlot

 

pA8qtW4.webp

pA8q6YD.jpg