Creating simple tools in UE4 – part 1

In this series of articles I’m going to show how to create some high-impact, low-effort tools to help develop your game in UE4.  Tools range from simple batch files or humble debug commands to complex ensembles like the UE4 level editor or Max/Maya; but for now let’s focus on some approaches that don’t require a broad knowledge of Unreal Engine or Slate to pull off. Some of these topics deserve a post of their own, but I’m going to start with more of a survey approach and see what people are most interested in digging into.

Programming is all about strategic laziness: the guiding philosophy developing tools is to spend time now to save someone time later, improving overall developer efficiency. This might play out by allowing the user to finish the same tasks in less time or it might allow them to perform more iterations in the same amount of time, increasing polish level and fun factor. Ideally you also save more time than was involved in making the tool, but that is not a hard requirement due to other factors like user morale, making it possible for a different group of people to do the work, reducing mistakes when the cost of failure is high, etc… This is especially true when it comes to automating key processes where a mistake might cause downtime or otherwise hurt your customers, e.g., releasing a broken update or corrupting a database.

In the more typical cases where there’s a reasonable if slow or annoying alternative, estimating the !/$ (bang for the buck) is a pragmatic way to look at whether or not it’s worth making a tool: does this help more than it’s going to cost to create (given very rough estimates of both)? As always, there’s a relevant XKCD strip:

[don’t forget to multiply ‘how often’ by the number of developers impacted; tools that help 50 people on a team are much easier to justify than ones that help 2, much to the distress of audio developers everywhere]

Part 1: Tools to help debug or iterate while the game is running

The debug console can be used to trigger commands and adjust console variables (CVars), and is available both in the editor and the game during development, though it is compiled out of the shipping game by default. It can be brought up by pressing the ` key (configurable in Project Settings), and commands can also be entered into the Output Log in the editor.

  • “Command [Args]” – Executes a console command, which might expect arguments
  • “CVar” – Prints out the current value of a CVar
  • “CVar NewValue” – Sets the value of a CVar to NewValue
  • “Command?”  – Prints out help on the specified command or CVar
  • “Help” – Saves a .html file with all CVars and most console commands

Console Variables (CVars)

Add console variables to tweak existing functionality at runtime or to control debug visualizations.  There are a number of ways to define a console variable (check out IConsoleManager.h), but an important gotcha is that only float, int32, and FString types are supported (use an int32 in places you’d normally use a bool or an enum).  A typical usage is going to be something like this:

#include "HAL/IConsoleManager.h"
int32 GSomeCoolDebugValue = -1;
static FAutoConsoleVariableRef CVarSomeCoolDebugValue(
	TEXT("This cool debug value controls how the foos are displayed\n")
	TEXT("  -1: Don't show any foos (default)\n")
	TEXT("   0: Show only the nearest foos\n")
	TEXT("   1: Show mid-range foos\n")
	TEXT(" >=2: Show all foos, even hidden ones"));
If (GSomeCoolDebugValue >= 0)
	// Show some set of foos based on GSomeCoolDebugValue

Console commands

Console commands let you execute code rather than change a variable value:

#include "HAL/IConsoleManager.h"
static FAutoConsoleCommandWithWorldAndArgs GMyCoolCommand(
	TEXT("Does cool things (this is the help text for the command)"),
	FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World)
		UE_LOG(LogTemp, Log, TEXT("My cool command was passed %d args"), Args.Num());

The example above gets a pointer to the current world but not a lot else to go on. In order to get closer to some game framework classes you can tag reflected member functions with the Exec specifier in the UFUNCTION() declaration (or by checking the Exec box in the BP editor). You can use this markup on functions in any class, but by default console commands are only ‘routed’ to a few kinds of classes (see this wiki page for more info).  The three most interesting places where Exec functions work are pawns, player controllers, and the cheat manager.

Here’s an example that could be triggered by typing, e.g., “GiveHealth 25” when controlling a hypothetical APawnWithHealth instance:

// Heal the pawn for the specified amount
void GiveHealth(float AmountToGive)
	Health = FMath::Clamp(Health + AmountToGive, 0.0f, MaxHealth);

Note that you can use parameters here and they’ll be automatically parsed for you, unlike FAutoConsoleCommandWithWorldAndArgs where you have to parse them yourself.  If a parameter is missing then the default value for the type will be passed in.

Leaving code like this around in your shipping game is not always desirable, especially if it’s a multiplayer game where the client shouldn’t even be handling these sorts of things. Using a cheat manager subclass is a good way to concentrate sensitive debugging commands in one place which will be automatically disabled in Shipping builds.  You can specify your custom cheat manager subclass in the CheatClass property of your player controller.

Remember how I mentioned that Exec functions are only callable from some classes by default?  That’s not entirely true, you can still call functions on arbitrary classes using a special command: “ke ActorName EventName” will trigger the exec command on the specified actor (you can use * to broadcast to all actors that implement the named event).  Similarly, any exec functions in a Level Script Blueprint can be called by using “ce EventName” (ce is short for cinematic event, and this is typically used to skip to specific scenes in a matinee or sequencer cinematic for ease of testing).

Random aside: abtest command

There is an amazingly useful console command called abtest which randomly switches back and forth between two values for a console variable or two console commands.

// Set CVar to value1 or value2 randomly every so often
abtest CVar value1 value2

// Calls Command1 or Command2 with optional arguments randomly every so often
abtest "Command1 Args1" "Command2 Args2 Args3"

// Stops whatever abtest has been running
abtest stop

This is useful when testing out the cosmetic impact of different settings, but it was really designed for performance testing. abtest spits out an estimate of potential time savings and a confidence in that estimate which updates every so often (trust the ‘gut reaction’, usually a real win will show up strongly and consistently very quickly, don’t go fishing for a lucky run that supports your bias).  An important caveat is to make sure the code under test is on the critical path or the results will be meaningless noise due to the pipelined nature of the engine.  There are other ways to do a/b testing in those cases, have a look at the code.

Stay tuned

Part 2 will likely cover the basics of creating a custom asset type, some approaches to data validation, and simple ways to customize the details panel or react to modifications.

4 thoughts on “Creating simple tools in UE4 – part 1”

  1. Glad I stumbled on this. abtest is something I’ll have to try out.

    I have a somewhat related question. I always thought the details pane + simulate should be an ideal debugging/testing environment, but in practice it’s not. It seems for some reason that most everything in the engine is designed with the assumption that properties won’t be changed from the editor during PIE/simulate. It’s pretty easy to break stuff by doing so, especially with the editor’s insistence on recreating components.

    Is there some reasoning behind this? Would it be feasible to have an editor option to disable component reregistering/construction script on property change during simulate? Seems to me that this behaviour is really only useful in the editor world anyway.

    1. It’s probably not something we’ll be adding anytime soon. For every class that you wouldn’t want to rerun the construction script, there are several where you would want to. I think the behavior being consistent in both places is generally useful, especially when combined with the ‘keep simulation changes’ option to push edits back to the editor world (e.g., when adjusting parametric actors you really do want to rerun the construction script in the sim/PIE world to see the expected changes, otherwise pushing an edit back to the editor world may turn out differently to how you expected it to).

      There are only a few engine-level concepts that are fundamentally hosed by a reinstance, and we’d like to reduce those over time (any of them are an impediment to eventually allowing BP recompiles during PIE, or to (slightly more) reliable C++ hot reload). It’s pretty easy to write game-specific code that is fragile to reinstancing though, which we can’t really do anything to prevent. Which classes are you running into the most issues with?

      Michael Noland

      1. True, I’ve never used ‘Keep Simulation Changes’ so didn’t occur that people might want to run procedural things during simulate. Still, seems like an editor-only bool property on AActor/UActorComponent to allow disabling it might be viable?

        I know UPhysicsConstraintComponent was one that was problematic. In fact I have a feeling it’s so fragile that even as a C++ component, the constraint breaks when modifying any property, just from being reregistered without any reinstancing. I’m now in the habit of adding all components at the C++ level where possible to avoid reinstancing issues, so I don’t recall specifically which other components I had issues with. But I’m fairly sure there are a number of engine level components that have runtime state which doesn’t come through a reinstancing intact. Next time I run into it I can let you know if you like.

        1. Please let me know when you come across other problematic components, and I’ll start a thread about the constraint component. I know the component referencing in there has had issues in the past.

          Michael Noland

Leave a Reply

Your email address will not be published. Required fields are marked *