【ue4】【架构】游戏框架
游戏框架
UE4 作为一个游戏引擎, 不仅完成了一个游戏引擎的本分, 还替游戏开发者着想,设计了一套用于游戏开发的框架,
这个游戏框架包含以 UObject
为基类的游戏性类
这些游戏性类构造了一个比较完整的游戏世界,只是留下了空白供使用者填充
由此也可以看出, UE4 的开发模式是基于__继承__而, 而非基于组件的 (u3d)
World 之下
ULevel
ULevel 作为游戏中的关卡, 承载着显示所有游戏中的物体 (AActor) 的使命 -- 属于 MVC 模式中的 View
拥有一个存储所有 AActor 的数组 -- Actors
拥有一个关卡蓝图 (ALevelScriptActor) 的引用
拥有一个关卡设置 (AWorldSettings) 的引用
【Tips】 关卡设置由于历史原因才叫 AWorldSettings, 叫 ALevelSettings 更合理
ALevelScriptActor
继承自 AActor
ALevelScriptActor
提供当前关卡级的逻辑实现 -- 属于 MVC 模式中的 Controller
用来实现当前场景中各个 Actor 之间的交互等问题
【Tips】 只与当前关卡相关的动作 (如在某个位置触发事件) 等才用关卡蓝图, 可复用的逻辑应用类蓝图
【Tips】 关卡蓝图应用于本Level逻辑的实现, 而 GameMode 才是整个完整的游戏逻辑的实现所在
【Tips】 是一个看不见的 Actor
AWorldSettings
AWorldSettings
记录着当前关卡中的各种规则属性 -- 属于 MVC 模式中的 Model
用来配置当前关卡的规则, 包括当前所使用的 游戏模式 (GameMode)
【Tips】 AWorldSettings 的名字由于历史原因这么叫,理论上叫 ALevelSettings 更为合理
AActor
属于 MVC 中的 View
AActor
是真实存在于游戏中的物体, 也包括不可见的游戏规则状态信息等幽灵
Actor 是一个树型结构, 一个 Actor 可以有许多 Children Actor
重写 Actor 的 Tick() 函数, 在游戏的主 Tick()
函数里就会通过多态依次调用
- AActor
- APawn
- ACharacter
- AController
- APlayerStartActor
- ALevelScriptActor
- AInfo
- AStaticMeshActor
- ASkeletalMeshActor
- ACameraActor
Actor 的生命周期基本为 -- BeginPlay -> Tick -> EndPlay
【Tips】 继承自 Actor 的类带有 A
前缀
【Tips】 有些 Actor 是不用显示在游戏中的
APawn
一种特殊的 AActor
可以接受外部的输入 -- InputComponent
可以将外部输入转化为移动及其他 -- MovementComponent
可以由 AController 控制
支持物理碰撞 PhysicsCollision
一些特殊的 APawn
ADefaultPawn -- 默认的 Pawn 方便使用
ASpectatorPawn -- 观战的 Pawn -- 提供一个不带重力的漫游 (USpectatorPawnMovement)
ACharacter -- 人形的 Pawn -- CapsuleComponent (胶囊体) + SkeletalMesh
AController
属于 MVC 中的 Controller
APawn 的控制器 -- 玩家控制的角色本身的行为逻辑
具有动态关联 APawn 的能力 -- Possess 和 UnPossess
Tick 机制
可以在场景中移动
支持事件响应
支持网络同步
【Tips】 可以脱离 APawn 存在 -- 可延迟选择 Posses 哪个 APawn
【Tips】 AController 拥有比 APawn 更长的生命周期 -- 可存放 APawn 消失也想保存下来的数据
APlayerState
属于 MVC 中的 Model
保存一个玩家使用某个 AController 需要的可网络复制的信息
如当前关卡的玩家得分 -- 关卡内的统计数据等
【Tips】 APlayerState 是与 UPlayer 对应的
UActorComponent
UActorComponent 作为一种功能的接口, 提供给 AActor 组装使用
- UActorComponent
- USceneComponent
- UPrimitiveComponent
- UMeshComponent
- UStaticMeshComponent
- UChildActorComponent
- UInputComponent
- UMovementComponent
一般一个 AActor 都有一个 USceneComponent 作为 RootComponent
USceneComponent 顾名思义即场景中的组件, 它有两个主要功能
提供 Transform -- 这样 AActor 才有位置
提供嵌套 -- 其他的 UActorComponent 是不支持嵌套的
【Tips】 Actor 不带 Transform 信息 -- 使用 SceneComponent 添加
World 之中
UWorld
UE 的游戏场景由多个关卡 (Level) 组成, 而 UWorld 则是 Level 的集合及管理者 -- MVC 中的 View
存储所有关卡的引用 - Levels 数组里保存当前已经加载过的 - StreamingLevels 数组里保存整个World的 Levels 配置列表 - PersistentLevel 表示主关卡 - CurrentLevel 表示当前关卡 -- 运行时只能指向 PersistentLevel
控制关卡的加载方式 - Persistent -- 一开始就加载进 World - Streaming -- 后续动态加载进 World
【Tips】 UE4 可以采用 World Composition + 原点偏移 + 基于距离的流式加载 的方式实现无缝大地图
AGameMode
游戏模块 即当前游戏的玩法
是整场游戏的逻辑实现地 -- MVC 中的 Controller
登记常用 Class -- Pawn, HUD, PlayerController, GameState, PlayerState, Spectator
生成游戏实体 -- Pawn Controller 的生成和数目管理等
控制游戏进度 -- SetPause, RestartPlayer 等
切换Level时的决策 -- 哪些Actor需要保存到下一个 Level
【Tips】 切换关卡时会重新生成新的 GameMode
【Tips】 应用于游戏本身的玩法, 玩家的行为控制交给 APlayerController 管理
AGameState
保存当前的状态数据 -- 如任务数据等 -- MVC 中的 Model
可以用来在网络中传播同步 (GameMode 中的不能)
保存玩家的状态列表
【Tips】 AGameMode AGameState AWorldSettings APlayerState 都继承自 AInfo -- 一种可以不在场景中显示出来的 AActor
World 之上
UEngine
位于 Engine\Source\Runtime\Engine\Classes\Engine.h
UEngine
是整个引擎的最高统治者, 控制着整个引擎的生命周期
与 UEngineLoop
配合,控制整个游戏及引擎的 Init Tick Exit
UEngine
负责着对编辑器或者游戏特别重要的系统, 还定义了某些默认的类, 并保存着一些类型的所有对象, 如 WorldList 保存所有的 World
UEngine
只有一个全局变量 GEngine
1 | // Engine\Source\Runtime\Engine\Private\UnrealEngine.cpp 258 |
UEngine
有两个派生类,
分别用于 编辑器 和 游戏运行时
UEditorEngine --
Engine\Source\Editor\UnrealEd\Classes\Editor\EditorEngine.h
UGameEngine --
Engine\Source\Runtime\Engine\Classes\Engine\GameEngine.h
可以新建继承自 UGameEngine
的类, 并在 DefaultEngine.ini
配置文件中指定其为引擎类, 便可通过此类扩展引擎类的功能
UGameInstance
位于 Engine\Source\Runtime\Engine\Classes\Engine\GameInstance.h
在 UE4 的游戏世界里,世界 World 是不止一个的, 不同的 World 有不同的功能
如 Game World 表示游戏运行的场景, PIE 表示在编辑器中运行的游戏场景等
在 Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h
里声明了世界类型的枚举量
1 | // Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h 796 |
目前来说, UE4 只能同时运行一个 World, 所以需要有一个地方来保存当前的 World 信息, 以及控制 World 的切换,
这个地方就是 WorldContext
1 | // Engine\Source\Runtime\Engine\Classes\Engine\Engine.h 292 |
而 GameInstance
就是管理 WorldContext 的地方
1 | // Engine\Source\Runtime\Engine\Classes\Engine\GameInstance.h 110 |
换句话说, GameInstance
比 World 的层次更高
它保存了当前的WorldContext和整个游戏的信息
所以我们用继承 GameInstance
类来处理独立于某些关卡的、应用于整个游戏范围的逻辑
引擎的初始化流程工作
LocalPlayer 的增删管理
重新为某个关卡修改 GameMode
全局的配置、UI、逻辑等
USaveGame
有时候一些全局的变量,往往是需要持久化保存下来的, 而 GameInstance 虽然可以保存在 Level 关卡之外持续存在的变量, 但是对于整个游戏来说需要离线存储的数据, 就不好使用 GameInstance 来保存了。
所以 USaveGame 的作用就类似于离线存档
我们只要继承一下 USaveGame, 然后添加我们想要离线存储下来的字段就可以了
USaveGame 继承自 UObject 的序列化机制自然就会将我们的字段序列化保存下来
1 | // Engine\Source\Runtime\Engine\Classes\GameFramework\SaveGame.h |
可以看到, USaveGame 实际上只是一个 UObject 的空壳, 而所以的存档接口都暴露在 UGamepalyStatic
类里
【Tips】 UGameplayStatic
类是一个蓝图函数库, 说白了就是一堆可让蓝图调用的静态函数
UPlayer
玩家即输入。
之所以把 UPlayer
放在和 UGameInstance
USaveGame
一起, 是因为很多时候, 游戏的输入模式是脱离于场景的, 可能多个场景切换来切换去, 而输入模式是不变的
所以 UPlayer 应该也是立足于 World 之上的存在, 这也是为什么 UPlayer 是继承自 UObject 而非 Actor 的原因吧
1 | // Engine\Source\Runtime\Engine\Classes\Engine\Player.h |
UPlayer 有两个派生类
ULocalPlayer -- 本地玩家 -- 控制 APlayerController 的生成
UNetConnection -- 远程连接玩家
APlayerController
即为 UPlayer 在 游戏场景中的话事者, 但是 APlayerController
的输入则是在它接受 UPlayer 里产生的
1 | // Engine\Source\Runtime\Engine\Private\PlayerController.cpp |
上面的代码说明 APlayerController
调用 SetPlayer()
调用的 InitInputSystem()
才是其可以获得输入的源头
【Tips】一般不在 UPlayer 里写逻辑, 但我们仍然可以继承它并这么做。
游戏启动流程
UE4 为了实现跨平台, 使用一个 GuardedMain()
来封装不同平台上的 main()
函数
GuardedMain()
位于 Engine\Source\Runtime\Launch\Private\Launch.cpp
大体的启动流程如下
1 | int32 GuardedMain() |