Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

there's any example to start with this? #12

Open
moebiussurfing opened this issue Oct 10, 2018 · 11 comments
Open

there's any example to start with this? #12

moebiussurfing opened this issue Oct 10, 2018 · 11 comments

Comments

@moebiussurfing
Copy link

sorry for posting here,,,
but I don't know how to start.
Plugin its copied to my project and detected and enabled fine.

@segross
Copy link
Owner

segross commented Oct 11, 2018

Hey, unfortunately there is no project with example how to use it. When I have a moment I’ll try to create one (I have one but I need to do something more publishable :).

I assume that you already configured module dependancy as described in "How to use it" section.

How well do you know ImGui? If you don’t then ImGui repository (https://github.com/ocornut/imgui) is a good place to start and imgui.h have extensive readme section. If you already know ImGui then all you need is to include imgui.h header and use it in your code. You can also register your own tick delegate and use ImGui code from there. I don’t think that one method is better than the other, so use whatever makes more sense in particular case. One thing to keep in mind is that I want to change the interface for ImGui tick delegates.

To see examples how to use ImGui you can check FImGuiDemo class which calls original demo. This is usually the best place to see how to use ImGui and what is possible. You can also check SImGuiWidget to see its own debug code. To see them in action use ImGui.ShowDemo and ImGui.Debug.Widget cvars.

If you don’t need to use custom textures that would be about it. If you do, then FImGuiModule has functions allowing to register textures before they can be used in ImGui. I see that I didn’t actually update readme with that information.

Hope that it helps a bit. Let me know if something is not clear.

@gabeparamo
Copy link

First off, thank you for the work put into this plugin!
It's really awesome stuff.

After just getting this setup myself, using your plugin, I can see where part of the confusion lies.
From the perspective of someone using the plugin from the Game Module side it's confusing how the plugin is intended to be interfaced with. I read your comment which led me to FImGuiDemo which is setup from within the plugin in ImGuiModuleManager. ImGuiModuleManager doesn't appear to be the intended class that users of the plugin should be interfacing with. It wasn't until I found ImGuiModule that it became clearer with how the plugin is intended to be used.

Create a class (FMyGameImGui) that is instanced somewhere (Globally? GameModule, GameInstance, GameSingleton) and have that class communicate with FImGuiModule using FImGuiModule::Get().AddWorldImGuiDelegate (Or AddMultiContextImGuiDelegate if in editor? This is the only one I was able to get to work in Editor). OnDraw will get called and then from there you can add your ImGui logic for MainMenuBars or Windows, etc.

Pseudo code below...

class FMyGameImGuiExample
{
       void BindToImGuiDelegate();
	void OnDraw();

	FImGuiDelegate ImGuiDelegate;
	FImGuiDelegateHandle ImGuiHandle;
};

void FMyGameImGuiExample::BindToImGuiDelegate()
{
	if (FImGuiModule::IsAvailable())
	{
		ImGuiDelegate = FImGuiDelegate::CreateRaw(this, &FGameModuleImpl::OnDraw);
#if WITH_EDITOR
		ImGuiHandle = FImGuiModule::Get().AddMultiContextImGuiDelegate(ImGuiDelegate);
#else
		ImGuiHandle = FImGuiModule::Get().AddWorldImGuiDelegate(ImGuiDelegate);
#endif
	}
}

void FMyGameImGuiExample::OnDraw()
{
        if (ImGui::BeginMainMenuBar())
	{
		if (ImGui::BeginMenu("Example Menu"))
		{
			if (ImGui::MenuItem("Example Menu Item")) {}

			ImGui::EndMenu();
		}
		ImGui::EndMainMenuBar();
	}
}

Is this the intended way for users to interface with the plugin?
Hopefully this helps and doesn't add more confusion.

Thanks,

Gabe

@segross
Copy link
Owner

segross commented Jan 2, 2019

Thank you @gabeparamo for your post.

First of all, thank you for pointing out possible confusion. I understand that this knowledge came from a personal struggle and for that I am sorry ;) I do see the problem and I maybe even better understand now what needs to be clarified. It is a bit rude of me to leave it without good example for that long but there is a reason to that. There is a bit of refactoring that I want to do which will almost surely affect the public API (those delegates that you mentioned) and I didn’t want to spread a soon-to-be depreciated knowledge. Taking from your example, the intention of those changes would be to:

  1. Being able to reliably register global objects before module instance is created and without testing FImGuiModule::IsAvailable().
  2. Ideally being able to use more Unreal 'native' interface when dealing with delegates, so Handle = FImGuiModule::Get().AddWorldImGuiDelegate(Delegate) and FImGuiModule::Get().RemoveImGuiDelegate(Handle) would be replaced with something more like this FImGuiDelegates::OnWorldImGuiDraw().AddRaw/Static/UObject(...) and FImGuiDelegates::OnWorldImGuiDraw().RemoveAll(this) or if user chose so removing with a handle.

This is important change for me before doing any official recommendations and that is why I'm postponing releasing of those examples for so long.

But I appreciate the fact that in the meantime people might be confused, so I’ll use this thread as a temporary solution and will try to post a snippet demonstrating how it should be used from game... probably tomorrow as it is getting late here.

@gabeparamo
Copy link

Ah I see! That makes perfect sense. Thank you for your response!! Happy new year!

@segross
Copy link
Owner

segross commented Jan 2, 2019

Thank you @gabeparamo. Happy New Year to you too!

Below is an example with three different methods to debug object (Actor in this case). A bit long but I just cleaned and copied one of my test actors that I use in my ImGui dev project:

// ImGuiCommon.h

#pragma once

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

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

#include <imgui.h>
#endif // WITH_IMGUI
// ImGuiDebugOrderTest.h

#pragma once

#include "ImGuiCommon.h"

#include <CoreMinimal.h>
#include <GameFramework/Actor.h>

#include "ImGuiDebugOrderTest.generated.h"

UCLASS()
class AImGuiDebugOrderTest : public AActor
{
	GENERATED_BODY()
	
public:	

	// To register and unregister static multi-context delegate.
	AImGuiDebugOrderTest();
	virtual void BeginDestroy() override;

	// To register and unregister per-object world delegate.
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	// To debug during world tick.
	virtual void Tick(float DeltaTime) override;

#if WITH_IMGUI
	void ImGuiTick();
	static void ImGuiMultiContextTick();

	FImGuiDelegateHandle ImGuiTickHandle;
	static FImGuiDelegateHandle ImGuiMultiContextTickHandle;
#endif // WITH_IMGUI
};
// ImGuiDebugOrderTest.cpp

#include "ImGuiDebugOrderTest.h"

#include <Runtime/Engine/Public/EngineUtils.h>


FImGuiDelegateHandle AImGuiDebugOrderTest::ImGuiMultiContextTickHandle;

AImGuiDebugOrderTest::AImGuiDebugOrderTest()
{
	// Let's say that this actor ticks and we want to debug it.
	PrimaryActorTick.bCanEverTick = true;

	// Register static multi-context delegate (only for default object for symmetry with destruction).
	// Note that if module is not available at this point, registration can fail. This limitation of the API will be
	// fixed in one of the next few updates but for now, if necessary, a solution would be to retry registration
	// at a later stage (which I'm not doing here). 
#if WITH_IMGUI
	if (IsTemplate() && !ImGuiMultiContextTickHandle.IsValid() && FImGuiModule::IsAvailable())
	{
		ImGuiMultiContextTickHandle = FImGuiModule::Get().AddMultiContextImGuiDelegate(FImGuiDelegate::CreateStatic(&AImGuiDebugOrderTest::ImGuiMultiContextTick));
	}
#endif // WITH_IMGUI
}

void AImGuiDebugOrderTest::BeginDestroy()
{
	Super::BeginDestroy();

	// Unregister static multi-context delegate. Failing to do so would result with multiplication of delegates during
	// hot reloading. And we do it only once for the default object to make sure that we unregister only when class is
	// not used anymore.
#if WITH_IMGUI
	if (IsTemplate() && ImGuiMultiContextTickHandle.IsValid() && FImGuiModule::IsAvailable())
	{
		FImGuiModule::Get().RemoveImGuiDelegate(ImGuiMultiContextTickHandle);
		ImGuiMultiContextTickHandle.Reset();
	}
#endif // WITH_IMGUI
}

void AImGuiDebugOrderTest::BeginPlay()
{
	Super::BeginPlay();

	// Register object's debug delegate in current world context.
#if WITH_IMGUI
	ImGuiTickHandle = FImGuiModule::Get().AddWorldImGuiDelegate(FImGuiDelegate::CreateUObject(this, &AImGuiDebugOrderTest::ImGuiTick));
#endif // WITH_IMGUI
}

void AImGuiDebugOrderTest::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	// Unregister object's delegate.
#if WITH_IMGUI
	FImGuiModule::Get().RemoveImGuiDelegate(ImGuiTickHandle);
#endif // WITH_IMGUI
}

void AImGuiDebugOrderTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// Debug during world tick.
#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 // WITH_IMGUI
}

#if WITH_IMGUI
void AImGuiDebugOrderTest::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();
}

void AImGuiDebugOrderTest::ImGuiMultiContextTick()
{
	ImGui::Begin("ImGui Debug Order Test");

	int32 Count = 0;
	for (TActorIterator<AImGuiDebugOrderTest> It(GWorld); It; ++It, ++Count)
	{
		AImGuiDebugOrderTest* Actor = *It;
		UWorld* World = Actor ? Actor->GetWorld() : nullptr;
		ImGui::Text("ImGui Multi-Context Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'",
			*GetNameSafe(Actor), *GetNameSafe(World), *GetNameSafe(GWorld));
	}

	ImGui::Text("ImGui Multi-Context Tick: %d actors in world '%ls'.", Count, *GetNameSafe(GWorld));

	ImGui::End();
}
#endif // WITH_IMGUI

@segross
Copy link
Owner

segross commented Jan 2, 2019

A few comments to the above example and in general.

  • World ImGui delegates are more suited for world objects that are created and destroyed in scope of one world.
  • Multi-context delegates are better for global objects, singletons etc.
  • There is a significant difference how world and multi-context delegates are called, so for particular feature you should commit to one or the other. You can of course mix them and use different combinations, even for particular class or object, but for any particular feature that you need to debug you should chose which is more suitable and commit. World delegate is called once during to world update, while the same multi-context delegate is called once for every world.
  • Registering world delegates can be spammy, so using multi-context one and then iterating trough world objects like in the example above might be a better option, especially if order is important as iterated objects are properly sorted. There is however a problem with multi-context delegates described in the example above. I’m going to fix API to make it more robust but for the time being you might need to test module state. This problem is particularly problematic when using raw global objects.
  • In any case it might be worth to bind registration/unregistration to a cvar state, so you keep the number of registered delegates at a reasonable level (this can be particularly a problem with world delegates when project grows). As a bonus this could also help to lessen problems with registrations of multi-context delegates.
  • There is also an option to just use ImGui from your tick functions. This doesn't require any setup but the downside is that debug information is not available during pause (which might be or might be not a problem).

@aoakenfo
Copy link

Ever think of recording a tutorial video for Youtube? If you have an nVidia card, recording is as simple as Alt+F9, preferably in one-take (https://signalvnoise.com/posts/1560-axl-vs-frank-more-time-doesnt-mean-a-better-product). Would love to listen to an overview of your code and decisions behind it.

@segross
Copy link
Owner

segross commented Apr 1, 2020

Hi @aoakenfo. I would like to upload to the GitHub a project showing how to set-up this plugin and a few different ways to add ImGui debug to the game code. Sort of "Hello, World" with a few examples demonstrating workflow. But for various reasons, I keep postponing it.
I never thought about making a Youtube video, though. I also didn't think about discussing the plugin itself as it feels to me merely as a wrapper for ImGui.

Is there anything particular that you would be interested in?
When it comes to decisions, the key points are:

  • It was designed primarily for debugging.
  • I wanted this to be as self-contained and as transparent from the game point of view as possible.
  • It carries a bit of legacy code and ideas that evolved from something that was one of the first things I have done in Unreal. I would like to maybe change it, but it works ok so I tolerate it for now.

@aoakenfo
Copy link

aoakenfo commented Apr 1, 2020

The Actor you posted above was enough to get started. I think adding that to the README.md is the easiest way for others to get started. In fact, you can simplify it even further by removing the multi-context code.

Yeah, I encourage a lot of people to just hit record and get it out there. (That's how all my Niagara tutorial vids were done.) Why not cover workflow in the video? You may think it's "just a wrapper" but it looks rather complex. I'm not sure where I would even begin to create an Imgui backend like this.

Anyway, thanks for all your hard work, you've done the community a great service. Much appreciated.

@segross
Copy link
Owner

segross commented Apr 2, 2020

Thank you for your kind words.

I didn't think about making a video because, well I'm not a very "video type" of person... whatever that means ;) But you are right that perhaps it would be good to put a simplified version of the above actor in the readme file, regardless plans for making a more complex example project.

@erdemserhatergen
Copy link

erdemserhatergen commented Dec 11, 2021

Hello, also thank you for the explanation above to integrate the plugin.

But i'm having a small issue about inputs. Even though i enabled input and share mouse input from properties, clicking on Unreal Engine viewport during runtime stops imgui to detect any input from mouse.

Setting Default Viewport Mouse Capture Mode to No Capture from project settings fixes this issue, but then I'm not able to get if mouse delta on other classes.

Is there any way to fix this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants