從零開始手敲次世代遊戲引擎(八十三) - 知乎

本篇咱們來實現遊戲內交互界面(GUI)git

遊戲內GUI的實現能夠有不少選擇,我這裏選用輕量級而且支持多平臺的Dear ImGui。由於它總體上的結構與咱們的引擎比較契合。github

https://github.com/ocornut/imguigithub.com

這個Dear ImGui在業界也十分有名,其脫胎於PSV的Tearaway這款遊戲,以後由原做者進行了大量的整理擴展工做,在諸如育碧的《刺客信條 奧德賽》《刺客信條 起源》、索尼的《拯救宇宙機器人》、SE的《FFVII重製版》、Mojang的《個人世界(Bedrock)》、暴雪的《Warcraft III: Reforged》等等知名遊戲當中都有應用。同時也被集成在UE、Unity、Cocos2D等知名引擎當中。json

Tearawaytearaway.mediamolecule.com
https://github.com/ocornut/imgui/wiki/Software-using-dear-imguigithub.com

Dear ImGui以一組自包含的源代碼形式提供。其自己封裝了GUI部件的狀態邏輯與控制,經過一個被稱爲ImGui::IO的模塊與外部進行通訊,包括獲取用戶輸入以及輸出渲染指令。Dear ImGui自己是平臺及圖形渲染API無關的。使用者須要將平臺的用戶輸入事件轉換傳遞給ImGui::IO,而後將ImGui::IO當中保存的平臺無關繪圖命令隊列轉換成具體的圖形渲染API隊列。app

不過,由於大量成熟項目的應用,Dear ImGui自身已經至關穩定,而且隨代碼提供了一系列常見平臺和圖形API的綁定源代碼。這些代碼以代碼片斷的方式提供,能夠很容易插入到咱們本身的工程當中。ide

Officially maintained bindings (in repository):
Renderers: DirectX9, DirectX10, DirectX11, DirectX12, OpenGL (legacy), OpenGL3/ES/ES2 (modern), Vulkan, Metal.
Platforms: GLFW, SDL2, Win32, Glut, OSX.
Frameworks: Emscripten, Allegro5, Marmalade.
Third-party bindings (see Bindings page):
Languages: C, C#/.Net, ChaiScript, D, Go, Haskell, Haxe/hxcpp, Java, JavaScript, Julia, Kotlin, Lua, Odin, Pascal, PureBasic, Python, Ruby, Rust, Swift...
Frameworks: AGS/Adventure Game Studio, Amethyst, bsf, Cinder, Cocos2d-x, Diligent Engine, Flexium, GML/Game Maker Studio2, GTK3+OpenGL3, Irrlicht Engine, LÖVE+LUA, Magnum, NanoRT, Nim Game Lib, Ogre, openFrameworks, OSG/OpenSceneGraph, Orx, Photoshop, px_render, Qt/QtDirect3D, SFML, Sokol, Unity, Unreal Engine 4, vtk, Win32 GDI, WxWidgets.
Note that C bindings ( cimgui) are auto-generated, you can use its json/lua output to generate bindings for other languages.
Also see Wiki for more links and ideas.







接下來咱們來實際操做一下。svg

首先是將Dear ImGui的Repository加入咱們的外部模塊當中:flex

git submodule add --name imgui https://github.com/ocornut/imgui.git External/src/imgui

代碼簽出以後,咱們用tree命令看一下它的構造:ui

雖然有很多的代碼,可是其實Dear ImGui自身只是頂層目錄下的那幾個文件(上圖最下方)。examples下面的文件都是各個平臺及圖形API綁定的參考代碼,並非Dear ImGui的主體部分。this

Dear ImGui將自身也劃分紅3個層面:平臺、圖形API、核心。這種劃分與咱們的引擎十分接近。所以,咱們首先在引擎的平臺對接層完成Dear ImGui的Binding的對接:lua

Platform/Windows/WindowsApplication.cpp

#include "imgui/examples/imgui_impl_win32.h" 
void WindowsApplication::CreateMainWindow() {
    // get the HINSTANCE of the Console Program     HINSTANCE hInstance = GetModuleHandle(NULL);

    // this struct holds information for the window class     WNDCLASSEX wc;

    // clear out the window class for use     ZeroMemory(&wc, sizeof(WNDCLASSEX));

...
    // display the window on the screen     ShowWindow(m_hWnd, SW_SHOW);

    // Initialize ImGui     IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    [[maybe_unused]] ImGuiIO& io = ImGui::GetIO();

    ImGui_ImplWin32_Init(m_hWnd);
    ImGui_ImplWin32_EnableDpiAwareness();

    ImGui::StyleColorsDark();
}

void WindowsApplication::Finalize() {
    // Finalize ImGui     ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

    ReleaseDC(m_hWnd, m_hDc);

    BaseApplication::Finalize();
}

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd,
                                                             UINT msg,
                                                             WPARAM wParam,
                                                             LPARAM lParam);

// this is the main message handler for the program LRESULT CALLBACK WindowsApplication::WindowProc(HWND hWnd, UINT message,
                                                WPARAM wParam, LPARAM lParam) {
    LRESULT result = 0;

    WindowsApplication* pThis;
    if (message == WM_NCCREATE) {
        pThis = static_cast<WindowsApplication*>(
            reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);

        SetLastError(0);
        if (!SetWindowLongPtr(hWnd, GWLP_USERDATA,
                              reinterpret_cast<LONG_PTR>(pThis))) {
            if (GetLastError() != 0) return FALSE;
        }
    } else {
        pThis = reinterpret_cast<WindowsApplication*>(
            GetWindowLongPtr(hWnd, GWLP_USERDATA));
    }

    // ImGui message handler     result = ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam);

    // sort through and find what code to run for the message given     switch (message) {
        case WM_CHAR: {
            g_pInputManager->AsciiKeyDown(static_cast<char>(wParam));
        } break;
        case WM_KEYUP: {
...
        // this message is read when the window is closed         case WM_DESTROY: {
            // close the application entirely             PostQuitMessage(0);
            m_bQuit = true;
        } break;
        default:
            // Handle any messages the switch statement didn't             result = DefWindowProc(hWnd, message, wParam, lParam);
    }

    return result;
}

上面加粗的部分就是新插入的ImGui的代碼片斷。能夠看到對咱們程序本來的結構基本沒有任何破壞,很是乾淨。

接下來,是在圖形API環境,也就是咱們的RHI當中,插入ImGui圖形API綁定的相關代碼:

RHI/OpenGL/OpenGLGraphicsManager.cpp

#include "imgui/examples/imgui_impl_opengl3.h"
#ifdef OS_WINDOWS
#include "imgui/examples/imgui_impl_win32.h"
#endif

int OpenGLGraphicsManager::Initialize() {
    int result;

    result = OpenGLGraphicsManagerCommonBase::Initialize();

    if (result) {
        return result;
    }

    result = gladLoadGL();
...
    ImGui_ImplOpenGL3_Init("#version 420");

    return result;
}

void OpenGLGraphicsManager::Finalize() {
    ImGui_ImplOpenGL3_Shutdown();
    OpenGLGraphicsManagerCommonBase::Finalize();
}

void OpenGLGraphicsManager::BeginFrame(const Frame& frame) {
    OpenGLGraphicsManagerCommonBase::BeginFrame(frame);
    ImGui_ImplOpenGL3_NewFrame();
#ifdef OS_WINDOWS
    ImGui_ImplWin32_NewFrame();
#endif
}

void OpenGLGraphicsManager::EndFrame(const Frame& frame) {
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
    OpenGLGraphicsManagerCommonBase::EndFrame(frame);
}

一樣,加粗的部分是插入的ImGui圖形API綁定的代碼片斷。寥寥幾行,很是乾淨。不過本來按照咱們的模塊劃分,RHI裏面是不該該有平臺相關的代碼的。我這裏暫時將平臺相關代碼放在這裏,用宏定義標出,從此須要將這一部分進一步整理到Platform下面去。

這樣平臺及RHI相關的部分就植入完畢了。接下來就是使用了:

Framework/Common/GraphicsManager.cpp

void GraphicsManager::Draw() {
    auto& frame = m_Frames[m_nFrameIndex];

    for (auto& pDrawPass : m_DrawPasses) {
        pDrawPass->BeginPass();
        pDrawPass->Draw(frame);
        pDrawPass->EndPass();
    }

    if (ImGui::GetCurrentContext())
    {
        ImGui::NewFrame();

        ImGui::ShowDemoWindow();

        ImGui::Render();
    }
}

依然,很是地簡單。在場景繪製完成以後,追加GUI繪製的部分就能夠了。固然,這部分目前也是臨時這麼寫,根據咱們引擎的結構,應該將這部分整理到DrawPass或者DrawSubPass當中去。

知乎視頻www.zhihu.com圖標
相關文章
相關標籤/搜索