Here’s how I was able to get ImGui working inside the Unity game engine from a .NET mod loaded with BepInEx .

ImGui Demo window rendering inside Town of Salem
ImGuiRenderingPlugin performs the heavy lifting of hooking the relevant graphics interface and acts as an ImGui backend. ImGui.NET is used as a wrapper around the native front-end provided by cimgui .
Dependency Setup
With BepInEx already installed, I built the example project and set up a post-build task to copy it to <Game>/BepInEx/plugins/ImGuiMod/Mod.dll. Inside that directory, I needed to provide some dependent Mono libraries which were not already included in the <Game>/<Game>_Data/Managed/ directory: System.Runtime.dll and System.Runtime.CompilerServices.Unsafe.dll. The versions of these libraries provided with the repository are unsuitable (System.Runtime.dll is a facade with no actual implementation, System.Runtime.CompilerServices.Unsafe.dll is a .NET build which is unsuitable to link against the Mono libraries provided by Unity game installs) so I replaced them with compatible counterparts. I installed Mono 6.12.0 and searched C:\Program Files\Mono\lib\mono to locate them, but a different release version might be required depending on the age of the game.
ImGuiRenderingPlugin needs to be able to perform hooking via Unity’s native plugin API, so I had to use the third-party tool bundled in the repo to patch <Game>_Data/globalgamemanagers to load both <Game>_Data/Plugins/cimgui.dll and <Game>_Data/Plugins/ImGuiRenderingPlugin.dll during initialization.
Debugging and Refactoring
At this point, the example plugin was successfully rendering ImGui inside the process, but improper state management caused rendering to break between scene changes. Let me explain how the existing code worked:
TrainerLoadercreates a GameObject withTrainerMenuattachedTrainerMenuuses Unity’s GUI to create a button that does the following steps when clicked- If the
ImGuiPluginHookGameObject + component pair was not initialized yet, create it. (This bridges our code andImGuiRenderingPlugin.dll)- Also, add the
ImGuiDemoWindowcomponent to the active camera, and subscribe to itsLayoutevent to callTrainerMenu.OnLayout
- Also, add the
- If the
ImGuiPluginHookchecks if theImGuiDemoWindowcomponent exists on the active camera and adds it where absentImGuiDemoWindowimplementsUpdate()and simply invokes itsLayoutevent
This was really frustrating to debug on account of how confusing it was to include all these moving parts for no apparent benefit. I avoided listing anything but the key elements for the sake of brevity. Ultimately, the problem stemmed from ImGuiDemoWindow’s event not being subscribed by TrainerMenu when ImGuiPluginHook re-applied a new ImGuiDemoWindow component to the new scene camera. ImGuiPluginHook is only initialized once (due to DontDestroyOnLoad preserving it between scene changes) and thus TrainerMenu is never able to subscribe to the new event.
Here’s what I did to fix that:
- Delete
TrainerLoader - Move
ImGuiPluginHookGameObject + component initialization toBepInLoader.csduring the mod initialization - Delete the
TrainerMenucomponent - Rename
ImGuiDemoWindowtoImGuiActiveWindowand delete everything inside - Implement
ImGuiActiveWindow.Update()and use it to execute my ImGui code
The modding framework executes the mod through BepInLoader which creates ImGuiPluginHook. During the next frame, ImGuiPluginHook creates and attaches the ImGuiActiveWindow component to the active scene camera. ImGuiActiveWindow calls a static method to render the GUI in its Update function each frame. When the scene changes and deletes the camera, ImGuiPluginHook automatically kicks in and we’re back to rendering two frames later.
Input Fall-through
The final problem: ImGui inputs were “falling-through” to Unity UI elements without being consumed. I’m not very familiar with Unity, but I was able to accomplish a simple hack that fixes this well.
// ImGuiInput.cs
public void UpdateMouse()
{
// ...
// disable input events when ImGui is focused
var inputModule = EventSystem.current?.currentInputModule;
if (inputModule != null)
{
if (inputModule.isActiveAndEnabled && WantCaptureMouse)
{
inputModule.DeactivateModule();
}
else if (!inputModule.isActiveAndEnabled && !WantCaptureMouse)
{
inputModule.ActivateModule();
}
}
}
This prevents key inputs from being processed by the game as long as ImGui is trying to trap the mouse. However, the game will lose keyboard focus when the mouse is moved over an ImGui element. It works well enough in practice that I haven’t been bothered by it.
