UE4 Inside GamePlay 笔记
架构层
UObject
- 提供UE4基础功能(反射,序列化,GC等)
UComponent
- 基础引擎功能,非逻辑功能
- 以UComponent形式依附于Actor
- UComponent都是平行关系,除了SceneComponent可以嵌套
- SceneComponent 提供 Transform
AActor
- Actor类似一个容器,提供创建销毁功能
- UComponent形式依附于Actor
- 父子关系基于SceneComponent
ULevel
- 当前的所有Actor保持在Level中的Actors
- Actors[0]存放AWroldSettingsActor
- NetActor放在Actors后面
- iFirstNetRelevantActor标记NetActor起始位置
- ALevelScriptActor继承于AActor,但是不能添加Component,除了引擎默认添加的UInputComponent
FWorldContext
- UE4世界中每种模式(EWorldType)
- None 默认,未定义
- Game 游戏世界
- Editor 在编辑器中的世界
- Play In Editor 的世界
- 编辑器工具的预览世界,如material editor里的世界
- 未加载的世界
- ThisCurrentWorld 指向当前的World
- 在World切换时,FWorldContext保存World的上下文
- 一般来说不需要直接操作这个类
- Wolrd里保存着Level切换的上下文
- 负责World之间的切换
- 负责Level之间的切换
GameInstance
- GameInstance 是 WorldContexts上层类
- GameInstance保存着当前的WorldConext和其他整个游戏的信息
- GameInstance 独立于 Level 的切换
- Level的切换数据应该保持在GameInstance
UEngine
- UEngine有两个子类
UGameEngine
a. StandloneGmae模式下使用
b. 创建唯一的GameWorld
c. 在其中直接保存GameInstance指针UEditorEngine
a. 编辑器模式下使用
b. EditorWorld只是用来预览的,所以没有OwningGameInstance
c. PlayWorld里的OwningGameInstance 间接保存GameInstance
逻辑层
Component
- 组织一个Actor
- 单一功能
- 与游戏无关
Actor
cocos
- 直接在Node中写逻辑
unity
- 以Componet模式,在MonoBehavior中写逻辑
UE4
- 从actor派生出特定类写逻辑
- Actor主要是提供基础功能,创建销毁,网络同步等
InputComponent 处理接受事件所以放在actor上
- 通过EnableInput(PlayerController)添加InputComponet
- 创建的InputComponet将会存在PlayerController.CurrentInputStack数组中
Pawn
- 用来被玩家控制的
- 可被Controller控制
- PhysicsCollision表示
- MovementInput的基本响应接口
DefaultPawn
Base类,默认带了3个方法
- DefaultPawnMovementComponent
- SphericalCollisionComponent
- StaticMeshComponent
SpectatorPawn
摄像机漫游功能
- USpectatorPawnMovement 不带重力的漫游
- StaticMesh 关闭
Character
人形移动
- USkeletalMeshComponent
- UCharacterMovementComponent
- UCapsuleComponent
Controller
- Controller和Pawn默认1:1
- Controller一个时间只能控制一个Pawn,但是可以切换Pawn
- 1对1的关系更简洁且可以满足绝大数情况的需求
- 可以通过基础Controller自己实现同时控制多个Pawn
- Controller 不嵌套
- 控制没有必要嵌套
- 控制的分层可以通过外部工具(状态机,分层状态机,行为树,GOAL)进行
UE4 隐藏了 Controller 渲染功能
Controller的位置意义
- 可以在切换Pawn时,为Pawn提供信息
- bAttachToPawn 可以让Controller跟随Pawn移动
- Controller中的逻辑
- 从概念上,Pawn上是执行,Controller上如何执行
- 如果是Pawn上特有的执行逻辑,可以在Pawn上执行
- Controller生命周期比Pawn长,Pawn可以死后,又重生,这时Pawn的数据需要延续的话,可以放在Controller中
APlayerState
- 记录PlayController的信息
- 在服务器上记录
- 虽然主要是记录PlayController的数据,但是AIController也可以读取到
- 记录在Level中
GameMode
- Class登记:GameMode里登记了游戏里基本需要的类型信息
- 游戏内实体的Spawn
- 游戏进度
- Level的切换或World的切换(第一人称,第三人才)
- 多人游戏的步调同步
- 游戏是由一个个World组成的,World又是由Level组成的
World: 玩法或逻辑
Level:场景或表示
- 一个游戏一般是一个玩法多种表现
- 一个World 多个 Level
- World包含多个Level
- 多个Level配置不同的GameMode时采用的是哪一个GameMode?
- 一个World只会有一个GameMode
- 当有多个Level的时候,一定是PersisitentLevel和多个StreamingLevel
- UE也只会为第一次创建World时加载PersisitentLevel的时候创建GameMode
- Level迁移时GameMode是否保持一致?
- bUseSeamlessTravel不开启,当前的GameMode释放掉,加载新的GameModeClass
- bUseSeamlessTravel开启, 加载时使用GetSeamlessTravelActorList 获取GameMode
- 哪些逻辑应该写在GameMode里?哪些应该写在Level Blueprint里?
- Level是表示,World是逻辑,跨Level的逻辑放在GameMode中(胜利条件,怪物刷新),Level中的逻辑在LevelScriptActor中(Actor的轨迹)
- 多个Level之间通用的玩法在GameMode中
- GameMode只在Server上,Client上没有GameMode, GameMode中不要又Client逻辑,LeveiScriptActor中可以有,通过RPC同步
- 跟下层比较,GameMode关系游戏本身玩法,PlayerController关系玩家行为
- 跟上层GameInstance比较,GameInstance关注更高层的不同World之间的逻辑,GameMode之间协调工作交给GameInstance,GameMode只专注自己的玩法世界
GameState
- 和APlayerState一样,GameState保存当前游戏的状态数据
- MatchState同步游戏状态
- PlayerArray 保存当前Client1的玩家列表,用来通过网络和其他Client交换数据
GameSession
一个方便的管理类,并不存储数据
Player
- UPlayer继承自UObject
- Player可以存在于多个Level中,所以不集成AActor
ULocalPlayer
- UGameInstance-CreatePlayer->CreatePlayerController->InitPlayerState
- PlayerState可以是网络的,也可以是本地的
- 为何不在LocalPlayer里编写逻辑?
- 在UE中Level和World有所需的所有数据,PlayerController可以获取到数据,在PlayerController最宜
UNetConnection
- 包含Socket的IpConnection也是玩家
- UNetConnection的列表保存在UNetDriver
- 再到FWorldContext
- 最后也依然是UGameInstance
- 和LocalPlayer的列表一样,是在World上层的对象
GameInstance
引擎的初始化加载,Init和ShutDown等(在引擎流程章节会详细叙述)
Player的创建,如CreateLocalPlayer,GetLocalPlayers之类的。
GameMode的重载修改,这是从4.14新增加进来改进,本来你只能为特定的某个Map配置好GameModeClass,但是现在GameInstance允许你重载它的PreloadContentForURL、CreateGameModeForURL和OverrideGameModeClass方法来hook改变这一流程。
OnlineSession的管理,这部分逻辑跟网络的机制有关(到时候再详细介绍),目前可以简单理解为有一个网络会话的管理辅助控制类。
- GameInstance只有一个吗?
- Game模式下只有一个,Editor模式下可以有多个
- 哪些逻辑应该放在GameInstance?
- World的切换 或 Level的打开
- 设置Player
- UI的公用逻辑,UI在Wolrd系统之外
- 全局配置(根据平台改变一些游戏的配置,Execute一些ConsoleCommand)
- 游戏的额外第三方逻辑
SaveGame
- 先在内存中写入一些SavegameFileVersion之类的控制文件头
- 然后再序列化USaveGame对象,接着会找到ISaveGameSystem接口
- 最后交于真正的子类实现文件的保存
- 目前的默认实现是FGenericSaveGameSystem
总结
Subsystems
Subsystems是什么?
- Subsystems是一套可以定义自动实例化和释放的类的框架。这个框架允许你从5类里选择一个来定义子类
- 自动实例化
- 托管生命周期
Subsystems的基本使用
- 定义C++子类
//声明定义:
UCLASS()
class HELLO_API UMyEditorSubsystem : public UEditorSubsystem
UCLASS()
class HELLO_API UMyEngineSubsystem : public UEngineSubsystem
UCLASS()
class HELLO_API UMyGameInstanceSubsystem : public UGameInstanceSubsystem
UCLASS()
class HELLO_API UMyWorldSubsystem : public UWorldSubsystem
UCLASS()
class HELLO_API UMyLocalPlayerSubsystem : public ULocalPlayerSubsystem
//注:使用UEditorSubsystem需要在Build.cs里加上EditorSubsystem模块的引用,因为这是编辑器模块
if (Target.bBuildEditor)
{
PublicDependencyModuleNames.AddRange(new string[] { "EditorSubsystem" });
}
- 像普通的UObject类一样,可以在里面定义属性和函数
UCLASS()
class HELLO_API UMyScoreSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public://重载的函数,可以做一些初始化和释放操作
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
virtual void Initialize(FSubsystemCollectionBase& Collection)override;
virtual void Deinitialize()override;
public:
UFUNCTION(BlueprintCallable)
void AddScore(float delta);
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Score;
};
- 就可以在C++和蓝图里访问这些类和调用函数了
- C++里的访问
//UMyEngineSubsystem获取
UMyEngineSubsystem* MySubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
//UMyEditorSubsystem的获取
UMyEditorSubsystem* MySubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>();
//UMyGameInstanceSubsystem的获取
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(...);
UMyGameInstanceSubsystem* MySubsystem = GameInstance->GetSubsystem<UMyGameInstanceSubsystem>();
//UMyWorldSubsystem的获取
UWorld* World=MyActor->GetWorld(); //world用各种方式也都可以
UMyWorldSubsystem* MySubsystem=World->GetSubsystem<UMyWorldSubsystem>();
//UMyLocalPlayerSubsystem的获取
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player)
UMyLocalPlayerSubsystem * MySubsystem = LocalPlayer->GetSubsystem<UMyLocalPlayerSubsystem>();
- 蓝图库函数(USubsystemBlueprintLibrary里的函数虽然不暴露在蓝图端,但也是可以在C++端方便调用的)
//我省略了一些宏标记和注释,因为函数名字是不言自明的。
UCLASS()
class ENGINE_API USubsystemBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static UEngineSubsystem* GetEngineSubsystem(TSubclassOf<UEngineSubsystem> Class);
static UGameInstanceSubsystem* GetGameInstanceSubsystem(UObject* ContextObject, TSubclassOf<UGameInstanceSubsystem> Class);
static ULocalPlayerSubsystem* GetLocalPlayerSubsystem(UObject* ContextObject, TSubclassOf<ULocalPlayerSubsystem> Class);
static UWorldSubsystem* GetWorldSubsystem(UObject* ContextObject, TSubclassOf<UWorldSubsystem> Class);
static ULocalPlayerSubsystem* GetLocalPlayerSubSystemFromPlayerController(APlayerController* PlayerController, TSubclassOf<ULocalPlayerSubsystem> Class);
};
- 蓝图内的访问
如何理解Subsystems的生命周期?
- Subsystem对象的生命周期取决于其依存的Outer对象的生命周期,随着Outer对象的创建而创建,随着Outer对象的销毁而销毁
为什么要引进Subsystems?
- 其实就是通过引擎钩子
- 不需要自己处理生命周期
- 在一个GameInstance里可以通过子类,将功能分开