Unreal Engine - Create console commands in blueprints
Normally, one of the only ways to create custom console commands in Unreal Engine is in C++ with the EXEC UFUNCTION specifier.
UFUNCTION(Exec)
void MyConsoleCommand();
But this generally sucks for other important use cases:
- Unable to create commands for systems entirely in blueprints.
- If it’s a networked game, have to manually route the function call with an RPC (unless you use
ServerExec
) - Has to be defined in a class which extends
FExec
, and if it doesn’t you need to extend it and make sure the commands are correctly routed.
Here’s how to make commands entirely in blueprints
(and also have them automatically be RPCs)
There are two parts to getting a function to run through the console:
- Making the blueprint events be reachable through a
ProcessConsoleExec
chain. - Having the console be aware of the blueprint events so it can autocomplete. (this is optional but helps with discoverability). The section below describes how this works.
1. Understanding how the console works (a part of it)
UConsole
is the class which represents Unreal’s console system.
UConsole has this function
/** Build the list of auto complete console commands */
virtual void BuildRuntimeAutoCompleteList(bool bForce = false);
and inside there is this snippet of code
for (TObjectIterator<UFunction> It; It; ++It)
{
UFunction* Func = *It;
/* ... */
if ((Func->HasAnyFunctionFlags(FUNC_Exec) /* ... */ )
{
/* ... */
const int32 NewIdx = (Idx < AutoCompleteList.Num()) ? Idx : AutoCompleteList.AddDefaulted();
AutoCompleteList[NewIdx].Command = FuncName;
/* ... */
}
/* ... */
}
This snippet iterates through all loaded functions and checks to see if they have the flag FUNC_Exec
i.e UFUNCTION(Exec)
. If it does, add it to the auto complete list. Therefore, if we can make sure our functions have the FUNC_Exec
flag when BuildRuntimeAutoCompleteList
is called, they’ll be discovered!
By looking for where BuildRuntimeAutoCompleteList
is called, we can see one occurrence is inside UWorld::InitializeActorsForPlay
. So, as long as our functions are ready before hitting play, we’re good.
2. Define a UConsoleCommands class
This is going to be the class which we’ll extend in blueprints to hold our commands. I made it an ActorComponent for the sole reason they can automatically replicate when attached to Actors. We could have it extended UObject
, but that would require more boilerplate code.
Create a ConsoleCommands.h
file and define the class:
UCLASS(Abstract, Blueprintable, BlueprintType)
class UConsoleCommands : public UActorComponent
{
GENERATED_BODY()
public:
UConsoleCommands();
};
Now create a ConsoleCommands.cpp
file to define the constructor. This is where we want to modify our functions to have the FUNC_Exec
flag.
As for why - Unreal has the concept of “Class Default Objects” or CDOs. These objects exist as defaulted instances of a class which are used throughout the engine. They are created as soon as a class is loaded, so for a blueprint class every time they are compiled they are recreated, which in turn causes the constructor will run. We can therefore use the constructor to change our functions before UWorld::InitalizeActorsForPlay
calls UConsole::BuildRuntimeAutoCompleteList
In the constructor, we’ll iterate through all of the functions our class has (which includes the blueprint only events) and give them the FUNC_Exec
flag.
UConsoleCommands::UConsoleCommands()
{
SetIsReplicatedByDefault(true);
for (TFieldIterator<UFunction> It(GetClass()); It; ++It)
{
// Make sure the function belongs to us instead of UActorComponent.
// Its functions will show up here since we extended it.
if (GetClass()->IsChildOf(Cast<UClass>(It.GetStruct())))
{
It->FunctionFlags |= FUNC_Exec;
}
}
}
UConsoleCommands
should also be replicated so we can send Server RPCs as well
3. Create a UConsoleCommandsManager
UConsoleCommandsManager
is going to be an ActorComponent which lives on the PlayerController. It is responsible for spawning our UConsoleCommands
objects and routing the commands to these objects.
ConsoleCommandsManager.h
class UConsoleCommands;
UCLASS(Within = PlayerController, BlueprintType)
class UConsoleCommandsManager: public UActorComponent
{
GENERATED_BODY()
public:
UConsoleCommandsManager();
virtual void BeginPlay() override;
virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override;
UPROPERTY(EditDefaultsOnly)
TArray<TSubclassOf<UConsoleCommands>> ConsoleCommandsClasses;
private:
UPROPERTY(Transient, Replicated)
TArray<UConsoleCommands*> ConsoleCommandsObjects;
Some quick notes -
ConsoleCommandsClasses
is going to be the list of classes we create onBeginPlay
.- We’ll store these objects inside
ConsoleCommandsObjects
. This list replicates so we can refer to their references on the client. ProccessConsoleExec
will route the command to the correctUConsoleCommands
object based on the function name.
ConsoleCommandsManager.cpp
UConsoleCommandsManager::UConsoleCommandsManager()
{
SetIsReplicatedByDefault(true);
}
void UConsoleCommandsManager::BeginPlay()
{
Super::BeginPlay();
// We only want to spawn the objects on the server, so they replicate down to the client.
if (!GetOwner()->HasAuthority())
{
return;
}
for (const TSubclassOf<UConsoleCommands>& Class : ConsoleCommandsClasses)
{
if (Class != nullptr)
{
ConsoleCommandsObjects.Add(NewObject<UConsoleCommands>(GetOwner(), Class));
}
}
}
bool UConsoleCommandsManager::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor)
{
for (UConsoleCommands* Object : ConsoleCommandsObjects)
{
if (Object->ProcessConsoleExec(Cmd, Ar, Executor))
{
return true;
}
}
return Super::ProcessConsoleExec(Cmd, Ar, Executor);
}
void UConsoleCommandsManager::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UConsoleCommandsManager, ConsoleCommandsObjects);
}
And that’s it for the manager. When a console command is entered which matches a blueprint event name inside one of our ConsoleCommands
objects, it’ll be triggered. If the event is a Server RPC, it’ll be routed to the server automatically!
One last piece of glue code is we need to attach UConsoleCommandManager
to our PlayerController and call ProcessConsoleExec
in the correct place.
4. Setup the PlayerController
In your already existing C++ PlayerController class (if you don’t have one you should make one now), override ProcessConsoleExec
and attach our new manager.
// MyPlayerController.h
UCLASS()
class AMyPlayerController : AController
{
AMyPlayerController();
virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override;
UPROPERTY(EditDefaultsOnly)
UConsoleCommandsManager* ConsoleCommandsManager;
/* ... */
};
// MyPlayerController.cpp
AMyPlayerController::AMyPlayerController()
{
ConsoleCommandsManager = CreateDefaultSubobject<UConsoleCommandsManager>(TEXT("ConsoleCommandsManager"));
}
bool AMyPlayerController::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor)
{
bool bHandled = Super::ProcessConsoleExec(Cmd, Ar, Executor);
if (!bHandled && ConsoleCommandsManager != nullptr)
{
bHandled |= ConsoleCommandsManager->ProcessConsoleExec(Cmd, Ar, Executor);
}
return bHandled;
}
And that’s it! We can now author functioning console commands with blueprints.
In practice
In order to make a command with our new system:
-
Create a new blueprint class which extends
UConsoleCommands
-
Create custom blueprint events with reasonable names which represent your console commands.
-
The names will be used in the autocomplete in the console, so I would recommend not to use spaces.
-
These events can also take input such as strings or numbers!
-
Add your new class to the
ConsoleCommandsClasses
list inside theConsoleCommandsManager
in your blueprint PlayerController. -
Hit play and use them!
Note - when first creating these commands, you might see some duplicate commands in the console. These will go away when you restart the editor.