UEC++学习(十八)使用TAutoConsoleVariable<T> / FAutoConsoleCommand自定义控制台变量/命令
UE常用的添加控制台变量/命令的方式。利用C++的RAII(资源获取即初始化)机制,在构造时自动向控制台管理器注册自己,在析构时自动注销,无需手动管理生命周期。还有一种是使用IConsoleManager手动注册,但是需要手动管理取消注册,否则可能会导致内存泄露问题。
(一)使用TAuroConsoleVariable<T>创建控制台变量
目的:暴露一个可实时修改的配置变量到控制台。它本质上是一个变量,其值可以通过控制台获取和修改,并且当值改变时,可以自动触发一个回调函数
要注意TAuroConsoleVariable定义的变量会自动记录当前值
(1)定义控制台变量:
//GameInstance.CPP中// 声明自定义控制台变量
static TAutoConsoleVariable<int32> CVarShowDebugUI(TEXT("ui.ShowDebugUI"),// 控制台变量名称0, // 默认值TEXT("Show or hide debug UI\n")TEXT("0: Hide (default)\n")TEXT("1: Show"), // 帮助文本ECVF_Cheat); // 标志位static TAutoConsoleVariable<FString> CVarSpawnDebugActor(TEXT("test.SpawnActor"),TEXT(""),TEXT("生成Actor\n")TEXT("Format: ClassName"),ECVF_Cheat);
也可以使用FAutoConsoleVariableRef绑定现有变量:
int32 MyBoundVariable = 50;
float MyBoundFloat = 5.0f;// 将控制台变量绑定到现有整数变量
FAutoConsoleVariableRef CVarRefMyVar(TEXT("MyModule.BoundVar"),MyBoundVariable, // 绑定到现有变量TEXT("绑定到现有整数变量的控制台变量。"),ECVF_Default
);// 将控制台变量绑定到现有浮点数变量
FAutoConsoleVariableRef CVarRefMyFloat(TEXT("MyModule.BoundFloat"),MyBoundFloat,TEXT("绑定到现有浮点数变量的控制台变量。"),ECVF_Default
);
// 现在MyBoundVariable的值会随着控制台命令"MyModule.BoundVar"的修改而自动改变
定义控制台变量时可以使用的常见标志位:
标志位 描述 ECVF_Default
默认值,无特殊标志 ECVF_Cheat
仅在作弊模式下有效,发行版本中通常无效 ECVF_RenderThreadSafe
可在渲染线程中安全访问 ECVF_Scalability
与画面缩放质量相关的设置 ECVF_Unregistered
未注册的变量,不会在控制台中显示 ECVF_ScalabilityGroup
标识为可伸缩性组别的一部分
(2)绑定回调函数
void UTestAdbGameInstance::Init()
{Super::Init();// 注册控制台命令//有两种方式://(1)使用控制台变量Sink注册:会在渲染前的主线程调用//例如要每次获取CVarShowDebugUI变量值的变化FAutoConsoleVariableSink CVarShowDebugUISink(FConsoleCommandDelegate::CreateStatic(&OnSinkBindFunc));//(2)使用SetOnChangedCallback,但是这样方法容易导致死锁问题,在任何线程都会调用CVarShowDebugUI->SetOnChangedCallback(FConsoleVariableDelegate::CreateUObject(this, &UTestAdbGameInstance::OnShowDebugUIChanged));CVarSpawnDebugActor->SetOnChangedCallback(FConsoleVariableDelegate::CreateUObject(this, &UTestAdbGameInstance::OnSpawnDebugActorChanged));}void UTestAdbGameInstance::Shutdown()
{Super::Shutdown();// 取消注册控制台命令CVarShowDebugUI->SetOnChangedCallback(FConsoleVariableDelegate());CVarSpawnDebugActor->SetOnChangedCallback(FConsoleVariableDelegate());}void UTestAdbGameInstance::OnShowDebugUIChanged(IConsoleVariable* Var)
{//获取变量的值int32 ShowDebugUI = Var->GetInt();UE_LOG(LogTemp, Display, TEXT("ShowDebugUI = %d"), ShowDebugUI);
}void UTestAdbGameInstance::OnSpawnDebugActorChanged(IConsoleVariable* Var)
{FString ActorClassStr = Var->GetString();if (!ActorClassStr.IsEmpty()){UE_LOG(LogTemp, Display, TEXT("ActorClass: %s"), *ActorClassStr);}else{UE_LOG(LogTemp, Display, TEXT("ActorClass: Default"));}// 重置变量值// Var->Set(TEXT(""));
}//静态函数
void UTestAdbGameInstance::OnSinkBindFunc()
{int32 Value = CVarShowDebugUI->GetInt();UE_LOG(LogTemp, Display, TEXT("ShowDebugUI = %d"), Value);
}
如果需要在其他地方获取这个变量的值,可以使用:
void UTestAdbGameInstance::TestGetConsoleVar()
{// 通用获取方式(自动检测线程)int32 Value1 = CVarShowDebugUI->GetInt();//自动检测线程int32 AnyThreadValue = CVarShowDebugUI.GetValueOnAnyThread();// 在游戏线程中读取int32 GameThreadValue = CVarShowDebugUI.GetValueOnGameThread();// 在渲染线程中读取int32 RenderThreadValue = CVarShowDebugUI.GetValueOnRenderThread();//使用FindConsoleVariable查找int32 Value = IConsoleManager::Get().FindConsoleVariable(TEXT("ui.ShowDebugUI"))->GetInt();UE_LOG(LogTemp, Display, TEXT("Value1: %d , AnyThreadValue: %d , Value: %d"), Value1,AnyThreadValue,Value);
}
(3)使用控制台命令
在游戏中按下 ~ ,输入:ui.ShowDebugUI 0/1 或者 test.SpawnActor 要生成的actor
(二)使用FAutoConsoleCommand创建控制台命令
目的:注册一个执行特定操作的命令。它不持有状态,而是在用户输入命令时执行一个动作。
类型 委托类型 参数传递 适用场景 无参数命令 FConsoleCommandDelegate
无 执行简单操作,如打印信息、触发简单事件 带简单参数的命令 FConsoleCommandWithArgsDelegate
const TArray<FString>& Args
需要传递一个或多个字符串参数的操作 带世界和参数的命令 FConsoleCommandWithWorldAndArgsDelegate
const TArray<FString>& Args, UWorld* World
需要访问世界上下文和参数的操作 带世界、参数和输出设备的命令 FConsoleCommandWithWorldArgsAndOutputDeviceDelegate
const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar
需要世界上下文、参数并控制输出信息的复杂操作
(1)定义
(1)无参数命令
//不使用Lamdba
static FAutoConsoleCommand CVarMySimpleCommand111(TEXT("MyGame.SimpleCommand111"), // 控制台命令名称TEXT("这是一个简单的无参数命令,用法: test.SimpleCommand"), // 命令帮助说明FConsoleCommandDelegate::CreateStatic(&UTestAdbGameInstance::TestFunc)
);void UTestAdbGameInstance::TestFunc()
{UE_LOG(LogTemp, Display, TEXT("SimpleCommand111"));
}//使用Lambda
static FAutoConsoleCommand CVarMySimpleCommand(TEXT("MyGame.SimpleCommand"), // 控制台命令名称TEXT("这是一个简单的无参数命令,用法: test.SimpleCommand"), // 命令帮助说明FConsoleCommandDelegate::CreateLambda([]() // 使用Lambda表达式处理命令{UE_LOG(LogTemp, Display, TEXT("SimpleCommand"));})
);(2)带简单参数命令
static FAutoConsoleCommand CVarMyCommandWithArgs(TEXT("MyGame.CommandWithArgs"),TEXT("这是一个带参数的命令。用法: MyGame.CommandWithArgs [Param1] [Param2]"),FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args){if (Args.Num() == 0){UE_LOG(LogTemp, Warning, TEXT("请提供参数! 用法: MyGame.CommandWithArgs [Param1] [Param2]"));return;}// 遍历并打印所有参数for (int32 i = 0; i < Args.Num(); ++i){UE_LOG(LogTemp, Log, TEXT("参数[%d]: %s"), i, *Args[i]);}// 示例:根据参数设置某个变量if (Args.Num() >= 1){// 尝试将第一个参数转换为整数int32 MyValue = FCString::Atoi(*Args[0]);// ... 这里用MyValue做一些事情 ...}})
);(3)带世界和参数的命令
static FAutoConsoleCommandWithWorldAndArgs CVarMyCommandWithWorldAndArgs(TEXT("MyGame.CommandWithWorld"),TEXT("这是一个需要世界上下文和参数的命令。"),FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* InWorld){if (InWorld){UE_LOG(LogTemp, Log, TEXT("当前世界: %s"), *InWorld->GetName());}else{UE_LOG(LogTemp, Warning, TEXT("世界上下文为空"));}})
);(4)带世界、参数和输出设备的命令
static FAutoConsoleCommand CVarMyComplexCommand(TEXT("MyGame.ComplexCommand"),TEXT("这是一个复杂的命令,接收参数、世界上下文并能控制输出。"),FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar){// 使用Ar来直接输出信息(会显示在控制台)Ar.Logf(TEXT("开始执行MyGame.ComplexCommand..."));if (Args.Num() < 1){Ar.Logf(TEXT("错误:需要至少一个参数。"));return;}//})
);
获取已注册的命令:
IConsoleManager::Get().FindConsoleObject(TEXT("MyGame.SimpleCommand"))
(2)使用
(1)无参数命令
(2)带简单参数命令
(3)带世界和参数的命令
(4)带世界、参数和输出设备的命令
(三)使用IConsoleManager
(1)定义控制台变量
void UTestAdbGameInstance::TestFunc()
{// 注册一个整数变量IConsoleVariable* CVarDynamicInt = IConsoleManager::Get().RegisterConsoleVariable(TEXT("MyModule.DynamicInt"),0,TEXT("动态注册的整数变量。"),ECVF_Default);
}
(2)定义控制台命令
void UTestAdbGameInstance::TestFunc()
{IConsoleCommand* MyDynamicCmd = IConsoleManager::Get().RegisterConsoleCommand(TEXT("MyModule.DynamicCmd"),TEXT("动态注册的无参数命令。"),FConsoleCommandDelegate::CreateLambda([](){UE_LOG(LogTemp, Log, TEXT("动态命令被执行了!"));}),ECVF_Default);// 注册一个带参数命令IConsoleCommand* MyDynamicCmdWithArgs = IConsoleManager::Get().RegisterConsoleCommand(TEXT("MyModule.DynamicCmdWithArgs"),TEXT("动态注册的带参数命令。"),FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args){UE_LOG(LogTemp, Log, TEXT("收到了 %d 个参数"), Args.Num());}),ECVF_Default);// 当你不再需要这个命令时(例如模块卸载时),记得注销它// ConsoleManager.UnregisterConsoleObject(MyDynamicCmd);// ConsoleManager.UnregisterConsoleObject(MyDynamicCmdWithArgs);
}
(3)查找与控制控制台变量/命令
void UTestAdbGameInstance::TestFunc()
{// 查找一个控制台变量IConsoleVariable* FoundCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ScreenPercentage"));if (FoundCVar){// 获取当前值int32 CurrentValue = FoundCVar->GetInt();UE_LOG(LogTemp, Log, TEXT("r.ScreenPercentage 当前值为: %d"), CurrentValue);// 设置新值(谨慎操作,可能影响其他系统)FoundCVar->Set(120); // 设置为120%}//查找一个控制台命令对象(较少用于直接执行,通常直接输入命令名)IConsoleObject* ConsoleObj = IConsoleManager::Get().FindConsoleObject(TEXT("MyModule.MyCommand"));if (ConsoleObj && ConsoleObj->AsCommand()){// 获取命令信息等}
}
(四)总结
类别 方法 关键特点 适用场景 控制台变量 TAutoConsoleVariable<T>
类型安全,静态初始化,常用 声明全局配置参数、功能开关 IConsoleManager::RegisterConsoleVariable
动态注册,返回指针 在运行时根据需要动态创建变量 FAutoConsoleVariableRef
绑定到现有变量,值自动同步(但会绕过线程安全等功能) 快速将现有变量暴露给控制台 控制台命令 FAutoConsoleCommand
静态初始化,支持无参数、带参数、世界上下文等多种委托 创建执行特定操作或调试的命令 IConsoleManager::RegisterConsoleCommand
动态注册,返回指针,需手动管理生命周期 在插件或模块中动态创建命令,并可卸载 查找与交互 IConsoleManager::FindConsoleVariable
查找已存在的CVar 获取或修改引擎或其他模块定义的变量值 IConsoleManager::FindConsoleObject
查找任何控制台对象(变量或命令) 检查命令或变量是否存在 响应变量变化 轮询检查 (Polling) 简单,可控(例如在Tick中检查) 简单的游戏逻辑响应 FAutoConsoleVariableSink
在主线程特定点(如渲染前)集中处理多个CVar变化 响应引擎关键设置(如渲染质量)变化,需集中处理 IConsoleVariable::SetOnChangedCallback
(谨慎使用)变化立即回调,但需注意线程安全和调用时机 需要立即响应的特殊情况下,并确保线程安全