UE控制台命令及帮助处理相关
文档
对于一个(大型)软件来说,文档始终是一个重要功能。例如,vim内置的在线文档就十分轻便基于文本格式,使用普通编辑器查看也不影响阅读),这样让vim的使用非常丝滑。
UE作为一款游戏软件,同样内置了控制台命令,通过这些命令可以交互式执行控制台命令,查看/控制进程状态。更妙的是在输入的时候,控制台会实时根据输入内容提示可能得命令,并且在命令的后面还会有命令对应的帮助文档。
by typing “help” in UE editor Cmd input box, and press Enter, a local web page will be open with all console variables and commands.
IConsoleObject
控制台对象主要分为两大类,一个是命令,一个是变量。
- IConsoleCommand
在IConsoleCommand的直接派生类FConsoleCommandBase中就包含了
class FConsoleCommandBase : public IConsoleCommand
{
public:
/**
* Constructor
* @param InHelp must not be 0, must not be empty
*/
FConsoleCommandBase(const TCHAR* InHelp, EConsoleVariableFlags InFlags)
: Help(InHelp), Flags(InFlags)
{
check(InHelp);
//check(*Help != 0); for now disabled as there callstack when we crash early during engine init
ApplyPreviewIfScalability();
}
// not using TCHAR* to allow chars support reloading of modules (otherwise we would keep a pointer into the module)
FString Help;
};
Help字符串,所以帮助算是一个比较基础的功能。
- IConsoleVariable
在FConsoleVariableBase类中直接包含了Help字段
class FConsoleVariableBase : public IConsoleVariable
{
///....
// not using TCHAR* to allow chars support reloading of modules (otherwise we would keep a pointer into the module)
FString Help;
///....
};
通过一些编程技巧,可以方便的注册一个控制台变量。
static TAutoConsoleVariable<int32> CVarBadDriverWarningIsFatal(
TEXT("r.BadDriverWarningIsFatal"),
0,
TEXT("If non-zero, trigger a fatal error when warning of bad drivers.\n")
TEXT("For the fatal error to occur, r.WarnOfBadDrivers must be non-zero.\n")
TEXT(" 0: off (default)\n")
TEXT(" 1: a fatal error occurs after the out of date driver message is dismissed\n"),
ECVF_RenderThreadSafe);
还可以注册变量修改时的回调,从而执行特定动作。
/**
* Interface for console variables
*/
class IConsoleVariable : public IConsoleObject
{
///...
/**
* Allows to specify a callback function that is called when the console variable value changes.
* Is even called if the value is the same as the value before. Will always be on the game thread.
* This can be dangerous (instead try to use RegisterConsoleVariableSink())
* - Setting other console variables in the delegate can cause infinite loops
* - Setting many console variables could result in wasteful cycles (e.g. if multiple console variables require to reattach all objects it would happen for each one)
* - The call can be at any time during initialization.
* As this cannot be specified during constructions you are not called on creation.
* We also don't call for the SetOnChangedCallback() call as this is up to the caller.
**/
virtual void SetOnChangedCallback(const FConsoleVariableDelegate& Callback) = 0;
///...
};
- 一些约定
一些常见命名约定
///@file: UE5\Engine\Source\Runtime\Core\Private\HAL\ConsoleManager.cpp
// Naming conventions:
//
// Console variable should start with (suggestion):
//
// r. Renderer / 3D Engine / graphical feature
// RHI. Low level RHI (rendering platform) specific
// a. Animation
// s. Sound / Music
// n. Network
// ai. Artificial intelligence
// i. Input e.g. mouse/keyboard
// p. Physics
// t. Timer
// log. Logging system
// con. Console (in game or editor)
// g. Game specific
// Compat.
// FX. Particle effects
// sg. scalability group (used by scalability system, ini load/save or using SCALABILITY console command)
注册
除了代码外,配置文件也可以添加命令帮助。
;UE5\Engine\Config\BaseInput.ini
[/Script/EngineSettings.ConsoleSettings]
MaxScrollbackSize=1024
+AutoCompleteMapPaths=Content/Maps
+ManualAutoCompleteList=(Command="Exit",Desc="Exits the game")
+ManualAutoCompleteList=(Command="DebugCreatePlayer 1",Desc=)
+ManualAutoCompleteList=(Command="ToggleDrawEvents",Desc="Toggles annotations for shader debugging with Pix, Razor or similar GPU capture tools")
+ManualAutoCompleteList=(Command="Shot",Desc="Make a screenshot")
;.....
+ManualAutoCompleteList=(Command="MemReport",Desc="Outputs memory stats to a profile file. -Full gives more data, -Log outputs to the log")
帮助提示
- 结构
/**
* A basic command line console that accepts most commands.
*/
UCLASS(Within=GameViewportClient, config=Input, transient)
class ENGINE_API UConsole
: public UObject
, public FOutputDevice
{
///...
/** Full list of auto-complete commands and info */
TArray<FAutoCompleteCommand> AutoCompleteList;
///...
};
- 生成
void UConsole::BuildRuntimeAutoCompleteList(bool bForce)
{
LLM_SCOPE(ELLMTag::EngineMisc);
#if ALLOW_CONSOLE
if (!bForce)
{
// unless forced delay updating until needed
bIsRuntimeAutoCompleteUpToDate = false;
return;
}
///...
// copy the manual list first
AutoCompleteList.Reset();
AutoCompleteList.AddDefaulted(ConsoleSettings->ManualAutoCompleteList.Num());
for (int32 Idx = 0; Idx < ConsoleSettings->ManualAutoCompleteList.Num(); Idx++)
{
AutoCompleteList[Idx] = ConsoleSettings->ManualAutoCompleteList[Idx];
AutoCompleteList[Idx].Color = ConsoleSettings->AutoCompleteCommandColor;
}
// systems that have registered to want to introduce entries
RegisterConsoleAutoCompleteEntries.Broadcast(AutoCompleteList);
// console variables
{
IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(
FConsoleObjectVisitor::CreateStatic(
&FConsoleVariableAutoCompleteVisitor::OnConsoleVariable,
&AutoCompleteList));
}
///...
// clear the existing tree
//@todo - probably only need to rebuild the tree + partial command list on level load
for (int32 Idx = 0; Idx < AutoCompleteTree.ChildNodes.Num(); Idx++)
{
FAutoCompleteNode* Node = AutoCompleteTree.ChildNodes[Idx];
delete Node;
}
AutoCompleteTree.ChildNodes.Reset();
// copy the manual list first
AutoCompleteList.Reset();
AutoCompleteList.AddDefaulted(ConsoleSettings->ManualAutoCompleteList.Num());
for (int32 Idx = 0; Idx < ConsoleSettings->ManualAutoCompleteList.Num(); Idx++)
{
AutoCompleteList[Idx] = ConsoleSettings->ManualAutoCompleteList[Idx];
AutoCompleteList[Idx].Color = ConsoleSettings->AutoCompleteCommandColor;
}
///...
for (const FString& MapName : Packages)
{
int32 NewIdx = 0;
// put _P maps at the front so that they match early, since those are generally the maps we want to actually open
if (MapName.EndsWith(TEXT("_P")))
{
AutoCompleteList.InsertDefaulted(0, 3);
}
else
{
NewIdx = AutoCompleteList.AddDefaulted(3);
}
AutoCompleteList[NewIdx].Command = FString::Printf(TEXT("open %s"), *MapName);
AutoCompleteList[NewIdx].Color = ConsoleSettings->AutoCompleteCommandColor;
AutoCompleteList[NewIdx + 1].Command = FString::Printf(TEXT("travel %s"), *MapName);
AutoCompleteList[NewIdx + 1].Color = ConsoleSettings->AutoCompleteCommandColor;
AutoCompleteList[NewIdx + 2].Command = FString::Printf(TEXT("servertravel %s"), *MapName);
AutoCompleteList[NewIdx + 2].Color = ConsoleSettings->AutoCompleteCommandColor;
}
}
- 匹配
- 代码
void UConsole::UpdateCompleteIndices()
{
if (!bIsRuntimeAutoCompleteUpToDate)
{
BuildRuntimeAutoCompleteList(true);
}
AutoComplete.Empty();
AutoCompleteIndex = 0;
AutoCompleteCursor = 0;
if (CVarConsoleLegacySearch.GetValueOnAnyThread())
{
// use the old autocomplete behaviour
FAutoCompleteNode* Node = &AutoCompleteTree;
FString LowerTypedStr = TypedStr.ToLower();
int32 EndIdx = -1;
for (int32 Idx = 0; Idx < TypedStr.Len(); Idx++)
{
int32 Char = LowerTypedStr[Idx];
bool bFoundMatch = false;
int32 BranchCnt = 0;
for (int32 CharIdx = 0; CharIdx < Node->ChildNodes.Num(); CharIdx++)
{
BranchCnt += Node->ChildNodes[CharIdx]->ChildNodes.Num();
if (Node->ChildNodes[CharIdx]->IndexChar == Char)
{
bFoundMatch = true;
Node = Node->ChildNodes[CharIdx];
break;
}
}
if (!bFoundMatch)
{
if (!bAutoCompleteLocked && BranchCnt > 0)
{
// we're off the grid!
return;
}
else
{
if (Idx < TypedStr.Len())
{
// if the first non-matching character is a space we might be adding parameters, stay on the last node we found so users can see the parameter info
if (TypedStr[Idx] == TCHAR(' '))
{
EndIdx = Idx;
break;
}
// there is more text behind the auto completed text, we don't need auto completion
return;
}
else
{
break;
}
}
}
}
if (Node != &AutoCompleteTree)
{
const TArray<int32>& Leaf = Node->AutoCompleteListIndices;
for (uint32 i = 0, Num = (uint32)Leaf.Num(); i < Num; ++i)
{
// if we're adding parameters we want to make sure that we only display exact matches
// ie Typing "Foo 5" should still show info for "Foo" but not for "FooBar"
if (EndIdx < 0 || AutoCompleteList[Leaf[i]].Command.Len() == EndIdx)
{
AutoComplete.Add(AutoCompleteList[Leaf[i]]);
}
}
AutoComplete.Sort();
}
}
else if (!TypedStr.IsEmpty())
{
// search for any substring, not just the prefix
static FCheatTextFilter Filter(FCheatTextFilter::FItemToStringArray::CreateStatic(&CommandToStringArray));
Filter.SetRawFilterText(FText::FromString(TypedStr));
for (const FAutoCompleteCommand& Command : AutoCompleteList)
{
if (Filter.PassesFilter(Command))
{
AutoComplete.Add(Command);
}
}
AutoComplete.Sort();
}
}
- 调用链
> UnrealEditor-Engine.dll!UConsole::UpdateCompleteIndices() 行 551 C++
UnrealEditor-Engine.dll!UConsole::AppendInputText(const FString & Text) 行 798 C++
UnrealEditor-Engine.dll!UConsole::InputChar_Typing(FInputDeviceId DeviceId, const FString & Unicode) 行 819 C++
UnrealEditor-Engine.dll!UGameViewportClient::InputChar(FViewport * InViewport, int ControllerId, wchar_t Character) 行 749 C++
UnrealEditor-Engine.dll!FSceneViewport::OnKeyChar(const FGeometry & InGeometry, const FCharacterEvent & InCharacterEvent) 行 1147 C++
UnrealEditor-Slate.dll!SViewport::OnKeyChar(const FGeometry & MyGeometry, const FCharacterEvent & CharacterEvent) 行 302 C++
UnrealEditor-Slate.dll!FSlateApplication::ProcessKeyCharEvent::__l3::<lambda>(const FArrangedWidget & SomeWidgetGettingEvent, const FCharacterEvent & Event) 行 4511 C++
UnrealEditor-Slate.dll!FEventRouter::Route<FReply,FEventRouter::FBubblePolicy,FCharacterEvent,FReply <lambda>(const FArrangedWidget &, const FCharacterEvent &)>(FSlateApplication * ThisApplication, FEventRouter::FBubblePolicy RoutingPolicy, FCharacterEvent EventCopy, const FSlateApplication::ProcessKeyCharEvent::__l3::FReply <lambda>(const FArrangedWidget &, const FCharacterEvent &) & Lambda, ESlateDebuggingInputEvent DebuggingInputEvent) 行 425 C++
[内联框架] UnrealEditor-Slate.dll!FEventRouter::RouteAlongFocusPath(FSlateApplication *) 行 394 C++
UnrealEditor-Slate.dll!FSlateApplication::ProcessKeyCharEvent(const FCharacterEvent & InCharacterEvent) 行 4505 C++
UnrealEditor-Slate.dll!FSlateApplication::OnKeyChar(const wchar_t Character, const bool IsRepeat) 行 4478 C++
UnrealEditor-ApplicationCore.dll!FWindowsApplication::ProcessDeferredMessage(const FDeferredWindowsMessage & DeferredMessage) 行 1974 C++
UnrealEditor-ApplicationCore.dll!FWindowsApplication::DeferMessage(TSharedPtr<FWindowsWindow,1> & NativeWindow, HWND__ * InHWnd, unsigned int InMessage, unsigned __int64 InWParam, __int64 InLParam, int MouseX, int MouseY, unsigned int RawInputFlags) 行 2726 C++
UnrealEditor-ApplicationCore.dll!FWindowsApplication::ProcessMessage(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) 行 1895 C++
[内联框架] UnrealEditor-ApplicationCore.dll!WindowsApplication_WndProc(HWND__ *) 行 919 C++
UnrealEditor-ApplicationCore.dll!FWindowsApplication::AppWndProc(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) 行 925 C++