Mega UI

Full example

A complete working mod, in one file, using every widget there is.

This is the whole mod. There is no second class, no UI folder, and not one line of RectTransform.

csharp
using BepInEx;
using MegaUI.Api;
using UnityEngine;

namespace ExampleMegaUiMod;

[BepInPlugin(Guid, "Example Mega UI Mod", "1.0.0")]
[BepInDependency(MegaUiApi.ModGuid, BepInDependency.DependencyFlags.HardDependency)]
public sealed class Plugin : BaseUnityPlugin
{
    public const string Guid = "ExampleMegaUiMod";

    // The state belongs to the mod, never to the UI. The widgets read from here
    // and write back here - which is why the screen can never fall out of step
    // with other code that changes these values.
    private static bool _warn = true;
    private static int  _limit = 5;
    private static int  _clicks;

    private void Awake()
    {
        var app = MegaUiApi.Register(Guid, new AppDefinition
        {
            Title = "Example",
            Icon  = UiIcon.Builtin("gauge"),
        });

        if (!app.Ok)
        {
            Logger.LogError($"Mega UI refused this app: {app.Problem}");
            return;
        }

        app.Build(ui =>
        {
            ui.Header("Example app");
            ui.Label("Everything on this screen was written with the Mega UI API.");

            ui.Gap();

            ui.Card(c =>
            {
                c.Row(r =>
                {
                    r.Icon("timer");
                    r.Label("Frames since the game started");
                    r.Spacer();
                    r.Value(() => Time.frameCount.ToString("N0"));
                });

                c.Row(r =>
                {
                    r.Label("Times the button was clicked");
                    r.Spacer();
                    r.Value(() => _clicks.ToString());
                });
            });

            ui.Header("Settings");

            ui.Toggle("Warn me about things", () => _warn, v =>
            {
                _warn = v;
                Logger.LogInfo($"warn = {v}");
            });

            ui.Stepper("How many at most", 1, 20, () => _limit, v => _limit = v);

            ui.Separator();

            ui.Row(r =>
            {
                r.Label("Ready?");
                r.Spacer();

                r.Button("Do the thing", () =>
                {
                    _clicks++;
                    Logger.LogInfo($"clicked {_clicks} time(s), limit {_limit}, warn {_warn}");
                });
            });
        });

        Logger.LogInfo("Loaded - open the store computer and look for 'Example'.");
    }
}

What to copy out of it

The rest is scaffolding so the example has something to show.

The csproj

xml
<ItemGroup>
  <Reference Include="MegaUI">
    <HintPath>$(GameDir)\BepInEx\plugins\rodopoulos\MegaUI.dll</HintPath>
    <Private>false</Private>
  </Reference>

  <Reference Include="BepInEx">
    <HintPath>$(GameDir)\BepInEx\core\BepInEx.dll</HintPath>
    <Private>false</Private>
  </Reference>

  <!-- Only because the example reads Time.frameCount. A mod showing its own
       data does not need even this. -->
  <Reference Include="UnityEngine">
    <HintPath>$(Managed)\UnityEngine.dll</HintPath>
    <Private>false</Private>
  </Reference>
  <Reference Include="UnityEngine.CoreModule">
    <HintPath>$(Managed)\UnityEngine.CoreModule.dll</HintPath>
    <Private>false</Private>
  </Reference>
</ItemGroup>
Look at what is not in that list

No UnityEngine.UI, no UnityEngine.UIModule, no Unity.TextMeshPro. A mod that draws a full screen references no UI assembly at all — that is the difference between describing a screen and building one.

Running it

  1. Build with dotnet build -c Release
  2. Copy the dll into BepInEx/plugins/, with the game closed
  3. Start the game, walk to the store computer
  4. Example is on the desktop, under the game's own icons

If it is not there, the log says why. Search BepInEx/LogOutput.log for [ui] — and copy the file before reopening the game, because the game wipes it on every launch.