C++中的控制台变量

控制台管理器的总览与创建控制台变量的实现细节。

Windows
MacOS
Linux

控制台命令 是一条用户输入字符串,其可被发送到引擎,而引擎能以某种方式进行回应(如控制台/日志响应,修改内部状态)。 控制台变量 将额外保存一些状态,可通过控制台进行修改。在控制台管理器中注册控制台命令和变量即可得到自动完成和列举,以便获取所有控制台对象的列表(控制台命令Help或DumpConsoleVariables)。因此需要避免使用老旧的Exec接口。中间点中的控制台管理器将控制所有内容和更多内容(如用户输入历史)。

什么是控制台变量?

控制台变量是一种简单的数据类型(如浮点、整数、字符串)。其拥有引擎层面的状态,使用者可对其状态进行读取和设置。 控制台变量拥有命名,使用者在控制台中输入命名时将自动完成拼写。举例而言:

用户控制台输入

控制台输出

描述

MyConsoleVar

MyConsoleVar = 0

变量的当前状态将被打印到控制台中。

MyConsoleVar 123

MyConsoleVar = 123 LastSetBy: Constructor

变量的状态已改变,新状态将被打印到控制台中。

MyConsoleVar ?

Possibly multi line help text.

将控制台变量帮助文本打印到控制台中。

创建/注册控制台变量

引擎创建时需要尽早注册变量。此例显示了最佳操作方法(在任意C++文件中):

static TAutoConsoleVariable<int32> CVarRefractionQuality(
    TEXT("r.RefractionQuality"),
    2,
    TEXT("Defines the distortion/refraction quality, adjust for quality or performance.\n")
    TEXT("<=0: off (fastest)\n")
    TEXT("  1: low quality (not yet implemented)\n")
    TEXT("  2: normal quality (default)\n")
    TEXT("  3: high quality (e.g. color fringe, not yet implemented)"),
    ECVF_Scalability | ECVF_RenderThreadSafe);

我们在此处注册了一个类型为int32的控制台变量,命名为r.RefractionQuality、默认值为2,并包括一些多行帮助文本和一些标签。 标签有很多个,而最重要的则是ECVF_Cheat。其在IConsoleManager.h中有详细讲解。 使用者在控制台变量后使用“?”即可显示帮助文本。

如有需要,也可以在函数中生成一个控制台变量:

IConsoleManager::Get().RegisterConsoleVariable(TEXT("r.RefractionQuality"),
   2,
   TEXT("Defines the distortion/refraction quality, adjust for quality or performance.\n")
    TEXT("<=0: off (fastest)\n")
    TEXT("  1: low quality (not yet implemented)\n")
    TEXT("  2: normal quality (default)\n")
    TEXT("  3: high quality (e.g. color fringe, not yet implemented)"),
   ECVF_Scalability | ECVF_RenderThreadSafe);

IConsoleManager::Get() 是全局访问点。可在此处注册控制台变量,或寻找现有的控制台变量。第一个参数是控制台变量的命名。 第二个参数是默认值。此常量的类型不同,创建的控制台变量类型也有所不同:整数、浮点或字符串(!FString)。 下个参数将定义控制台变量帮助文本。

也可以注册一个对现有变量的引用。这种方法方便快速但会绕过多项功能(如线程安全、回调、sink、cheat),因此不建议使用此方法。请参考下例:

FAutoConsoleVariableRef CVarVisualizeGPUSimulation(
    TEXT("FX.VisualizeGPUSimulation"),
    VisualizeGPUSimulation,
    TEXT("Visualize the current state of GPU simulation.\n")
    TEXT("0 = off\n")
    TEXT("1 = visualize particle state\n")
    TEXT("2 = visualize curve texture"),
    ECVF_Cheat
    );

其类型可由变量类型推断而出。

获取控制台变量的状态

获取以 RegisterConsoleVariableRef 创建的控制台变量的状态十分便利,使用其注册时使用的变量即可。举例而言:

// 只在未处于相同cpp文件中时需要
extern TAutoConsoleVariable<int32> CVarRefractionQuality;
// 获取游戏线程上的值
int32 MyVar = CVarRefractionQuality.GetValueOnGameThread();

使用Getter函数(即!GetInt()、!GetFloat()、!GetString())来确定控制台变量状态会导致实现速度稍慢(虚拟函数调用、可能的缓存丢失等)。 要获得最佳性能,应使用用于注册变量的相同类型。为获取变量的指针,可保存注册函数的返回参数或在需要变量之前调用 FindConsoleVariable。范例如下:

static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("TonemapperType")); 
int32 Value = CVar->GetInt();

此处的静态将确保命名搜索(实现为地图)只在此代码首次调用时完成。 这是正确的,因为变量从不会移动,只会在引擎关闭时被销毁。

如何追踪控制台变量修改

如果希望在控制台变量变化时执行一些自定义代码,可在三个方法中进行选择。

通常最简单的方法即是最佳方法:可将旧状态保存在子系统中,并每帧检查其是否有所不同。可在此处自由控制其发生时间(如渲染线程或游戏线程、流送线程、tick或渲染前后)。 检测到差异时复制控制台变量状态并自定义代码。范例如下:

void MyFunc()
{
    int GBufferFormat = CVarGBufferFormat.GetValueOnRenderThread();

    if(CurrentGBufferFormat != GBufferFormat)
    {
        CurrentGBufferFormat = GBufferFormat;

        // 自定义代码
    }
}

也可注册一个控制台变量sink。范例如下:

static void MySinkFunction()
{
    bool bNewAtmosphere = CVarAtmosphereRender.GetValueOnGameThread() != 0;

    // 默认假定状态为true
    static bool GAtmosphere = true;

    if (GAtmosphere != bNewAtmosphere)
    {
        GAtmosphere = bNewAtmosphere;

        // 自定义代码
    }
}

FAutoConsoleVariableSink CMyVarSink(FConsoleCommandDelegate::CreateStatic(&MySinkFunction));

sink将在渲染前主线程上的一个指定点处调用。函数不会获取控制台变量命名/指针,因为这通常会导致错误行为。 如多个控制台变量(如r.SceneColorFormat、r.GBufferFormat)皆会触发修改,则最好在所有修改完成后调用代码,而非一个接一个调用。

最后一种方法是使用回调,建议尽量不使用。如不谨慎使用,可能会引起问题:

  • 循环可能出现死锁(可以防止死锁出现,但使用哪个回调则不明确)。

  • 回调可在 !Set() 被调用中的任意时间处返回。代码必须在所有情况下(在初始化中、在序列化中)均能使用。 可假定其固定处于主线程一侧。

不建议使用此方法,除非用上述其他方法已无法解决。

范例:

void OnChangeResQuality(IConsoleVariable* Var)
{
    SetResQualityLevel(Var->GetInt());
}

CVarResQuality.AsVariable()
    ->SetOnChangedCallback(FConsoleVariableDelegate::CreateStatic(&OnChangeResQuality));

控制台变量预期行为和风格

  • 控制台输入应反映用户输入,并不一定反映系统的状态(例如部分平台可能不支持!MotionBlur 0/1)。 变量状态不应由代码修改。否则变量不含用户指定的状态时用户会无端怀疑自己错误输入, 又或者用户可能因其他变量的状态而无法对控制台变量进行修改。

  • 请务必提供解释,说明变量的用途和恰当的值。

  • 多数控制台变量仅适用于开发,因此建议尽早指定 ECVF_Cheat 标签。 能够使用定义来编译出功能则更佳(如#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST))。

  • 变量命名应尽量简短,描述性强,不应使用否定含义。 (举例而言,较差的命名有!EnableMotionBlur、!MotionBlurDisable、MBlur、!HideMotionBlur)。 使用大小写,使命名统一、易读(如!MotionBlur)。

  • 为便于缩进,可假定为固定的宽度字体(非比例)输出。

  • 在引擎初始化中注册变量十分重要,这样自动完成、!DumpConsoleCommands和!Help才能正常工作。

请阅读 IConsoleManager.h 了解这部分详情。

加载控制台变量

引擎启动时,可从文件 Engine/Config/ConsoleVariables.ini 加载控制台变量的状态。此位置预留给本地开发者,不应用于项目设置。 文件自身中有更多详情:

; 利用此文件当前可在引擎启动时设置控制台变量(排序未定义)。
; 当前无其他文件覆盖此文件。
; 此文件应在源码管理数据库中(便于进行注释和了解在何处进行查找)
; 变量中不应含空格。
; 开发者可在本地对其进行修改,避免输入重复的控制台变量设置,
; 以节约时间。须将变量放在名为[Startup]的部分中。
; 之后可能会有多个命名部分被部分命名所引用。
; 这会实现平台或关卡特定的覆盖。
; 命名对比不区分大小写;如果变量不存在,其便会被略过。
;
; 文本内容范例:
;
; [Startup]
; FogDensity = 0.9
; ImageGrain = 0.5
; FreezeAtPosition = 2819.5520 416.2633 75.1500 65378 -25879 0

[Startup]

可将设定放在 Engine/Config/BasEngine.ini 中。范例如下:

[SystemSettings]
r.MyCvar = 2

[SystemSettingsEditor]
r.MyCvar = 3

设置也可来自 Script/Engine.RendererSettings。这些项目设置公开如下:

UPROPERTY(config, EditAnywhere, Category=Optimizations, meta=(
    ConsoleVariable="r.EarlyZPassMovable",DisplayName="Movables in early Z-pass",
    ToolTip=”是否在早Z阶段渲染可移动对象。需要重新加载关卡!"))
    uint32 bEarlyZPassMovable:1;

这些设置可在编辑器UI中修改。项目设置不应与可延展性设置混合(避免出现优先级问题)。

其他设置可来自可延展性功能。查看 Config/BaseScalability.ini 或可延展性文档了解更多信息。

命令行

命令行允许设置控制台变量、调用控制台命令或exec命令。范例如下:

UE4Editor.exe GAMENAME -ExecCmds="r.BloomQuality 12;vis 21;Quit"

可在此处执行3个命令。注意:以此方式设置控制台变量需要省略掉ini文件中需要的“=”。

优先级

控制台变量可从多个源处覆盖,如用户/编辑器/项目设置、命令行、consolevariables.ini等为能够重新指定部分设置(如可在编辑器UI中修改的项目设置)而保持指定的覆盖(如从命令行),我们加入了优先级。 现在所有设置均能按任意排序来应用。

参见IConsoleManager.h:

//最低优先级(控制台变量创建后为默认)
ECVF_SetByConstructor =         0x00000000,
// 来自Scalability.ini
ECVF_SetByScalability =         0x01000000,
//(在游戏UI或来自文件)
ECVF_SetByGameSetting =         0x02000000,
// 项目设置
ECVF_SetByProjectSetting =      0x03000000,
// 逐设备设置
ECVF_SetByDeviceProfile =       0x04000000,
// 逐项目设置
ECVF_SetBySystemSettingsIni =   0x05000000,
// consolevariables.ini(针对多个项目)
ECVF_SetByConsoleVariablesIni = 0x06000000,
// 减法命令,如-VSync 
ECVF_SetByCommandline =         0x07000000,
// 用处不大,就像是hack一样,也许找到正确的SetBy更好
ECVF_SetByCode =                0x08000000,
// 编辑器UI或游戏/编辑器中的控制台
ECVF_SetByConsole =             0x09000000,

一些情况下可能会出现此日志:

Console variable 'r.MyVar' wasn't set (Priority SetByDeviceProfile < SetByCommandline)

这可能是正常情况(如命令行强制进行用户设置),也可能是因一些代码问题而造成。 优先级也有所帮助,可以查看最后设置变量的是谁。获取控制台变量状态时即可获得此信息。范例如下:

> r.GBuffer

r.GBuffer = "1"      LastSetBy: Constructor

未来更新

  • 现版本中控制台变量只能在C++中创建,但未来情况可能会不同。

  • 我们考虑过添加列举和布尔类型,但存在诸多问题。现在我们建议使用整数,必要时使用字符串。

  • 帮助文本虽然方便,但为了节约可执行文件大小并增加作弊的难度,我们考虑在未来加入一个定义,防止帮助文本进入可执行文件。

取消注册控制台变量

UnregisterConsoleVariable 方法可移除控制台变量。至少从使用者角度而言是如此。 变量仍被保留(带未注册的标签),使指针访问数据时不会发生崩溃。如新变量以相同命名注册,旧变量将被恢复,并从新变量复制标签。 这样DLL加载和卸载便能正常工作,变量状态也不会丢失。 注意:这无法用于控制台变量引用。

欢迎来到全新虚幻引擎4文档站!

我们正在努力开发新功能,包括反馈系统,以便您能对我们的工作作出评价。但它目前还未正式上线。如果您对此页面有任何意见与在使用中遭遇任何问题,请前往文档反馈论坛告知我们。

新系统上线运行后,我们会及时通知您的。

发表反馈意见