Siv3DDay 3

OpenSiv3DでImGuiを使う


はじめに

OpenSiv3Dで簡単にGUIを作りたいと思ったことはありませんか?

この記事ではImGuiというGUIライブラリを組み込む方法を解説します。

nuklearというGUIライブラリを使った例は、既に記事が書かれていますのでここで紹介しておきます。

OpenSiv3D+nuklearでGUIを実現する


ImGuiとは

https://github.com/ocornut/imgui

詳しくはリポジトリを見てもらうのが早いですが、簡潔にまとめると、直感的なコードでGUIを構築できる、マルチプラットフォームなGUIライブラリです。


環境

この記事の内容は以下のバージョンを元に解説しています。


  • OpenSiv3D 0.3.1

  • ImGui 1.66


準備



  1. OpenSiv3Dをクローンする。


  2. Dependenciesフォルダーに、要求されているバージョンのboostを配置する。


  3. ImGuiをダウンロードする。

ImGuiのフォルダからそのまま参照する場合、プロジェクトに含めて、インクルードディレクトリに指定すれば、以下の手順は必要ありません。



  1. include/ThirdPartyimguiフォルダーを作成する。


  2. 以下のファイルをinclude/ThirdParty/imguiにコピーする。


    • imgui.h

    • imgui_internal.h

    • imconfig.h

    • misc/cpp/imgui_stdlib.h1



  3. include/ThirdPartyimguiフィルターを作成する。


  4. include/ThirdParty/imguiフィルターにinclude/ThirdParty/imguiフォルダーの中身を全て追加する。


  5. src/ThirdPartyimguiフォルダーを作成する。



  6. 以下のファイルをsrc/ThirdParty/imguiにコピーする


    • imgui.cpp

    • imgui_demo.cpp

    • imgui_draw.cpp

    • imgui_widgets.cpp

    • imstb_rectpack.h

    • imstb_textedit.h

    • imstb_truetype.h

    • examples/imgui_impl_dx11.cpp

    • examples/imgui_impl_dx11.h

    • examples/imgui_impl_win32.cpp

    • examples/imgui_impl_win32.h

    • misc/cpp/imgui_stdlib.cpp1



  7. src/ThirdPartyimguiフィルターを作成する。


  8. src/ThirdParty/imguiフィルターにsrc/ThirdParty/imguiフォルダーの中身を全て追加する。



  9. 以下のソースのインクルードディレクトリを修正する。


    • imgui.cpp

    • imgui_demo.cpp

    • imgui_draw.cpp

    • imgui_impl_dx11.cpp

    • imgui_impl_win32.cpp

    • imgui_stdlib.cpp

    • imgui_widgets.cpp



 #include "imgui.h"#include <imgui/imgui.h>

 #include "imgui_internal.h"#include <imgui/imgui_internal.h>

 #include "imgui_stdlib.h"#include <imgui/imgui_stdlib.h>


実装

d3dcompilerをリンクする。

直近のコミットで本家に追記されたため、今後のリリースではこの手順は必要無いと思われます。

https://github.com/ocornut/imgui/commit/8d58055a5433e1227ebb5e3b8dba80bb00cc239a


imgui_impl_dx11.cpp

    #include <stdio.h>

#include <d3d11.h>
#include <d3dcompiler.h>

+ #pragma comment(lib, "d3dcompiler")


初期化とフレーム開始の処理を追記する。


src/Siv3D/System/CSystem_Windows.cpp

    # include <Siv3D/Cursor.hpp>

# include <Siv3D/Logger.hpp>

+ #include <imgui/imgui.h>
+ #include <imgui/imgui_impl_win32.h>
+ #include <imgui/imgui_impl_dx11.h>
+ #include "../Graphics/D3D11/CGraphics_D3D11.hpp"



src/Siv3D/System/CSystem_Windows.cpp

        if (!Siv3DEngine::GetAsset()->init())

{
return false;
}

+ ImGui::CreateContext();
+ ImGui_ImplWin32_Init(Siv3DEngine::GetWindow()->getHandle());
+ CGraphics_D3D11* graphics = dynamic_cast<CGraphics_D3D11*>(Siv3DEngine::GetGraphics());
+ ImGui_ImplDX11_Init(graphics->getDevice(), graphics->getContext());

m_setupState = SetupState::Initialized;

LOG_INFO(U"✅ Siv3D engine setup completed");

return true;
}

bool CSystem_Windows::update(bool clearGraphics)
{
if (!m_updateSucceeded)
{
return false;
}

m_updateSucceeded = false;

if (m_setupState == SetupState::Initialized)
{
Siv3DEngine::GetWindow()->show(true);

+ ImGui_ImplDX11_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+ ImGui::NewFrame();

m_setupState = SetupState::Displayed;

Siv3DEngine::GetCursor()->applyStyleImmediately(CursorStyle::Default);
}



src/Siv3D/System/CSystem_Windows.cpp

        if (!Siv3DEngine::GetProfiler()->beginFrame())

{
return false;
}

+ ImGui_ImplDX11_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+ ImGui::NewFrame();

const bool onDeviceChange = m_onDeviceChange.exchange(false);


フレーム終了(レンダリング)の処理を追記する。


src/Siv3D/Grapgics/D3D11/CGraphics_D3D11.cpp

    # include "../../Siv3DEngine.hpp"

# include "CGraphics_D3D11.hpp"

+ #include <imgui/imgui.h>
+ #include <imgui/imgui_impl_dx11.h>



src/Siv3D/Grapgics/D3D11/CGraphics_D3D11.cpp

    bool CGraphics_D3D11::flush(bool clearGraphics)

{
m_renderer2D->flush(clearGraphics);

+ ImGui::Render();
+ ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

m_renderTarget->resolve();

return true;
}


アプリ終了時の処理を追記する。


src/Siv3D/Siv3DEngine.cpp

    # include "Script/IScript.hpp"

# include "Asset/IAsset.hpp"

+ #include <imgui/imgui.h>
+ #include <imgui/imgui_impl_win32.h>
+ #include <imgui/imgui_impl_dx11.h>



src/Siv3D/Siv3DEngine.cpp

    Siv3DEngine::~Siv3DEngine()

{
+ ImGui_ImplDX11_Shutdown();
+ ImGui_ImplWin32_Shutdown();
+ ImGui::DestroyContext();

m_asset.release();
m_script.release();

ウィンドウプロシージャを編集する。

ImGuiにウィンドウメッセージを渡し、Siv3Dのテキスト入力処理を削除する。(コメントアウトで構いません)


src/Siv3D/Window/WindowProc.cpp

    # include "../TextInput/ITextInput.hpp"

# include "../TextInput/CTextInput_Windows.hpp"

+ #include <imgui/imgui.h>
+ #include <imgui/imgui_impl_win32.h>

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

namespace s3d
{
namespace detail
{
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

+ if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) {
+ return 1;
+ }

- if (auto textinput = dynamic_cast<CTextInput_Windows*>(Siv3DEngine::GetTextInput()))
- {
- if (textinput->process(message, wParam, &lParam))
- {
- return 0;
- }
- }



src/Siv3D/Window/WindowProc.cpp

    case WM_CHAR:

{
- Siv3DEngine::GetTextInput()->pushChar(static_cast<uint32>(wParam));

return 0;
}
case WM_UNICHAR:
{
if (wParam == UNICODE_NOCHAR)
{
return true;
}

- Siv3DEngine::GetTextInput()->pushChar(static_cast<uint32>(wParam));

return 0;
}



ライブラリを使う

まずは通常通り、OpenSiv3Dのプロジェクトを作成します。

その後、インクルードディレクトリやライブラリディレクトリを、ここでクローンしたディレクトリに変更してください。


サンプル


デモウィンドウの表示


Main.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.3.1

# include <imgui/imgui.h>

void Main()
{
Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

while (System::Update())
{
ImGui::ShowDemoWindow();
}
}



日本語入力

任意のフォントファイルをフォルダに配置してから実行してください。(この例ではmplus-1p-regular.ttfを配置しています)

範囲の指定に

io.Fonts->GetGlyphRangesJapanese()

等がありますが、以下のように全範囲を指定しても問題無いようです。


Main.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.3.1

# include <imgui/imgui.h>
# include <imgui/imgui_stdlib.h>

void Main()
{
Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

ImGuiIO& io = ImGui::GetIO();
static const ImWchar ranges[] = { 0x0020, 0xFFFF, 0 };
io.Fonts->AddFontFromFileTTF("mplus-1p-regular.ttf", 20.0f, NULL, &ranges[0]);

std::string text;

while (System::Update())
{
ImGui::InputText(u8"テキスト", &text);
}
}



既知の不具合

変換候補がキャレットの位置ではない場所に表示される。





  1. InputTextでstd::stringが使えるようになります。