Illusion Engine 07 - ImGui

Introduction

Game engine editors play a crucial role in the development of video games. They provide a graphical user interface for game developers to create and manipulate game assets, such as characters, levels, and environments, without having to write code. The editor acts as a central hub for all of the game’s content and assets, making it easier for developers to collaborate and experiment with different ideas. Additionally, the use of an editor can significantly speed up the game development process.

In order to create an editor in our engine, we choose to use ImGui library to write various windows of the editor. ImGui library has already been included in Illusion/Lib/imgui… folder and configured in premake.lua file. (The configuration of third-party library is in article Preparation)


Implementation

Essentially, we want to create an editor interface that does not logically belong to the content of the gameplay level, instead it belongs to the debug level, so we want it to be rendered at the end. The Overlay Layer mentioned above can just meet this requirement. Therefore, we can first write an ImguiLayer, which is specially used to put Imgui-related rendering logic.

Before we actually code the ImGuiLayer class, we have to define a macro to enable ImGui. To make the header file clearly, we put the defination of this macro in a single cpp file. Thus, Inside Illusion/src/Engine/ImGui/… folder, create a file called ImGuiBuild.cpp and enter:

1
2
3
4
#include "pch.h"

#define IMGUI_IMPL_OPENGL_LOADER_GLAD
#include "examples/imgui_impl_opengl3.cpp"

After that, we are supposed to use ImGui library as usual. Inside the same folder, create two files ImGuiLayer.h/.cpp, and enter:

ImGuiLayer.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#pragma once

#include "Engine/Core/Layer/Layer.h"
//--------------------namespace: Illusion starts--------------------
namespace Illusion
{
class ImGuiLayer : public Layer
{
public:
ImGuiLayer();
~ImGuiLayer();

virtual void OnAttach() override;
virtual void OnDetach() override;

virtual void OnImGuiRender() override;

void Begin();
void End();
};
//--------------------namespace: Illusion ends--------------------
}
  • ImGuiLayer is inherited from Layer Class so that it could be pushed into the layerstack and be updated and rendered as overlay layers.

ImGuiLayer.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "pch.h"

#include "ImGuiLayer.h"

#include "imgui.h"
#include "examples/imgui_impl_glfw.cpp"
#include "examples/imgui_impl_opengl3.h"

#include "Engine/Core/Application/Application.h"

//--------------------namespace: Illusion starts--------------------
namespace Illusion
{

ImGuiLayer::ImGuiLayer()
: Layer("ImGuiLayer")
{ }

ImGuiLayer::~ImGuiLayer() { }

// Call when it is put into the layerstack
void ImGuiLayer::OnAttach()
{
//Set up the ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
// Enable keyboard controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// Enable docking
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Enable Multi_Viewports
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;

// Set up ImGui Style
ImGui::StyleColorsLight();
//ImGui::StyleColorsDark();

// When viewports are enabled, tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}

// Setup Platform/Renderer bindings
Application& app = Application::Get();
GLFWwindow* window = static_cast<GLFWwindow*>(app.GetWindow().GetNativeWindow());

// Initialize the implementation of OpenGL3
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 410");
}

// Call when it is taken from the layerstack
void ImGuiLayer::OnDetach()
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}

void ImGuiLayer::Begin()
{
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}

void ImGuiLayer::End()
{
ImGuiIO& io = ImGui::GetIO();
Application& app = Application::Get();
io.DisplaySize = ImVec2((float)app.GetWindow().GetWidth(), (float)app.GetWindow().GetHeight());

// RENDERING
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
}

void ImGuiLayer::OnImGuiRender() { }
//--------------------namespace: Illusion ends--------------------
}
  • OnAttach() function basically just sets up the render environment for ImGui.
  • Begin() and End() controls the rendering environment for ImGui. We can submit data after Begin() and the ImGui windows would be rendered by calling End();
  • OnImGuiRender() is where we bind and update everything that is related to the editor or debug windows. It would be called between Begin() and End().
  • OnImGuiRender() exists in every layer. The purpose of that is to make sure all layers are able to render their own debug window.

Utilization

In order to put ImGuiLayer into use, we have to revise our Application class:

Application.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
#include "Engine/ImGui/ImGuiLayer.h"
...
//--------------------namespace: Illusion starts--------------------
namespace Illusion
{
class Application
{
...
private:
...
ImGuiLayer* m_ImGuiLayer;
...
};
...
//--------------------namespace: Illusion ends--------------------
}
  • Here we include the ImGuiLayer class into our engine and then create a pointer that stores our ImGuiLayer.

Application.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
//--------------------namespace: Illusion starts--------------------
namespace Illusion
{
...
Application::Application()
{
...
m_ImGuiLayer = new ImGuiLayer();
PushOverlay(m_ImGuiLayer);
}
...
//The function where the app actually starts
void Application::Run()
{
while (m_Running)
{
...
// Render all ImGui windows
m_ImGuiLayer->Begin();
for (Layer* layer : m_LayerStack)
layer->OnImGuiRender();
m_ImGuiLayer->End();
...
}
}
//--------------------namespace: Illusion ends--------------------
}
  • In the constructor of Application, we create an instance of ImGuiLayer and push it into the layerstack;
  • In Run() function, we render all the ImGui related window at once by going throught the layerstack and call every layers’ OnImGuiRender() function.

Conclusion

For now, we create a class that enables us to build our own editor in the engine. Temporarily, we can add some code in ImGuiLayer’s OnImGuiRender() function to see the effect:

1
2
3
4
5
void ImGuiLayer::OnImGuiRender()
{
static bool show = true;
ImGui::ShowDemoWindow(&show);
}

By using such code, a demo window that demonstrates all basic features of ImGui would be rendered

Demo Window


Illusion Engine 07 - ImGui
https://rigel.github.io/ImGui/
Author
Rigel
Posted on
August 24, 2022
Licensed under