🖥️ Dear ImGui as a raylib Debugging Interface
Here, we will look at using raylib with Dear ImGui. Dear ImGui is a C++ immediate mode GUI library, helpful for creating using interfaces in game development, or standalone apps. We see how you might create a basic debugging interface for your raylib game, using Dear ImGui.
Printing debug message to the console, can be a useful first step in debugging, and libraries like dbg macro, spdlog and fmtlib will help you here. However, for real-time app data or even updating game state while the game is running, something like Dear ImGui will provide a more powerful interface.
In this post, using CMake, we set up a basic raylib app, with a Dear ImGui powered debug mode. In the real world, the debugging interface might be used for game entity introspection as well as updating game state. Here, we keep things simple, to demonstrate the concept. We change the colour of a rendered cube while the game is running, using radio buttons in our Dear ImGui debug window.
🧱 What we're Building
The app with have a main view, which is the way the typical end-user will interact with it. The app will also have a debug view. This will contain a scaled-down version of the main view, to make space for the Dear ImGui debug interface.
Project CMake File
I like using CMake for C++ projects, as it offers a convenient way to handle project dependencies. It is also great for cross-platform projects and integrates well with CI tooling, such as GitHub Actions.
cmake_minimum_required(VERSION 3.16)
project(RaylibImGui LANGUAGES C CXX)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_EXPORT_COMPILE_COMMANDS True)
add_library(raylib_imgui_compiler_flags INTERFACE)
target_compile_features(raylib_imgui_compiler_flags INTERFACE cxx_std_17)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(
raylib_imgui_compiler_flags
INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-pedantic-errors;-Werror;-Wall;-Weffc++;-Wextra;-Wconversion;-Wsign-conversion>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W4>>")
include(cmake/StaticAnalysers.cmake)
enable_clang_tidy()
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${PROJECT_SOURCE_DIR}/cmake")
include(Dependencies.cmake)
raylib_imgui_setup_dependencies()
add_executable(RaylibImGuiApp src/main.cpp src/game/game.cpp)
set_target_properties(RaylibImGuiApp PROPERTIES CXX_CLANG_TIDY
"${CLANG_TIDY_COMMAND}")
target_link_libraries(
RaylibImGuiApp
PUBLIC dbg_macro
fmt
imgui
raylib
rlimgui
spdlog::spdlog_header_only
raylib_imgui_compiler_flags)
target_compile_definitions(RaylibImGuiApp PRIVATE SPDLOG_FMT_EXTERNAL)
target_compile_definitions(
RaylibImGuiApp PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/")
target_include_directories(RaylibImGuiApp PUBLIC "${PROJECT_SOURCE_DIR}/src")
- We include the dependencies in a separate CMake file (
23
-24
). -
SPDLOG_FMT_EXTERNAL
(line38
) can help avoid compile errors when using fmtlib and spdlog in the same project (spdlog will look for an external fmtlib library, instead of building its own). - Defining
ASSETS_PATH
here makes it easier to like the font TTF loaded in C++ code.
In your C++ code which needs a link to an asset path, you can write something like this:
const Font font{LoadFont(ASSETS_PATH "ibm-plex-mono-v19-latin-500.ttf")};
Remembering to place the asset file in the project assets
folder, matching the path written in the CMakeLists.txt
file.
There is a link to the full project code further down the page, in case you want to explore anything here in detail. Let’s have a quick look at that dependencies file, next, before opening up main.cpp
.
👪 CMake Dependencies
We use rlImGui to add Dear ImGui to the raylib project. On top, we have some C++ utility libraries (dbg-macro, fmtlib, and spdlog).
CPM is a marvellous tool for handling dependencies in CMake. It is essentially a convenient wrapper for the CMake FetchContent
module, and comes with handy caching capabilities too.
I use CPM here for libraries which I do not need to build manually. For Dear ImGui and rlImGui, I use the CMake FetchContent
module. This is just because I have caching switched on for CPM, and the source files are saved outside the project (they are shared between all local projects). FetchContent
, on the other hand, places the source files within the project directory, helpful for manual library building.
include(cmake/CPM.cmake)
function(raylib_imgui_setup_dependencies)
message(STATUS "Include Dear ImGui")
FetchContent_Declare(
ImGui
GIT_REPOSITORY https://github.com/ocornut/imgui
GIT_TAG 277ae93c41314ba5f4c7444f37c4319cdf07e8cf) # v1.90.4
FetchContent_MakeAvailable(ImGui)
FetchContent_GetProperties(ImGui SOURCE_DIR IMGUI_DIR)
add_library(
imgui STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp)
target_include_directories(imgui INTERFACE ${imgui_SOURCE_DIR})
include(cmake/CPM.cmake)
message(STATUS "Include dbg-macro")
cpmaddpackage(
"gh:sharkdp/dbg-macro#fb9976f410f8b29105818b20278cd0be0e853fe8"
)# v0.5.1
message(STATUS "Include fmtlib")
cpmaddpackage("gh:fmtlib/fmt#e69e5f977d458f2650bb346dadf2ad30c5320281"
)# 10.x
message(STATUS "Include raylib")
cpmaddpackage("gh:raysan5/raylib#ae50bfa2cc569c0f8d5bc4315d39db64005b1b0"
)# v5.0
message(STATUS "Include spdlog")
cpmaddpackage("gh:gabime/spdlog#7c02e204c92545f869e2f04edaab1f19fe8b19fd"
)# v1.13.0
message(STATUS "Include rlImGui")
FetchContent_Declare(
rlImGui
GIT_REPOSITORY https://github.com/raylib-extras/rlImGui
GIT_TAG d765c1ef3d37cf939f88aaa272a59a2713d654c9)
FetchContent_MakeAvailable(rlImGui)
FetchContent_GetProperties(rlImGui SOURCE_DIR RLIMGUI_DIR)
add_library(rlimgui STATIC ${rlimgui_SOURCE_DIR}/rlImgui.cpp)
target_link_libraries(rlimgui PRIVATE imgui raylib)
target_include_directories(rlimgui INTERFACE ${rlimgui_SOURCE_DIR})
endfunction()
I like to pin GitHub dependencies using a commit hash, instead of a tag. You need a recent CPM.cmake file in your project for CPM to work.
Using raylib on Windows with spdlog
If you are running raylib on Windows and include spdlog in a C++ source file that uses raylib, you might get a duplicate definition compile error. I spotted this running a game build in CI using GitHub Actions. The issue stems from raylib having a windows.h
file, also used by spdlog. There are a few known workarounds mentioned in this GitHub issue.
Including raylib.h
and spdlog/spdlog.h
in this order and adding the preprocessor macros worked for me:
// Windows workarounds for CloseWindow / ShowCursor errors
#if defined(_WIN32)
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#endif
#include <fmt/core.h>
#include <spdlog/spdlog.h>
#undef near
#undef far
#include <raylib.h>
♟️ C++ Time: Main Game Loop
The easiest way to learn raylib is probably to scan through the raylib examples for something that resembles what you need, and using that as your launch point. The other fantastic resource is the raylib cheat sheet (also available as a PDF from that same page). Once you know which function to use, you can pop open the source in your code editor to check how to use it.
I created this demo by merging the 3D Camera Mode example and an external CMake / raylib / rlImgui / imgui example on GitHub by nosqd.
raylib Textures
Textures, in raylib are typically used to paint an external image within the game window. We will use textures here in the debug view. One texture for the scaled-down main view, and another for the Dear ImGui debug interface.
We can use rectangles to define the bounds and mapping for the main window texture:
RenderTexture gameTexture;
gameTexture = LoadRenderTexture(static_cast<int>(windowSize.x),
static_cast<int>(windowSize.y));
constexpr float kDebugScaleUp{1.5F};
debugTexture =
LoadRenderTexture(static_cast<int>(windowSize.x / kDebugScaleUp),
static_cast<int>(windowSize.y / kDebugScaleUp));
const Rectangle source_rectangle{0, // top-left x
-windowSize.y, // top-left y
windowSize.x, // width
-windowSize.y}; // height
const Rectangle destination_rectangle{0,
0,
windowSize.x / kDebugScaleUp,
windowSize.y / kDebugScaleUp};
This code is adapted from the nosqd repo mentioned, above.
Then, to draw the scaled-down main window, in debug mode:
BeginTextureMode(debugTexture);
DrawTexturePro(gameTexture.texture,
source_rectangle,
destination_rectangle,
{0, 0}, // origin
0.F, // rotation
RAYWHITE); // tint
EndTextureMode();
🎨 Creating Dear ImGui Interfaces
Dear ImGui is extremely versatile, with many in-built components. You can style it, though, so probably the only Dear ImGui criticism you will have is that it does not look fantastic out-of-the box. For a debugging user interface, I think it is marvellous (even without adding styling).
Because there are so many components, to get an idea of what it can do, and also how to do it, you should see the Dear ImGui Online Manual created by Pascal Thomet. That site is kind of a breakfast buffet of all the Dear ImGui components. You can hover over a component to see the C++ source needed to create it.
As it happens, the radio button code, for selecting colour, is quite straight-forward:
ImGui::Begin("Game Debug");
ImGui::Text("%s",
fmt::format("FPS: {}", GetFPS()).c_str());
if (ImGui::TreeNode("Cube colour"))
{
int index{0};
for (const std::string &colour : constants::kCubeColourLabels)
{
ImGui::RadioButton(colour.c_str(), &selected_cube_colour, index);
++index;
}
}
ImGui::End();
selected_cube_colour
is an integer, which Dear ImGui will update to match the index of the selected radio button.
Main code structure
int main()
{
SetWindowState(FLAG_MSAA_4X_HINT);
InitWindow(static_cast<int>(windowSize.x),
static_cast<int>(windowSize.y),
constants::kTitle.c_str());
rlImGuiSetup(true);
/* Game Texture code (as above) */
RenderTexture gameTexture;
// TRUNCATED...
while (!WindowShouldClose())
{
BeginDrawing();
rlImGuiBegin();
ClearBackground(DARKGRAY);
if (debugMenu)
{
// Create the main window texture
BeginTextureMode(gameTexture);
ClearBackground(RAYWHITE);
draw_scene(camera,
cube_position,
constants::kCubeColours[static_cast<size_t>(
selected_cube_colour)],
font);
EndTextureMode();
// Create the debug panel texture
BeginTextureMode(debugTexture);
DrawTexturePro(gameTexture.texture,
source_rectangle,
destination_rectangle,
{0, 0},
0.F,
RAYWHITE);
EndTextureMode();
// draw the debug panel
ImGui::Begin("Game Debug");
ImGui::Text("%s", // NOLINT [cppcoreguidelines-pro-type-vararg]
fmt::format("FPS: {}", GetFPS()).c_str());
// TRUNCATED...
ImGui::End();
// Place the debug texture within the panel
ImGui::Begin(
"Game",
&debugMenu,
static_cast<uint8_t>(
ImGuiWindowFlags_AlwaysAutoResize) |
static_cast<uint8_t>(ImGuiWindowFlags_NoResize) |
static_cast<uint8_t>(ImGuiWindowFlags_NoBackground));
rlImGuiImageRenderTexture(&debugTexture);
ImGui::End();
}
else
{
// draw the main view — no need for textures for main view
ClearBackground(RAYWHITE);
draw_scene(camera,
cube_position,
constants::kCubeColours[static_cast<size_t>(
selected_cube_colour)],
font);
}
rlImGuiEnd();
EndDrawing();
}
}
Code is simplified here to highlight the main steps. See the repo (like below) for full code demo.
🙌🏽 Using raylib with Dear ImGui: Wrapping Up
In this using raylib with Dear ImGui post, we had a look at the elements you need to add a debugging, developer tools or testing view to your raylib game using Dear ImGui. More specifically, we saw:
- CMake configuration for a raylib game running Dear ImGui;
- how to use textures in raylib; and
- setting up a C++ game loop in rlImGui with a debug mode.
I hope you found this useful. As promised, you can get the full project code on the Rodney Lab GitHub repo. We have looked at some raylib basics here, and you might also consider using the debug view to help learn raylib. For example, you could link ImGui widgets to the 3D camera parameters to get a grip on how these work. Do let me know how you use this code. Also reach out if there is anything I could improve to provide a better experience for you.
🙏🏽 Using raylib with Dear ImGui: Feedback
If you have found this post useful, see links below for further related content on this site. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X (previously Twitter) and also, join the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Game Dev as well as Rust and C++ (among other topics). Also, subscribe to the newsletter to keep up-to-date with our latest projects.
Top comments (0)