게임 엔진에서 에디터는 게임 개발에 필요한 다양한 요소를 시각적으로 배치하고 조정할 수 있는 그래픽 사용자 인터페이스(GUI) 도구입니다. 에디터는 개발자가 게임 세계를 구성하고 테스트하는 과정에서 편의성과 효율성을 높여 주는 중요한 역할을 합니다.
다양한 에디터 예시
에디터란 무엇인가?
에디터는 게임 엔진에 내장된 개발 환경으로, 다음과 같은 주요 기능을 포함합니다.
•
레벨 디자인: 게임의 월드나 맵을 설계하는 도구로, 오브젝트를 배치하거나 지형을 조성하고, 조명 및 이펙트를 추가하는 등 게임 공간을 시각적으로 구성할 수 있습니다.
•
자산 관리: 모델, 텍스처, 사운드, 애니메이션 등 다양한 리소스를 관리하고, 이를 손쉽게 프로젝트에 추가할 수 있는 기능을 제공합니다.
•
스크립팅 및 코드 통합: 코드나 스크립트를 연결하여 오브젝트에 동작을 부여하고, 특정 이벤트를 발생시키는 기능을 제공합니다.
•
UI 및 HUD 구성: 게임 인터페이스와 HUD를 디자인하고 배치할 수 있는 기능을 제공합니다.
•
테스트 및 디버깅: 개발 중에 게임을 테스트할 수 있는 실시간 디버깅 및 프로파일링 도구가 포함되어 있어, 게임의 성능과 오류를 파악할 수 있습니다.
에디터가 필요한 이유
1.
생산성 향상: 에디터는 코드 작성 없이도 오브젝트를 배치하고 조작할 수 있어 개발 속도를 크게 향상시킵니다. 레벨 디자인과 같은 반복 작업도 수월해집니다.
2.
즉각적인 피드백: 에디터에서 시각적으로 결과를 확인할 수 있으므로, 수정을 즉시 반영하고 테스트할 수 있습니다. 이는 개발자가 설정을 변경할 때마다 전체 게임을 빌드할 필요 없이 빠르게 테스트할 수 있게 합니다.
3.
협업 효율성 증가: 에디터는 디자이너, 아티스트, 프로그래머 등 다양한 직군의 사람들이 같은 도구를 사용하여 협업할 수 있게 합니다. 특히 프로그래머가 아닌 디자이너와 아티스트도 에디터를 통해 프로젝트에 기여할 수 있습니다.
4.
디버깅과 최적화 도구 제공: 에디터는 메모리 사용량, CPU/GPU 성능, 충돌 감지 등 디버깅 및 최적화 도구를 통해 개발자가 성능 문제를 발견하고 해결할 수 있게 돕습니다.
5.
사용자 정의 및 확장성: 대부분의 게임 엔진 에디터는 플러그인이나 스크립팅으로 확장할 수 있어, 프로젝트에 맞춰 기능을 커스터마이즈할 수 있습니다. 예를 들어, Unity에서는 C# 스크립팅을 통해 맞춤형 기능을 추가할 수 있습니다.
대표적인 게임 엔진 에디터 예시
•
Unity 에디터: Unity는 게임 오브젝트를 씬에 배치하고, 컴포넌트를 추가하여 동작을 설정할 수 있는 직관적인 인터페이스를 제공합니다. 또한, Play Mode를 통해 에디터에서 바로 게임을 실행해 테스트할 수 있습니다.
•
Unreal Engine 에디터: Unreal Engine 에디터는 높은 그래픽 퀄리티와 강력한 레벨 디자인 도구를 제공하며, 블루프린트라는 비주얼 스크립팅 기능을 통해 프로그래밍 지식이 없는 사람도 게임의 논리를 구성할 수 있습니다.
주의할점
에디터 개발에 지나치게 집중하다 보면, 본래 게임이나 프로그램의 목표를 벗어나 기능이 과도하게 많거나, 불필요한 인터페이스 요소가 추가되는 경우가 생길 수 있습니다. 이런 상황은 에디터 기능이 본 프로젝트의 목적보다 우선시되면서, 정작 중요한 게임플레이와 핵심 기능 개발이 지연되거나 소홀히 다루어질 수 있다는 문제를 야기합니다.
이를 방지하기 위한 방법으로는 다음과 같은 점들을 고려해 볼 수 있습니다:
1.
프로젝트의 주요 목표 설정: 처음부터 명확한 목표를 세우고, 에디터 개발의 범위를 프로그램 본질과 일치시키는 것이 중요합니다. 에디터가 목표를 보조하는 역할에 집중하게 합니다.
2.
에디터의 필수 기능과 부가 기능 구분: 반드시 필요한 기능과 개발 편의를 위한 부가 기능을 구분하여, 필수 기능을 중심으로 에디터를 설계합니다.
3.
사용자 피드백과 테스트 반영: 실제 사용자, 즉 개발팀의 디자이너나 아티스트로부터 피드백을 자주 받고, 에디터가 본질에 맞게 설계되고 있는지 검토합니다.
4.
단계별 기능 릴리스: 에디터 기능을 작은 단위로 추가하며 각 단계가 프로그램 본질을 강화하는지 확인해 나가는 것도 좋은 방법입니다.
에디터가 게임이나 프로그램의 본질을 돕는 도구로서 역할을 하도록 주기적으로 검토하고 조정하는 것이 중요합니다.
그럼 이제 우리 프로그램에 툺프로그래밍이 가능하게 하기 위한 imgui 프로젝트를 추가해 보겠다.
ImGui를 윈도우 프로젝트에 추가하려면 몇 가지 단계를 통해 라이브러리를 통합해야 합니다. 아래는 ImGui를 Visual Studio Windows 프로젝트에 통합하는 방법입니다.
1. ImGui 소스 파일 다운로드
1.
2.
다운로드 후, imgui 폴더 내에 있는 다음 파일들이 필요합니다.
2. 프로젝트에 ImGui 파일 추가
1.
Visual Studio에서 프로젝트 솔루션을 엽니다.
2.
솔루션에 공유항목 프로젝트를 추가해준다.
3.
솔루션 탐색기에서 프로젝트를 마우스 오른쪽 클릭하여 새로 추가 > 기존 항목을 선택합니다.
4.
ImGui의 소스 파일들을 선택하여 프로젝트에 추가합니다:
이제 코드를 추가 해주면 된다.
imgui 는 총 3가지의 과정으로 나누어진다.
1.
초기화
2.
렌더링
3.
메모리 해제
3. ImGui 초기화 코드 추가
이제 ImGui를 초기화하는 코드를 추가해야 합니다. 이 예제에서는 DirectX 11을 사용한다고 가정합니다.
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
//io.ConfigViewportsNoAutoMerge = true;
//io.ConfigViewportsNoTaskBarIcon = true;
//io.ConfigViewportsNoDefaultParent = true;
//io.ConfigDockingAlwaysTabBar = true;
//io.ConfigDockingTransparentPayload = true;
//io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleFonts; // FIXME-DPI: Experimental. THIS CURRENTLY DOESN'T WORK AS EXPECTED. DON'T USE IN USER APP!
//io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleViewports; // FIXME-DPI: Experimental.
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// When viewports are enabled we 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 backends
ImGui_ImplWin32_Init(application.GetHwnd());
ya::graphics::GraphicDevice_DX11*& graphicdevice = ya::graphics::GetDevice();
ID3D11Device* device = graphicdevice->GetID3D11Device().Get();
ID3D11DeviceContext* device_context = graphicdevice->GetID3D11DeviceContext().Get();
ImGui_ImplDX11_Init(device, device_context);
// Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
// - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
// - Read 'docs/FONTS.md' for more instructions and details.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
//io.Fonts->AddFontDefault();
//io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != NULL);
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
C++
복사
4. 윈도우 메시지 처리
윈도우 메시지를 ImGui 백엔드에 전달하여 입력 이벤트가 ImGui에서 처리되도록 합니다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam))
return true; //imgui 에서 처리된 메세지라면 바로 반환
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = NULL;
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DPICHANGED: // imgui 윈도우 포지션 재정의
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
{
//const int dpi = HIWORD(wParam);
//printf("WM_DPICHANGED to %d (%.0f%%)\n", dpi, (float)dpi / 96.0f * 100.0f);
const RECT* suggested_rect = (RECT*)lParam;
::SetWindowPos(hWnd, NULL, suggested_rect->left, suggested_rect->top
, suggested_rect->right - suggested_rect->left, suggested_rect->bottom - suggested_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
C++
복사
5. ImGui 렌더링 호출 추가
DirectX 11 렌더링 루프에서 ImGui 렌더링 함수를 호출합니다.
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
application.Run();
//editor::Run();
//이제 2단계로 나누어줘야 한다.
//application.Run()을 통해 게임을 돌리고
//editor::Run()을 통해 에디터를 돌린다.
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
// Rendering
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
// Update and Render additional Platform Windows
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
application.Present();
}
C++
복사
6.
imgui 메모리 해제
// Cleanup
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
// 해제는 조립의 역순
//editor::Release();
application.Release();
JavaScript
복사
요약
1.
ImGui 소스 파일 다운로드 및 추가: GitHub에서 ImGui 파일을 다운로드하여 프로젝트에 추가합니다.
2.
ImGui 초기화 코드 작성: DirectX 11과 Win32를 기반으로 ImGui를 초기화합니다.
3.
윈도우 메시지 전달 설정: WndProc 함수에서 ImGui 입력 처리를 추가합니다.
4.
렌더링 루프에 ImGui 추가: 메인 렌더링 루프에서 ImGui 새 프레임을 생성하고, 원하는 UI 요소를 추가하여 렌더링합니다.
이 과정을 통해 DirectX 11을 사용하는 Windows 애플리케이션에 ImGui 기능을 추가할 수 있습니다.
imgui 의 작동원리
ImGui는 즉시 모드 GUI(Immediate Mode GUI) 방식으로 작동하는 GUI 라이브러리입니다. 즉시 모드 방식은 전통적인 GUI 라이브러리의 **보존 모드(Retained Mode)**와 달리, 프레임마다 UI 상태를 매번 새로 그리는 방식을 사용합니다. 이 방식 덕분에 ImGui는 빠르고 직관적인 UI 구축이 가능하며, 게임 엔진 및 다양한 애플리케이션에서 빠르게 UI를 만들고 수정할 수 있습니다.
ImGui의 작동 원리
ImGui의 작동 방식은 크게 다음과 같은 단계로 이루어집니다:
1.
매 프레임 UI 상태 갱신 및 구성:
•
ImGui에서는 각 프레임마다 ImGui::Begin()과 ImGui::End() 사이에 UI 요소를 정의하고, 해당 요소에 대한 상태가 즉시 적용됩니다.
•
ImGui::Button()이나 ImGui::SliderFloat() 같은 함수들은 UI 요소를 화면에 그리기 위해 호출될 뿐만 아니라, 이와 동시에 요소의 상태나 입력 이벤트를 즉시 처리합니다.
•
이런 방식은 각 프레임에서 UI가 다시 구성되고 렌더링되기 때문에, UI 상태가 매번 업데이트됩니다.
2.
입력 처리 및 상호작용 감지:
•
마우스 클릭, 키보드 입력 등은 ImGuiIO 구조체를 통해 처리됩니다. ImGuiIO에는 입력 이벤트, 화면 크기, DPI 설정 등 UI 전반에 필요한 데이터가 포함됩니다.
•
프레임마다 이 데이터를 통해 ImGui는 각 UI 요소가 현재 활성화되었는지, 마우스와 같은 입력에 반응하는지를 판단합니다.
•
예를 들어 ImGui::Button() 함수는 현재 마우스 위치와 클릭 상태를 확인하고, 버튼 위에 마우스가 있고 클릭이 발생하면 이 버튼이 활성화되도록 처리합니다.
3.
렌더링 데이터 생성:
•
ImGui는 각 UI 요소가 그려져야 할 정보를 DrawList라는 형태로 수집합니다. 이 DrawList는 그려야 할 텍스트, 버튼, 슬라이더, 창 등의 그래픽 데이터와 각 요소의 위치, 색상 등을 포함한 정보로 이루어집니다.
•
DrawList는 ImGui 내부에서 미리 정의된 형식으로 관리되며, 사용자가 직접 이를 수정할 필요는 없습니다. 필요한 모든 UI 요소가 정의된 후 ImGui::Render()가 호출되면, 이 DrawList가 최종 렌더링을 위해 준비됩니다.
4.
렌더링 백엔드 호출:
•
최종적으로 생성된 렌더링 데이터(DrawList)는 사용자가 구현한 백엔드에서 렌더링됩니다. 백엔드는 OpenGL, DirectX, Vulkan과 같은 그래픽 API로, 각 API에 맞는 방식으로 UI 요소를 그리는 역할을 합니다.
•
예를 들어 DirectX 11 백엔드를 사용한다면 imgui_impl_dx11.cpp 파일이 렌더링 작업을 담당합니다. 이 파일은 ImGui가 생성한 버텍스와 인덱스 버퍼를 DirectX로 전달하여 실제로 화면에 UI가 표시되도록 합니다.
5.
프레임 종료 후 상태 초기화:
•
ImGui는 매 프레임 UI 상태를 다시 계산하기 때문에, UI 상태를 따로 유지할 필요가 없습니다. 프레임이 끝나면 이전 UI 상태는 초기화되고, 다음 프레임에서 다시 필요한 UI를 구성하게 됩니다.
즉시 모드와 보존 모드의 차이
•
즉시 모드 GUI (Immediate Mode GUI): 각 프레임마다 UI를 다시 정의하고 그립니다. ImGui는 이 방식을 따르며, UI 상태가 특정 프레임에서 새로 정의된다는 특징이 있습니다.
•
보존 모드 GUI (Retained Mode GUI): UI 상태와 구조가 시스템에 의해 유지됩니다. 상태와 레이아웃이 계속 유지되기 때문에 UI 변경에 대해 매번 정의할 필요가 없습니다. 전통적인 윈도우 UI 라이브러리가 이 방식을 사용합니다.
즉시 모드의 장점
1.
간결하고 직관적인 코드: UI 요소를 직접 순서대로 호출하여 표시할 수 있으므로, 코드가 직관적이고 간결합니다.
2.
상태 관리 간편: UI의 상태를 관리할 필요가 적고, 매 프레임 새로 정의되기 때문에 복잡한 상태 관리가 필요하지 않습니다.
3.
빠른 프로토타이핑: UI 상태를 코드에서 즉시 설정할 수 있어 프로토타이핑이 빠르고 개발 생산성이 높습니다.
ImGui는 이런 즉시 모드 방식을 통해 사용자 인터페이스를 빠르고 효율적으로 렌더링하며, 특히 게임 엔진, 시뮬레이션, 개발 도구 등에서 매우 유용하게 사용되고 있습니다.