[UE4] 使用 GUI 框架“Dear ImGui”

Posted u010019717

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[UE4] 使用 GUI 框架“Dear ImGui”相关的知识,希望对你有一定的参考价值。


创建C++版本项目,这里以项目名称 MyDemo为例。

你可以从这里获得UE下ImGui的源代码。
https://github.com/segross/UnrealImGui


 

在项目所在的文件夹中创建一个名为 MyDemo\\Plugins\\ImGui的文件夹,
并将所有文件放在MyDemo项目的如下位置。


 

像往常一样添加文件后,运行 Generate VisualStudioProjectFiles 以更新 VisualStudio 项目。


 

启动 Visual Studio 项目时,可以看到插件代码:


默认插件是启用的


我可以在Play状态下,通过执行控制台命令 ImGui.ToggleDemo来打开一个窗口。

默认情况下,鼠标等输入不在ImGui上工作,因此运行控制台命令 ImGui.ToggleInput 将使鼠标正常工作。

输入 ImGui.ToggleInput麻烦怎么办?

打开项目设置,为插件 ImGui > ToggleInput 分配适当的键盘快捷键
如果将 ImGui > ImGuiInputHandlerClass 设置为 ImGuiInputHandler,则不需要执行 ImGui.ToggleInput。



参考插件文档:https://github.com/segross/UnrealImGui/blob/master/README.md

尝试创建自己的窗口。

修改项目的 Build.cs 文件,添加 ImGui 模块。
MyDemo.Build.cs)

public class MyDemo : ModuleRules

	public MyDemo(ReadOnlyTargetRules Target) : base(Target)
	
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[]  "Core", "CoreUObject", "Engine", "InputCore"
		
		);

		PrivateDependencyModuleNames.AddRange(new string[]  "ImGui" );


创建文件 ImGuiCommon.h、ImGuiTest.h 和 ImGuiTest.cpp 文件并输入了以下代码。

ImGuiCommon.h)

#pragma once

#ifdef IMGUI_API
#define WITH_IMGUI 1
#else
#define WITH_IMGUI 0
#endif

#if WITH_IMGUI
#include <ImGuiModule.h>
#include <ImGuiDelegates.h>

#include <imgui.h>
#endif

ImGuiTest.h)

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ImGuiCommon.h" 

#include "ImGuiTest.generated.h"

UCLASS()
class GUITEST_API AImGuiTest : public AActor

	GENERATED_BODY()

public:	
	AImGuiTest();

	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void Tick(float DeltaTime) override;

#if WITH_IMGUI
	void ImGuiTick();
#endif
;

ImGuiTest.cpp

#include "ImGuiTest.h"

AImGuiTest::AImGuiTest()

	PrimaryActorTick.bCanEverTick = true;


void AImGuiTest::BeginPlay()

	Super::BeginPlay();
#if WITH_IMGUI
	FImGuiDelegates::OnWorldDebug().AddUObject(this, &AImGuiTest::ImGuiTick);
#endif


void AImGuiTest::EndPlay(const EEndPlayReason::Type EndPlayReason)

	Super::EndPlay(EndPlayReason);
#if WITH_IMGUI
	FImGuiDelegates::OnWorldDebug().RemoveAll(this);
#endif


void AImGuiTest::Tick(float DeltaTime)

	Super::Tick(DeltaTime);
#if WITH_IMGUI
	ImGui::Begin("ImGui Debug Order Test");
	ImGui::Text("Actor Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'", *GetNameSafe(this), *GetNameSafe(GetWorld()), *GetNameSafe(GWorld));
	ImGui::End();
#endif


#if WITH_IMGUI
void AImGuiTest::ImGuiTick()

	ImGui::Begin("ImGui Debug Order Test");
	ImGui::Text("ImGui World Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'", *GetNameSafe(this), *GetNameSafe(GetWorld()), *GetNameSafe(GWorld));
	ImGui::End();

#endif

把 ImGuiTest Actor 的资产拖放到场景中。
Play一下,然后看到场景中显示了ImGui界面

ActorTick 与 ImGuiTick 的区别:

Tick 是一个熟悉的函数,因此无需解释,但 ImGui Tick 是通过注册ImGui 的 World Delegate来操作的。通过这样做,即使 Actor 的 Tick 频率由于暂停或 Slomo 发生变化,ImGuiTick 函数也可以每帧运行。

下图显示了实际操作。当您暂停时,Actor 的Tick暂停止,您可以看到 ImGui 窗口中显示的文本减少了。

 

显示贴图看看:

将以下内容添加到 h 中

#if WITH_IMGUI
	UTexture2D* Texture;
	void ImGuiTick();
#endif

将以下内容添加到 cpp中

void AImGuiTest::BeginPlay()

	Super::BeginPlay();
#if WITH_IMGUI
    // const ConstructorHelpers::FObjectFinder<UTexture2D> TextureLoader(TEXT("/Game/Template/Textures/T_RedPoint.T_RedPoint"));// 崩溃
    // Texture = TextureLoader.Object;
    Texture = Cast<UTexture2D>(StaticLoadObject(UTexture2D::StaticClass(), nullptr, TEXT("/Game/Template/Textures/T_RedPoint.T_RedPoint")));
    FImGuiModule::Get().RegisterTexture(FName(Texture->GetName()), Texture);
	FImGuiDelegates::OnWorldDebug().AddUObject(this, &AImGuiTest::ImGuiTick);
#endif


void AImGuiTest::EndPlay(const EEndPlayReason::Type EndPlayReason)

	Super::EndPlay(EndPlayReason);
#if WITH_IMGUI
    const FImGuiTextureHandle TextureHandle = FImGuiModule::Get().FindTextureHandle( FName(Texture->GetName()) ); 
    FImGuiModule::Get().ReleaseTexture(TextureHandle);
	FImGuiDelegates::OnWorldDebug().RemoveAll(this);
#endif


void AImGuiTest::Tick(float DeltaTime)

	Super::Tick(DeltaTime);
#if WITH_IMGUI
	ImGui::Begin("ImGui Debug Order Test");
	ImGui::Text("Actor Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'", *GetNameSafe(this), *GetNameSafe(GetWorld()), *GetNameSafe(GWorld));
    const FImGuiTextureHandle TextureHandle = FImGuiModule::Get().FindTextureHandle( FName(Texture->GetName()) ); 
    ImGui::Image(TextureHandle, ImVec2( 200 , 200 ));    
	ImGui::End();
    

从上面的代码中看到,UE4和 ImGui 之间的图像数据交换是使用 FImGuiTextureHandle 完成的,它具有字符串(FName)和 ID 数据。因此,传递给 ImGui 端的字符串是相当重要的。由于它也需要是唯一的,我认为在很多情况下您传递资产名称。

更多测试代码可以参看 imgui_demo.cpp 。


可以通过适当地复制代码来创建这样一个窗口:


 

源代码来自 imgui_demo.cpp 。
ImGuiTest.h)

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ImGuiCommon.h" 

#include "ImGuiTest.generated.h"

UCLASS()
class GUITEST_API AImGuiTest : public AActor

	GENERATED_BODY()

public:	
	AImGuiTest();

	virtual void Tick(float DeltaTime) override;
;


ImGuiTest.cpp

#include "ImGuiTest.h"

AImGuiTest::AImGuiTest()

	PrimaryActorTick.bCanEverTick = true;


void AImGuiTest::Tick(float DeltaTime)

	Super::Tick(DeltaTime);
#if WITH_IMGUI
    if (ImGui::TreeNode("Basic"))
    
        static int clicked = 0;
        if (ImGui::Button("Button"))
            clicked++;
        if (clicked & 1)
        
            ImGui::SameLine();
            ImGui::Text("Thanks for clicking me!");
        

        static bool check = true;
        ImGui::Checkbox("checkbox", &check);

        static int e = 0;
        ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine();
        ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine();
        ImGui::RadioButton("radio c", &e, 2);

        // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.
        for (int i = 0; i < 7; i++)
        
            if (i > 0)
                ImGui::SameLine();
            ImGui::PushID(i);
            ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f));
            ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f));
            ImGui::Button("Click");
            ImGui::PopStyleColor(3);
            ImGui::PopID();
        

        // Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
        ImGui::AlignTextToFramePadding();
        ImGui::Text("Hold to repeat:");
        ImGui::SameLine();

        // Arrow buttons with Repeater
        static int counter = 0;
        float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
        ImGui::PushButtonRepeat(true);
        if (ImGui::ArrowButton("##left", ImGuiDir_Left))  counter--; 
        ImGui::SameLine(0.0f, spacing);
        if (ImGui::ArrowButton("##right", ImGuiDir_Right))  counter++; 
        ImGui::PopButtonRepeat();
        ImGui::SameLine();
        ImGui::Text("%d", counter);

        ImGui::Text("Hover over me");
        if (ImGui::IsItemHovered())
            ImGui::SetTooltip("I am a tooltip");

        ImGui::SameLine();
        ImGui::Text("- or me");
        if (ImGui::IsItemHovered())
        
            ImGui::BeginTooltip();
            ImGui::Text("I am a fancy tooltip");
            static float arr[] =  0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f ;
            ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr));
            ImGui::EndTooltip();
        

        ImGui::Separator();

        ImGui::LabelText("label", "Value");

        
            // Using the _simplified_ one-liner Combo() api here
            // See "Combo" section for examples of how to use the more complete BeginCombo()/EndCombo() api.
            const char* items[] =  "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" ;
            static int item_current = 0;
            ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items));
            ImGui::SameLine();
        

        
            static char str0[128] = "Hello, world!";
            ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0));
            ImGui::SameLine();

            static char str1[128] = "";
            ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1));

            static int i0 = 123;
            ImGui::InputInt("input int", &i0);
            ImGui::SameLine();

            static float f0 = 0.001f;
            ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f");

            static double d0 = 999999.00000001;
            ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f");

            static float f1 = 1.e10f;
            ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e");
            ImGui::SameLine();

            static float vec4a[4] =  0.10f, 0.20f, 0.30f, 0.44f ;
            ImGui::InputFloat3("input float3", vec4a);
        

        
            static int i1 = 50, i2 = 42;
            ImGui::DragInt("drag int", &i1, 1);
            ImGui::SameLine();

            ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%");

            static float f1=1.00f, f2=0.0067f;
            ImGui::DragFloat("drag float", &f1, 0.005f);
            ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns");
        

        
            static int i1=0;
            ImGui::SliderInt("slider int", &i1, -1, 3);
            ImGui::SameLine();

            static float f1=0.123f, f2=0.0f;
            ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f");
            ImGui::SliderFloat("slider float (curve)", &f2, -10.0f, 10.0f, "%.4f", 2.0f);

            static float angle = 0.0f;
            ImGui::SliderAngle("slider angle", &angle);

            // Using the format string to display a name instead of an integer.
            // Here we completely omit '%d' from the format string, so it'll only display a name.
            // This technique can also be used with DragInt().
            enum Element  Element_Fire, Element_Earth, Element_Air, Element_Water, Element_COUNT ;
            const char* element_names[Element_COUNT] =  "Fire", "Earth", "Air", "Water" ;
            static int current_element = Element_Fire;
            const char* current_element_name = (current_element >= 0 && current_element < Element_COUNT) ? element_names[current_element] : "Unknown";
            ImGui::SliderInt("slider enum", &current_element, 0, Element_COUNT - 1, current_element_name);
            ImGui::SameLine();
        

        
            static float col1[3] =  1.0f,0.0f,0.2f ;
            static float col2[4] =  0.4f,0.7f,0.0f,0.5f ;
            ImGui::ColorEdit3("color 1", col1);
            ImGui::SameLine();

            ImGui::ColorEdit4("color 2", col2);
        

        
            // List box
            const char* listbox_items[] =  "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" ;
            static int listbox_item_current = 1;
            ImGui::ListBox("listbox\\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4);
        

        ImGui::TreePop();
    
#endif

imGui 非常方便。 现在为运行时调试提供了一种新的UI形式。

以上是关于[UE4] 使用 GUI 框架“Dear ImGui”的主要内容,如果未能解决你的问题,请参考以下文章

[UE4] 使用 GUI 框架“Dear ImGui” 示例:ImguiLogWindow

[UE4] 使用 GUI 框架“Dear ImGui”

[UE4] 使用 GUI 框架“Dear ImGui”

[UE4] 使用 GUI 框架“Dear ImGui”

UE4运行时交互工具框架

ue4是Qt制作的吗?