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有两个子类
  1. UGameEngine

    a. StandloneGmae模式下使用
    b. 创建唯一的GameWorld
    c. 在其中直接保存GameInstance指针

  2. UEditorEngine

    a. 编辑器模式下使用
    b. EditorWorld只是用来预览的,所以没有OwningGameInstance
    c. PlayWorld里的OwningGameInstance 间接保存GameInstance

逻辑层

Component

  1. 组织一个Actor
  2. 单一功能
  3. 与游戏无关

Actor

  • cocos

    1. 直接在Node中写逻辑
  • unity

    1. 以Componet模式,在MonoBehavior中写逻辑
  • UE4

    1. 从actor派生出特定类写逻辑
    2. Actor主要是提供基础功能,创建销毁,网络同步等
  • InputComponent 处理接受事件所以放在actor上

    1. 通过EnableInput(PlayerController)添加InputComponet
    2. 创建的InputComponet将会存在PlayerController.CurrentInputStack数组中

Pawn

  1. 用来被玩家控制的
  2. 可被Controller控制
  3. PhysicsCollision表示
  4. MovementInput的基本响应接口

DefaultPawn

  • Base类,默认带了3个方法

    1. DefaultPawnMovementComponent
    2. SphericalCollisionComponent
    3. StaticMeshComponent
SpectatorPawn
  • 摄像机漫游功能

    1. USpectatorPawnMovement 不带重力的漫游
    2. StaticMesh 关闭

Character

  • 人形移动

    1. USkeletalMeshComponent
    2. UCharacterMovementComponent
    3. UCapsuleComponent

Controller

  • Controller和Pawn默认1:1
  1. Controller一个时间只能控制一个Pawn,但是可以切换Pawn
  2. 1对1的关系更简洁且可以满足绝大数情况的需求
  3. 可以通过基础Controller自己实现同时控制多个Pawn
  • Controller 不嵌套
  1. 控制没有必要嵌套
  2. 控制的分层可以通过外部工具(状态机,分层状态机,行为树,GOAL)进行
  • UE4 隐藏了 Controller 渲染功能

  • Controller的位置意义

  1. 可以在切换Pawn时,为Pawn提供信息
  2. bAttachToPawn 可以让Controller跟随Pawn移动
  • Controller中的逻辑
  1. 从概念上,Pawn上是执行,Controller上如何执行
  2. 如果是Pawn上特有的执行逻辑,可以在Pawn上执行
  3. Controller生命周期比Pawn长,Pawn可以死后,又重生,这时Pawn的数据需要延续的话,可以放在Controller中

APlayerState

  1. 记录PlayController的信息
  2. 在服务器上记录
  3. 虽然主要是记录PlayController的数据,但是AIController也可以读取到
  4. 记录在Level中

GameMode

  1. Class登记:GameMode里登记了游戏里基本需要的类型信息
  2. 游戏内实体的Spawn
  3. 游戏进度
  4. Level的切换或World的切换(第一人称,第三人才)
  5. 多人游戏的步调同步
  • 游戏是由一个个World组成的,World又是由Level组成的

World: 玩法或逻辑
Level:场景或表示

  1. 一个游戏一般是一个玩法多种表现
  2. 一个World 多个 Level
  3. World包含多个Level
  • 多个Level配置不同的GameMode时采用的是哪一个GameMode?
  1. 一个World只会有一个GameMode
  2. 当有多个Level的时候,一定是PersisitentLevel和多个StreamingLevel
  3. UE也只会为第一次创建World时加载PersisitentLevel的时候创建GameMode
  • Level迁移时GameMode是否保持一致?
  1. bUseSeamlessTravel不开启,当前的GameMode释放掉,加载新的GameModeClass
  2. bUseSeamlessTravel开启, 加载时使用GetSeamlessTravelActorList 获取GameMode
  • 哪些逻辑应该写在GameMode里?哪些应该写在Level Blueprint里?
  1. Level是表示,World是逻辑,跨Level的逻辑放在GameMode中(胜利条件,怪物刷新),Level中的逻辑在LevelScriptActor中(Actor的轨迹)
  2. 多个Level之间通用的玩法在GameMode中
  3. GameMode只在Server上,Client上没有GameMode, GameMode中不要又Client逻辑,LeveiScriptActor中可以有,通过RPC同步
  4. 跟下层比较,GameMode关系游戏本身玩法,PlayerController关系玩家行为
  5. 跟上层GameInstance比较,GameInstance关注更高层的不同World之间的逻辑,GameMode之间协调工作交给GameInstance,GameMode只专注自己的玩法世界

GameState

  1. 和APlayerState一样,GameState保存当前游戏的状态数据
  2. MatchState同步游戏状态
  3. PlayerArray 保存当前Client1的玩家列表,用来通过网络和其他Client交换数据

GameSession

一个方便的管理类,并不存储数据

image

Player

  • UPlayer继承自UObject
    • Player可以存在于多个Level中,所以不集成AActor

ULocalPlayer

  1. UGameInstance-CreatePlayer->CreatePlayerController->InitPlayerState
  2. PlayerState可以是网络的,也可以是本地的

image

  • 为何不在LocalPlayer里编写逻辑?
  1. 在UE中Level和World有所需的所有数据,PlayerController可以获取到数据,在PlayerController最宜

UNetConnection

  1. 包含Socket的IpConnection也是玩家
  2. UNetConnection的列表保存在UNetDriver
  3. 再到FWorldContext
  4. 最后也依然是UGameInstance
  5. 和LocalPlayer的列表一样,是在World上层的对象

image

GameInstance

image

  1. 引擎的初始化加载,Init和ShutDown等(在引擎流程章节会详细叙述)

  2. Player的创建,如CreateLocalPlayer,GetLocalPlayers之类的。

  3. GameMode的重载修改,这是从4.14新增加进来改进,本来你只能为特定的某个Map配置好GameModeClass,但是现在GameInstance允许你重载它的PreloadContentForURL、CreateGameModeForURL和OverrideGameModeClass方法来hook改变这一流程。

  4. OnlineSession的管理,这部分逻辑跟网络的机制有关(到时候再详细介绍),目前可以简单理解为有一个网络会话的管理辅助控制类。

  • GameInstance只有一个吗?
  1. Game模式下只有一个,Editor模式下可以有多个
    image
  • 哪些逻辑应该放在GameInstance?
  1. World的切换 或 Level的打开
  2. 设置Player
  3. UI的公用逻辑,UI在Wolrd系统之外
  4. 全局配置(根据平台改变一些游戏的配置,Execute一些ConsoleCommand)
  5. 游戏的额外第三方逻辑

SaveGame

  1. 先在内存中写入一些SavegameFileVersion之类的控制文件头
  2. 然后再序列化USaveGame对象,接着会找到ISaveGameSystem接口
  3. 最后交于真正的子类实现文件的保存
  4. 目前的默认实现是FGenericSaveGameSystem

image

总结

image

image

Subsystems

Subsystems是什么?

  • Subsystems是一套可以定义自动实例化和释放的类的框架。这个框架允许你从5类里选择一个来定义子类
  1. 自动实例化
  2. 托管生命周期

image

Subsystems的基本使用

  1. 定义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" });
}
  1. 像普通的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;
};
  1. 就可以在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);
};
  • 蓝图内的访问

image

如何理解Subsystems的生命周期?

  • Subsystem对象的生命周期取决于其依存的Outer对象的生命周期,随着Outer对象的创建而创建,随着Outer对象的销毁而销毁

image

image

为什么要引进Subsystems?

  • 其实就是通过引擎钩子
  1. 不需要自己处理生命周期
  2. 在一个GameInstance里可以通过子类,将功能分开