Intro
Hi, I’m Reuben Dunnington, a Systems Engineer here at Believer. Today we’ll be discussing debug menus, especially in the context of multiplayer games. I’m really passionate about empowering other team members to self-solve problems they have, and debug menus are a great way to do this. They help make systems:
- Observable: allowing runtime inspection of data and objects in the game
- Discoverable: browsable UIs allow users to find information they didn’t previously know existed
- Tweakable: change data and object properties at runtime for faster iteration
One key facet of these menus is that they must be easy to tweak and change as the needs of the project change. Since gameplay requirements change all the time, the menus must be easy to change as well.
Unreal Gameplay Debugger
Unreal has lots of debug menus, and one that is designed to be hookable by users in a game project is the Gameplay Debugger. It’s the default option you got if you want to go with stock Unreal. There are several systems with their debug menus using the gameplay debugger, such as AI or GAS. Here’s a shot of it in action, ripped from the link:
As you can see, it has basic debug text drawing, some in-worldspace drawing capabilities, can respond to user input, and is replication-enabled so you can get visibility of server-side state. Overall though, I’m not a huge fan of this system for several reasons.
- The UI drawing primitives are very basic (see
FGameplayDebuggerCanvasContext
), where we can only draw text or images with manual positioning. There is an escape hatch for “other things” (FCanvasItem
), but - The controls are undiscoverable, where you either must use a numpad by default (good luck if your keyboard doesn’t have one!) or console commands. No option for mouse-driven UI.
- The mechanics of getting server-side state replicating to the client involve writing a bunch of tedious boilerplate serialization code. Also, every time your server state changes, you have to update the serialization code.
- There’s no way to send commands back to the server, like “do X damage to enemy” or “make me invincible” or “teleport me to POI X” - we’re back to ye olde
serverexec <command>
. ☹️
Overall it’s a relatively primitive debug menu system that could really use some love from Epic.
Dear ImGui
My favorite popular option for writing dev menus in C++ is the amazing Dear ImGui library. Due to the immediate-mode nature of it’s API, it shortens the iteration cycle of writing debug menus drastically for a few reasons:
- Just write code to create and run the UI, no separate tools
- Automatic layout of UI elements
- The widgets can directly use runtime data, avoiding any tedious data transforms
Part of the reason writing imgui-style code is so fast is because the API semantically binds the UI drawing code to the logical actions of the user. For example, to create a window, draw a button, and respond to clicked events, I can simply write:
static int Clicked = 0;
if (ImGui::Begin("My Window"))
{
if (ImGui::Button("Click me!"))
{
++Clicked;
}
ImGui::Text("Clicked: %d", Clicked);
ImGui::End();
}
This code is very concise. In other classical retained-mode frameworks (like Slate), I’d have to create a Window
object, then a Button
object, assign it to the window, set it’s label, then hook up a Clicked
event handlers that needs to be its own function, etc.
Imgui in Unreal
Imgui is designed to be an embeddable library so it can be hooked up to any game engine, and Unreal is no exception. There are a few plugins around, but we’ve chosen to use a fork of a relatively popular one: https://github.com/segross/UnrealImGui. Overall it works great.
Note that while the Imgui plugin does allow creating menus in the editor’s edit mode, we’ve chosen to only use Imgui while in-game. Fragmenting the edit mode experience into both Unreal-style and imgui UIs would lead to confusion, so we keep all edit-time tools in Slate and UMG.
To enable the debug menu, we have a customizable hotkey to toggle imgui drawing and capture mouse input. There’s also a little icon and text on the HUD to remind everyone how to open the menu - we’re keeping discoverability in mind here.
One other really cool feature about the Unreal + Imgui workflow is that it is very hot-reload friendly. Unreal 5 has a feature called Live Coding where it will rebuild all modified source files and hot-reload the owning modules. In practice this doesn’t work so well when introducing new data types such as UObject
s or even struct
s, but it works great when iterating on changes to a single function - like an imgui menu. This accelerates the iteration cycle even more, so getting into a hot loop of Modify Code → Hot Reload → Test Changes → Repeat is a snap. As you can see in the video below, the UI updates instantly once Live Coding finishes building and reloading the module.
Debug UI for remote servers
So far Imgui works great for single player games, but what about multiplayer? In Play-In-Editor (PIE) mode, everything works fine because the editor spins up both a client and server world in-process, so the imgui code running in the server context can still issue draws to the global editor window. However, this gets tricky when connecting with an out-of-process server. How are we supposed to have local input and UI on the client while running server-side logic? Enter NetImgui.
NetImgui is a library to remotely display and control Dear ImGui menus with an associated NetImgui Server application.
The general idea is to can integrate NetImgui into your application, then use a separate standalone app to connect to it. The project is intended mainly to reduce clutter for local development, since you can have all your dev menus in a separate window, and it’s definitely useful for that. But we can also do better, and use it to preserve the debug workflow when using a remote server.
We’ve integrated NetImgui with the Unreal plugin and made some modifications to allow it to stream data from one Unreal app to another. Game worlds running in NM_Client
have the ability to connect to a netimgui server, and server worlds listen for those connections. The result is that we get the exact same dev menu experience on a client connected to a remote server that you would running in PIE.
Here’s a short demo of it in action:
Future Improvements
There are still several fixes and features we’d like to make to the system.
- One big problem with NetImgui is the way it actually works - instead of attempting to draw the UI on the client, it streams the raw geometry to the server, which can wind up being a lot of data. For example, having a few windows open with various widgets can result in ~2-20KB/s sent and ~9KB/s received from the remote. Ideally NetImgui would work by sending over snapshots+deltas of UI state instead of geometry, which would likely be much smaller.
- Currently the way Netimgui is implemented is it has the drawing application host the connection to the remote game server, which technically is a netimgui client. This means that only one user can effectively be connected to the remote game server at a time. I believe the reasoning here was so that the stock NetImgui server app could connect to multiple apps, but that doesn’t seem to be a compelling use case since you could just start multiple instances if needed. Ideally the remote game server hosts client connections so that multiple users can connect.
- There are some missing features that still have to be done - for example custom render target support. If you have a custom render target on the server, that data will just get skipped.
- Clipboard support is still missing from this integration.
Closing
Hopefully this has inspired you to make sure you have a good workflow for debug menus, whatever type of project you’re working on.
If you’re interested in using this technology, check out the work we’ve put into the plugin in our OSS Github org at https://github.com/believer-oss/UnrealImGui or drop us a line on our Discord server.
Thanks for reading!
Reuben