Using raylib with Dear ImGui: Game Dev Debugging UI

Rodney Lab - Apr 6 - - Dev Community

🖥️ 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

Using raylib with Dear ImGui: screen capture shows a red cube sitting on a large wireframe grid, in the centre of the screen.  At the top, left there is a rendering frames per second readout.  Below that, text reads 'Press F 9 for I m G u i debug mode'.

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.

Using raylib with Dear ImGui: screen capture shows a blue cube sitting on a large wireframe grid, in the centre of a large panel.  A smaller panel sits to the left of the main one.  This smaller panel has a list of colours with blue (matching the colour of the cube) selected.  At the top, left of the main panel, there is a rendering frames per second readout.  Below that, text reads 'Press F 9 for I m G u i debug mode'.

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")
Enter fullscreen mode Exit fullscreen mode
  • We include the dependencies in a separate CMake file (23-24).
  • SPDLOG_FMT_EXTERNAL (line 38) 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")};
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

♟️ 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};
Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

🎨 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();
Enter fullscreen mode Exit fullscreen mode

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();
        }
}
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .