# 编程子系统

此文为Programming Subsystems的原创翻译,本文内容版权归原文所有,仅供学习,如需转载望注本文地址,翻译不易,谢谢理解。

概览UE4中的编程子系统。

在UE4中的子系统是自动被实例化的类,这些类有着自己的生命周期,这些类提供易于使用的扩展点,程序员可以立刻使用其蓝图和Python接口,而不用复杂地修改或重写引擎类。

当前支持的子系统生命周期包括:

子系统 继承自
Engine UEngineSubsystem类
Editor UEditorSubsystem类
GameInstance UGameInstanceSubsystem类
LocalPlayer ULocalPlayerSubsystem类

比如,如果你想创建一个类继承这个基类:

class UMyGamesSubsystem : public UGameInstanceSubsystems
1

这会导致:

  1. 在UGameInstance被创建后,一个UMyGamesSubsystem的实例也会被创建。
  2. 在UGameInstance初始化时,子系统会调用Initialize()。
  3. 在UGameInstance被关闭后,子系统会调用Deinitialize()。
  4. 这个时候,如果子系统的引用被丢掉后没有其他引用指向它,那么该子系统会被垃圾回收。

# 使用子系统的原因

有以下几个原因使用编程子系统:

  • 子系统可以节省编程时间。
  • 子系统能帮你避免重写引擎类。
  • 子系统能帮你避免在一个已存在很复杂的类上添加更多API。
  • 子系统能让蓝图通过对用户友好的节点来获取。
  • 子系统能让Python脚本获取,来进行编辑器编程或者编写测试代码。
  • 子系统让代码库模块化和一致化。

子系统在创建插件的时候是很有用的。你不必对代码进行说明来让插件工作。用户只需添加插件到游戏里,然后你就准确地知道插件啥时候被实例化和初始化。最后,你只需集中精力在如何使用UE4提供的API和功能上。

# 使用蓝图获取子系统

子系统会被自动地暴露给蓝图,在理解上下文的节点中,并不需要转换。你需要使用标准UFUNCTION()标记来控制哪些API对蓝图是可用的。

如果你在蓝图上右击,勾选Context Sensitive,然后搜索子系统,你得到的会和下面相似,每个主要类型都有一个分类,然后每个具体的子系统也有一个入口。

Subsystems_01

如果你从上面添加了一个节点,你会得到像下面这样的。

Subsystems_02

# 使用Python获取子系统

如果你在使用Python,你可以使用内置的访问器来获取子系统,就像下面的例子那样:

my_engine_subsystem = unreal.get_engine_subsystem(unreal.MyEngineSubsystem)
my_editor_subsystem = unreal.get_editor_subsystem(unreal.MyEditorSubsystem)
1
2

# 子系统的生命周期

# Engine子系统

class UMyEngineSubsystem : public UEngineSubsystem { ... };
1

在Engine子系统模块加载时,子系统会在模块的Startup()执行后执行Initialize(),在模块的Shutdown()执行后执行Deinitialize()。

这些子系统可以通过GEngine获取:

UMyEngineSubsystem MySubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
1

# Editor子系统

class UMyEditorSubsystem : public UEditorSubsystem { ... };
1

在Editor子系统模块加载时,子系统会在模块的Startup()执行后执行Initialize(),在模块的Shutdown()执行后执行Deinitialize()。

这些子系统可以通过GEditor获取:

UMyEditorSubsystem MySubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>();
1

# GameInstance子系统

class UMyGameSubsystem : public UGameInstanceSubsystem { ... };
1

这些子系统能通过UGameInstance获取:

UGameInstance* GameInstance = ...;
UMyGameSubsystem* MySubsystem = GameInstance->GetSubsystem<UMyGameSubsystem>();
1
2

# LocalPlayer子系统

class UMyPlayerSubsystem : public ULocalPlayerSubsystem { ... };
1

这些子系统能通过ULocalPlayer来获取:

ULocalPlayer* LocalPlayer = ...;
UMyPlayerSubsystem * MySubsystem = LocalPlayer->GetSubsystem<UMyPlayerSubsystem>();
1
2

# 子系统例子

在下面的例子中,我们想通过给游戏添加状态系统来记录获取资源的数量。

我们可以创建UMyGamesGameInstance继承自UGameInstance,然后给它添加IncrementResourceStat()函数。但是我们知道最终,这个团队也想添加其他状态,比如状态聚合器和保存/加载的状态等等。因此,我们想把这些放到一个类中,比如UMyGamesStatsSubsystem。

现在再来回想下,我们可以创建UMyGamesGameInstance然后添加一个UMyGamesStatsSubsystem成员,然后再给它添加一个获取器,并连接Initialize和Deinitialize函数,但是如果这样做会有几个问题:

  • 没有游戏相关的UGameInstance派生类。
  • UMyGamesGameInstance存在,但是它会有大量的函数,给它添加更多的函数并不好。

在一个复杂的游戏里,有充分的理由去继承UGameInstance,但是当有你不需要的子系统时,最好使用子系统,这样可有选择的减少编码。

我们最终创建的类如下:

UCLASS()
class UMyGamesStatsSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()
public:
    // Begin USubsystem
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    // End USubsystem

    void IncrementResourceStat();
private:
    // All my variables
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14