Java 3D LWJGL 改造 〜Imgui を使用した GUI 描画を作成する〜

Imgui を使用した GUI 描画を改造

依然学習したLWJGL Gitbookをヒントにして現在実装しているテキストRPGのLWJGL化を行いたいと考えております。

現状のテキストRPG

上の動画にあるように、コマンドプロンプトなどのCUIで実行する前提で作成しています。しかし、表示する内容として余計なものが多すぎると感じています。
なので、LWJGLを使用してウィンドウから起動できるように改造しようと考えております。

しかし、学習を開始してからいろいろと上手くいかない状況になり。。。
Imguiを単体で使用することにしました。ちょうどImguiのjavaバインディングがあったのでそちらを使用します。

【参照するドキュメント一覧】

1.LWJGLのGitbook

  1. テキストRPGのドキュメント
  2. Imguiのドキュメント
  3. ImguiのJavaDoc
  4. Imguiのチュートリアル

Imguiの実装に関して(Java Binding)

この動画を参照しました。

動画の参照ページ

Imguiのセットアップ

  1. こちらのサイトより「java-libraries.zip」をダウンロードします。
  2. imgui-app-X.XX.X-all.jarをプロジェクトから参照できるように設定します。
  3. 下のコードをコピペします。参照先はこちらです。
    
    import imgui.ImGui;
    import imgui.app.Application;

public class Main extends Application {
@Override
protected void configure(Configuration config) {
config.setTitle("Dear ImGui is Awesome!");
}

@Override
public void process() {
    ImGui.text("Hello, World!");
}

public static void main(String[] args) {
    launch(new Main());
}

}


自分が作成したクラスは次のようになっています。(ImguiMain.java)
```java
package jp.zenryoku.imgui;

import imgui.ImGui;
import imgui.app.Application;
import imgui.app.Configuration;

/** ImGuiの学習用メインクラス */
public class ImguiMain extends Application {
    @Override
    protected void configure(Configuration config) {
        config.setTitle("Dear ImGui is Awesome!");
    }

    @Override
    public void process() {
        ImGui.text("Hello, World!");
    }

    public static void main(String[] args) {
        launch(new ImguiMain());
    }
}

書いて動かしてみた動画です。

どこから学習するか?

チュートリアルの動画など見ましたが、結局のところImguiを基本文法の学習時と同じように学んだほうが早いと判断しました。
具体的には次のようなところを学びます。

  1. Imguiでハローワールド(これは、上記で実施済み)
  2. 同様に、ハローワールドを改造する。参考ページはこちら
  3. こちらのFAQを見ながらやりたいことを実行する

まずは、実行したのが2の参考ページを見ながら作成したコードを見てください。

public class ImguiMain extends Application {
    @Override
    protected void configure(Configuration config) {
        config.setTitle("Dear ImGui is Awesome!");
    }

    @Override
    public void process() {
        // Windowその1
        ImGui.begin("Title A");
        ImGui.text("Hello, World!");
        ImGui.end();

        // Windowその2
        ImGui.begin("Title B");
        ImGui.text("A nice World!");
        ImGui.end();
    }

    public static void main(String[] args) {
        launch(new ImguiMain());
    }
}

ここから、基本を学びます。参照先はこちらのページにあるものを見ます。

ImGui::ShowDemoWindow()はどこに?

上の参照先にある記事を読むと、「デモコードをみて理解してね」とあるのでそれを見るようにします。

  1. デモ・コードに移動します。
  2. 下のような画面で247行目からのプログラムがそうです。

ImGui::ShowDemoWindow()を読む

289-302行目では、デモウィンドウを表示するメソッドが呼ばれているようです。しかし、どのようにウィンドウを作成するのかわからないので細かく見ません。

330-334行目にあるコードが参考になりそうです。

    // We specify a default position/size in case there's no data in the .ini file.
    // We only do it to make the demo applications a little more welcoming, but typically this isn't required.
    const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 650, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver);

上記のコードは、メソッド呼び出しを行っているだけなので、プログラムを動かしてみるのが早そうです。というわけでコードを書いて実行します。
ここでもポイントは、呼び出しているメソッド(関数 or ファンクション)の名前がC言語と変わっているかどうか?です。

<動かしてみる>

Javaのデモ・コードをみる

上記の「参照先はこちらのページ」にもあるように、デモ・コードから基本を学びます。
実際のプログラムは、ImGui::ShowDemoWindow();に実装しているようです。

しかし、上記のでもコードはC/C++なので、Javaでのサンプルコードが欲しいところ。。。
そんなわけで、見つけました。サンプルはGithubにありました。

ImGui Java-bindingがありました。

まずは、Mainクラスを参照します。

中身を読んでみたところ、同じディレクトリにあるクラスがすべて必要なようなのでこのクラスたちをダウンロードしてきます。

このページにある下のような部分をクリック

ダウンロードしてきたファイルを展開して、次のディレクトリに各ファイルがあります。

  • imgui-java/example/src/main/java/: 実行するJavaファイル
  • imgui-java/example/src/main/resources/: 使用するファイル(Tahoma.ttfなど)

実行してみた!

コードを書いてみる

ここで、サンプルコードをいじって書いたコードを紹介します。
ほぼ、Mainクラスをコピーしたものですが、process()メソッドの中身を書き換えて下のように修正、実行しました。

    @Override
    public void process() {
        if (ImGui.begin("Stories", ImGuiWindowFlags.AlwaysAutoResize)) {
            ImGui.text("Hello, World! " + FontAwesomeIcons.Smile);
            ImGui.sameLine();
            ImGui.text(String.valueOf(count));
            ExampleImGuiColorTextEdit.show(new ImBoolean(true));
        }
        ImGui.end();
    }

キーポイントになるのは「ExampleImGuiColorTextEdit.show(new ImBoolean(true));」の部分です。
ExampleImGuiColorTextEditクラスのstaticメソッド「show()」を呼び出しています。
早速、クラスの中身を見ます。

import imgui.ImGui;
import imgui.extension.texteditor.TextEditor;
import imgui.extension.texteditor.TextEditorLanguageDefinition;
import imgui.flag.ImGuiWindowFlags;
import imgui.type.ImBoolean;

import java.util.HashMap;
import java.util.Map;

public class ExampleImGuiColorTextEdit {
    private static final TextEditor EDITOR = new TextEditor();

    static {
        TextEditorLanguageDefinition lang = TextEditorLanguageDefinition.c();

        String[] ppnames = {
            "NULL", "PM_REMOVE",
            "ZeroMemory", "DXGI_SWAP_EFFECT_DISCARD", "D3D_FEATURE_LEVEL", "D3D_DRIVER_TYPE_HARDWARE", "WINAPI", "D3D11_SDK_VERSION", "assert"};
        String[] ppvalues = {
            "#define NULL ((void*)0)",
            "#define PM_REMOVE (0x0001)",
            "Microsoft's own memory zapper function\n(which is a macro actually)\nvoid ZeroMemory(\n\t[in] PVOID  Destination,\n\t[in] SIZE_T Length\n); ",
            "enum DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_DISCARD = 0",
            "enum D3D_FEATURE_LEVEL",
            "enum D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE  = ( D3D_DRIVER_TYPE_UNKNOWN + 1 )",
            "#define WINAPI __stdcall",
            "#define D3D11_SDK_VERSION (7)",
            " #define assert(expression) (void)(                                                  \n" +
                "    (!!(expression)) ||                                                              \n" +
                "    (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \n" +
                " )"
        };

        // Adding custom preproc identifiers
        Map<String, String> preprocIdentifierMap = new HashMap<>();
        for (int i = 0; i < ppnames.length; ++i) {
            preprocIdentifierMap.put(ppnames[i], ppvalues[i]);
        }
        lang.setPreprocIdentifiers(preprocIdentifierMap);

        String[] identifiers = {
            "HWND", "HRESULT", "LPRESULT","D3D11_RENDER_TARGET_VIEW_DESC", "DXGI_SWAP_CHAIN_DESC","MSG","LRESULT","WPARAM", "LPARAM","UINT","LPVOID",
                "ID3D11Device", "ID3D11DeviceContext", "ID3D11Buffer", "ID3D11Buffer", "ID3D10Blob", "ID3D11VertexShader", "ID3D11InputLayout", "ID3D11Buffer",
                "ID3D10Blob", "ID3D11PixelShader", "ID3D11SamplerState", "ID3D11ShaderResourceView", "ID3D11RasterizerState", "ID3D11BlendState", "ID3D11DepthStencilState",
                "IDXGISwapChain", "ID3D11RenderTargetView", "ID3D11Texture2D", "TextEditor" };
        String[] idecls = {
            "typedef HWND_* HWND", "typedef long HRESULT", "typedef long* LPRESULT", "struct D3D11_RENDER_TARGET_VIEW_DESC", "struct DXGI_SWAP_CHAIN_DESC",
                "typedef tagMSG MSG\n * Message structure","typedef LONG_PTR LRESULT","WPARAM", "LPARAM","UINT","LPVOID",
                "ID3D11Device", "ID3D11DeviceContext", "ID3D11Buffer", "ID3D11Buffer", "ID3D10Blob", "ID3D11VertexShader", "ID3D11InputLayout", "ID3D11Buffer",
                "ID3D10Blob", "ID3D11PixelShader", "ID3D11SamplerState", "ID3D11ShaderResourceView", "ID3D11RasterizerState", "ID3D11BlendState", "ID3D11DepthStencilState",
                "IDXGISwapChain", "ID3D11RenderTargetView", "ID3D11Texture2D", "class TextEditor" };

        // Adding custom identifiers
        Map<String, String> identifierMap = new HashMap<>();
        for (int i = 0; i < ppnames.length; ++i) {
            identifierMap.put(identifiers[i], idecls[i]);
        }
        lang.setIdentifiers(identifierMap);

        EDITOR.setLanguageDefinition(lang);

        // Adding error markers
        Map<Integer, String> errorMarkers = new HashMap<>();
        errorMarkers.put(1, "Expected '>'");
        EDITOR.setErrorMarkers(errorMarkers);

        EDITOR.setTextLines(new String[]{
            "#include <iostream",
            "",
            "int main() {",
            "   std::cout << \"Hello, World!\" << std::endl;",
            "}"
        });
    }
    public static void show(final ImBoolean showImColorTextEditWindow) {
        ImGui.setNextWindowSize(500, 400);
        if (ImGui.begin("Text Editor", showImColorTextEditWindow,
                ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.MenuBar)) {
            if (ImGui.beginMenuBar()) {
                if (ImGui.beginMenu("File")) {
                    if (ImGui.menuItem("Save")) {
                        String textToSave = EDITOR.getText();
                        /// save text....
                    }

                    ImGui.endMenu();
                }
                if (ImGui.beginMenu("Edit")) {
                    final boolean ro = EDITOR.isReadOnly();
                    if (ImGui.menuItem("Read-only mode", "", ro)) {
                        EDITOR.setReadOnly(!ro);
                    }

                    ImGui.separator();

                    if (ImGui.menuItem("Undo", "ALT-Backspace", !ro && EDITOR.canUndo())) {
                        EDITOR.undo(1);
                    }
                    if (ImGui.menuItem("Redo", "Ctrl-Y", !ro && EDITOR.canRedo())) {
                        EDITOR.redo(1);
                    }

                    ImGui.separator();

                    if (ImGui.menuItem("Copy", "Ctrl-C", EDITOR.hasSelection())) {
                        EDITOR.copy();
                    }
                    if (ImGui.menuItem("Cut", "Ctrl-X", !ro && EDITOR.hasSelection())) {
                        EDITOR.cut();
                    }
                    if (ImGui.menuItem("Delete", "Del", !ro && EDITOR.hasSelection())) {
                        EDITOR.delete();
                    }
                    if (ImGui.menuItem("Paste", "Ctrl-V", !ro && ImGui.getClipboardText() != null)) {
                        EDITOR.paste();
                    }

                    ImGui.endMenu();
                }

                ImGui.endMenuBar();
            }

            int cposX = EDITOR.getCursorPositionLine();
            int cposY = EDITOR.getCursorPositionColumn();

            String overwrite = EDITOR.isOverwrite() ? "Ovr" : "Ins";
            String canUndo = EDITOR.canUndo() ? "*" : " ";

            ImGui.text(cposX + "/" + cposY + " " + EDITOR.getTotalLines() + " lines | " + overwrite + " | " + canUndo);

            EDITOR.render("TextEditor");

            ImGui.end();
        }
    }
}

処理の中身を見ていると「ImGui.XXX()」のメソッドを呼び出している処理がほとんどです。
つまり、ほぼ、ImGuiクラスで画面の作成を行っているということです。ということは、JavaDocを見れば何とかなりそうです。

ImGuiでテキストエリア

JavaDocを見ながら、なんとか作成しました。実行結果は下のようになりました。

プログラムは、元のコードから変更しました。
<Main.java>

    @Override
    public void process() {
        ExampleImGuiColorTextEdit.show(new ImBoolean(false));
    }

元々は、ImGui.begin()を使用して、メニューバーを追加する処理を行っていましたが、これを削除して「ExampleImGuiColorTextEdit」クラスのshow()メソッドで定義している処理を呼び出すだけに修正しました。

<ExampleImGuiColorTextEdit.java>

public class ExampleImGuiColorTextEdit {
    private static final TextEditor EDITOR = new TextEditor();
    private static final String SEP = System.lineSeparator();

    static {
        TextEditorLanguageDefinition lang = TextEditorLanguageDefinition.c();

        String[] ppnames = {
            "NULL", "PM_REMOVE",
            "ZeroMemory", "DXGI_SWAP_EFFECT_DISCARD", "D3D_FEATURE_LEVEL", "D3D_DRIVER_TYPE_HARDWARE", "WINAPI", "D3D11_SDK_VERSION", "assert"};
        String[] ppvalues = {
            "#define NULL ((void*)0)",
            "#define PM_REMOVE (0x0001)",
            "Microsoft's own memory zapper function\n(which is a macro actually)\nvoid ZeroMemory(\n\t[in] PVOID  Destination,\n\t[in] SIZE_T Length\n); ",
            "enum DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_DISCARD = 0",
            "enum D3D_FEATURE_LEVEL",
            "enum D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE  = ( D3D_DRIVER_TYPE_UNKNOWN + 1 )",
            "#define WINAPI __stdcall",
            "#define D3D11_SDK_VERSION (7)",
            " #define assert(expression) (void)(                                                  \n" +
                "    (!!(expression)) ||                                                              \n" +
                "    (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \n" +
                " )"
        };

        // Adding custom preproc identifiers
        Map<String, String> preprocIdentifierMap = new HashMap<>();
        for (int i = 0; i < ppnames.length; ++i) {
            preprocIdentifierMap.put(ppnames[i], ppvalues[i]);
        }
        lang.setPreprocIdentifiers(preprocIdentifierMap);

        String[] identifiers = {
            "HWND", "HRESULT", "LPRESULT","D3D11_RENDER_TARGET_VIEW_DESC", "DXGI_SWAP_CHAIN_DESC","MSG","LRESULT","WPARAM", "LPARAM","UINT","LPVOID",
                "ID3D11Device", "ID3D11DeviceContext", "ID3D11Buffer", "ID3D11Buffer", "ID3D10Blob", "ID3D11VertexShader", "ID3D11InputLayout", "ID3D11Buffer",
                "ID3D10Blob", "ID3D11PixelShader", "ID3D11SamplerState", "ID3D11ShaderResourceView", "ID3D11RasterizerState", "ID3D11BlendState", "ID3D11DepthStencilState",
                "IDXGISwapChain", "ID3D11RenderTargetView", "ID3D11Texture2D", "TextEditor" };
        String[] idecls = {
            "typedef HWND_* HWND", "typedef long HRESULT", "typedef long* LPRESULT", "struct D3D11_RENDER_TARGET_VIEW_DESC", "struct DXGI_SWAP_CHAIN_DESC",
                "typedef tagMSG MSG\n * Message structure","typedef LONG_PTR LRESULT","WPARAM", "LPARAM","UINT","LPVOID",
                "ID3D11Device", "ID3D11DeviceContext", "ID3D11Buffer", "ID3D11Buffer", "ID3D10Blob", "ID3D11VertexShader", "ID3D11InputLayout", "ID3D11Buffer",
                "ID3D10Blob", "ID3D11PixelShader", "ID3D11SamplerState", "ID3D11ShaderResourceView", "ID3D11RasterizerState", "ID3D11BlendState", "ID3D11DepthStencilState",
                "IDXGISwapChain", "ID3D11RenderTargetView", "ID3D11Texture2D", "class TextEditor" };

        // Adding custom identifiers
        Map<String, String> identifierMap = new HashMap<>();
        for (int i = 0; i < ppnames.length; ++i) {
            identifierMap.put(identifiers[i], idecls[i]);
        }
        lang.setIdentifiers(identifierMap);

        EDITOR.setLanguageDefinition(lang);
    }

    public static void show(final ImBoolean showImColorTextEditWindow) {
        ImGui.setNextWindowSize(500, 400);
        if (ImGui.begin("Story", showImColorTextEditWindow)) {
            int cposX = EDITOR.getCursorPositionLine();
            int cposY = EDITOR.getCursorPositionColumn();

            String overwrite = EDITOR.isOverwrite() ? "Ovr" : "Ins";
            String canUndo = EDITOR.canUndo() ? "*" : " ";
            EDITOR.setReadOnly(true);

            //ImGui.text(cposX + "/" + cposY + " " + EDITOR.getTotalLines() + " lines | " + overwrite + " | " + canUndo);

            // 実装部分
            EDITOR.setText("GoodMorning" + SEP + "Hello World");
            EDITOR.render("TextEditor");

            ImGui.end();
        }
    }
}

同様に、メニューバー内に設定する文字列、Saveをクリックしたときの処理(処理内容はなし)が定義してありましたが、それを削除。
Mainクラスでshow(true)としていた部分をshow(false)に変更して、赤いラインを表示しないようにしました。

これで、目的の文字表示領域を作成することができました。

ImGui + TextRPG

テキストRPGを作成中でしたので、これを単体の画面で実行できるようにしたいと思っていたので、ImGuiは格好のライブラリです。
LWJGLを実行しないと実現できないと思っていましたが、ImGuiで事足りそうです。

TextRPGに関して、現状の考えとしてはImGuiをメインにして追加でLWJGLを実行したいと考えています。

TextRPGとは、下の動画のようなものです。これは、コマンドプロンプトで実行しているので「ゲーム」って感じがしない。。。と思っていたところです。

投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す