Example & Tutorials

Updated 8 hours ago

Examples & Tutorials

Step-by-step guides and practical examples to help you master ZUI integration.


📋 Table of Contents


Basic Main Menu Integration

The simplest way to add your mod to ZUI - just add buttons to the main menu.

Example 1: Single Button

using BepInEx;
using BepInEx.Unity.IL2CPP;
using System;
using System.Linq;
using System.Reflection;

[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
[BepInDependency("Zanakinz.ZUI", BepInDependency.DependencyFlags.SoftDependency)]
public class Plugin : BaseUnityPlugin
{
    private static Type _zuiType;
    
    public override void Load()
    {
        if (InitZUI())
        {
            RegisterUI();
        }
    }
    
    private bool InitZUI()
    {
        if (!IL2CPPChainloader.Instance.Plugins.ContainsKey("Zanakinz.ZUI"))
        {
            Log.LogInfo("ZUI not found - UI features disabled");
            return false;
        }
        
        var assembly = AppDomain.CurrentDomain.GetAssemblies()
            .FirstOrDefault(a => a.GetName().Name == "ZUI");
        
        if (assembly == null) return false;
        
        _zuiType = assembly.GetType("ZUI.API.ZUI");
        return _zuiType != null;
    }
    
    private void RegisterUI()
    {
        Call("SetPlugin", "MyMod");
        Call("SetTargetWindow", "Main");
        Call("AddCategory", "My Features");
        Call("AddButton", "Heal Me", ".heal");
    }
    
    private void Call(string methodName, params object[] args)
    {
        if (_zuiType == null) return;
        
        try
        {
            var method = _zuiType.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .FirstOrDefault(m => m.Name == methodName && 
                                     m.GetParameters().Length == args.Length);
            
            if (method != null)
            {
                method.Invoke(null, args);
            }
        }
        catch (Exception ex)
        {
            Log.LogError($"Error calling ZUI.{methodName}: {ex.Message}");
        }
    }
}

What this does:

  • Creates a category called "My Features" in the main ZUI menu
  • Adds a button labeled "Heal Me" that executes the .heal command
  • Gracefully handles ZUI not being installed

Example 2: Multiple Buttons with Categories

private void RegisterUI()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "Main");
    
    // Player commands
    Call("AddCategory", "Player Commands");
    Call("AddButton", "Heal", ".heal");
    Call("AddButton", "God Mode", ".god");
    Call("AddButton", "Fly Mode", ".fly");
    
    // Admin commands
    Call("AddCategory", "Admin Tools");
    Call("AddButton", "Save World", ".save");
    Call("AddButton", "Teleport", ".tp");
    Call("AddButton", "Give Items", ".give");
}

Result: Two organized categories with multiple buttons each.


Creating a Simple Custom Window

Custom windows give you complete control over layout and positioning.

Example 3: Basic Custom Window

private void RegisterUI()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "StatusPanel");
    
    // Create 500x300 window
    Call("SetUI", 500, 300);
    Call("SetTitle", "Player Status");
    
    // Add content
    Call("AddText", "Health: 100%", 20f, 50f);
    Call("AddText", "Mana: 75%", 20f, 100f);
    Call("AddText", "Speed: Normal", 20f, 150f);
    
    Call("AddButton", "Refresh", ".refresh", 20f, 200f);
}

What this creates:

  • A 500x300 pixel window titled "Player Status"
  • Three text labels showing stats
  • A refresh button at the bottom
  • A button in the main menu to open this window (auto-generated)

Example 4: Using Layout Helper

public class LayoutHelper
{
    private float _currentY;
    private readonly float _margin;
    private readonly float _spacing;
    
    public LayoutHelper(float margin = 20f, float spacing = 50f)
    {
        _margin = margin;
        _spacing = spacing;
        _currentY = margin;
    }
    
    public float NextY()
    {
        float y = _currentY;
        _currentY += _spacing;
        return y;
    }
    
    public float Margin => _margin;
}

private void RegisterStatusWindow()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "StatusPanel");
    Call("SetUI", 500, 400);
    Call("SetTitle", "Player Status");
    
    var layout = new LayoutHelper(margin: 20f, spacing: 45f);
    
    Call("AddText", "<size=16><b>Character Stats</b></size>", layout.Margin, layout.NextY());
    Call("AddText", "Health: 100%", layout.Margin, layout.NextY());
    Call("AddText", "Mana: 75%", layout.Margin, layout.NextY());
    Call("AddText", "Stamina: 90%", layout.Margin, layout.NextY());
    
    Call("AddText", "<size=16><b>Actions</b></size>", layout.Margin, layout.NextY());
    Call("AddButton", "Heal", ".heal", layout.Margin, layout.NextY());
    Call("AddButton", "Restore Mana", ".mana", layout.Margin, layout.NextY());
}

Why this is better:

  • Automatic spacing - no manual Y calculations
  • Easy to add/remove elements
  • Consistent layout throughout

Building an Admin Control Panel

A complete example of a professional admin panel.

Example 5: Full Admin Panel

public class ZUIConfig
{
    public const string PLUGIN_NAME = "AdminTools";
    public const int PANEL_WIDTH = 700;
    public const int PANEL_HEIGHT = 600;
    public const float MARGIN = 30f;
    public const float SPACING = 50f;
    public const float CATEGORY_SPACING = 70f;
}

private void RegisterAdminPanel()
{
    Call("SetPlugin", ZUIConfig.PLUGIN_NAME);
    Call("SetTargetWindow", "AdminPanel");
    Call("SetUI", ZUIConfig.PANEL_WIDTH, ZUIConfig.PANEL_HEIGHT);
    Call("SetTitle", "<color=#E74C3C>Admin Control Panel</color>");
    
    var layout = new LayoutHelper(ZUIConfig.MARGIN, ZUIConfig.SPACING);
    
    // Header
    Call("AddText", "<size=18><b>Server Administration</b></size>", 
         layout.Margin, layout.NextY());
    
    // Player Management Section
    Call("AddCategory", "<color=#3498DB>Player Management</color>", 
         layout.Margin, layout.NextY());
    Call("AddButton", "Heal Player", ".heal", layout.Margin, layout.NextY());
    Call("AddButton", "Kick Player", ".kick", layout.Margin, layout.NextY());
    Call("AddButton", "Ban Player", ".ban", layout.Margin, layout.NextY());
    
    layout.NextY(); // Extra space between sections
    
    // Server Management Section
    Call("AddCategory", "<color=#2ECC71>Server Management</color>", 
         layout.Margin, layout.NextY());
    Call("AddButton", "Save World", ".save", layout.Margin, layout.NextY());
    Call("AddButton", "Restart Server", ".restart", layout.Margin, layout.NextY());
    Call("AddButton", "Announce", ".announce", layout.Margin, layout.NextY());
    
    layout.NextY(); // Extra space
    
    // Warning footer
    Call("AddText", "<color=#E74C3C><i>⚠ Use admin commands responsibly</i></color>", 
         layout.Margin, layout.NextY());
}

Features:

  • Organized into logical sections
  • Color-coded categories
  • Professional styling with rich text
  • Consistent spacing using helper class

Dynamic Content Updates

Learn how to update UI content dynamically at runtime.

Example 6: Real-time Status Display

private void RegisterDynamicWindow()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "LiveStatus");
    Call("SetUI", 400, 300);
    Call("SetTitle", "Live Server Status");
    
    Call("AddText", "Players Online: Loading...", 20f, 50f);
    Call("AddText", "Server Time: Loading...", 20f, 100f);
    Call("AddButton", "Refresh", ".refreshstatus", 20f, 150f);
    
    // Start update loop
    StartCoroutine(UpdateStatusLoop());
}

private IEnumerator UpdateStatusLoop()
{
    while (true)
    {
        yield return new WaitForSeconds(5f); // Update every 5 seconds
        
        // Remove old content
        Call("RemoveButton", "Players Online: Loading...");
        Call("RemoveButton", "Server Time: Loading...");
        
        // Add updated content
        int playerCount = GetPlayerCount(); // Your method
        string serverTime = DateTime.Now.ToString("HH:mm:ss");
        
        Call("AddText", $"Players Online: {playerCount}", 20f, 50f);
        Call("AddText", $"Server Time: {serverTime}", 20f, 100f);
    }
}

Note: See Advanced Features (WIP) for details on element removal.


Working with Images

Add custom images to your UI for branding and visual appeal.

Example 7: Adding a Logo

private void RegisterWindowWithLogo()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "BrandedPanel");
    Call("SetUI", 600, 400);
    Call("SetTitle", "My Awesome Mod");
    
    // Add logo at top
    Call("AddImage", "logo.png", 200f, 20f, 200f, 100f);
    
    // Add content below logo
    Call("AddText", "Welcome to my mod!", 20f, 150f);
    Call("AddButton", "Get Started", ".start", 20f, 200f);
}

Image requirements:

  • Must be PNG or JPG format
  • Embed as resource in your mod DLL
  • Keep file size under 500KB
  • Recommended: Use power-of-2 dimensions (256x128, 512x256, etc.)

See Working with Images for detailed guide.


Example 8: Background Image

private void RegisterWindowWithBackground()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "StyledPanel");
    Call("SetUI", 800, 600);
    Call("SetTitle", "Styled Panel");
    
    // Background image covers entire window
    Call("AddImage", "background.png", 0f, 0f, 800f, 600f);
    
    // Add content on top of background
    Call("AddText", "<color=#FFFFFF><size=20><b>Welcome!</b></size></color>", 
         50f, 100f);
    Call("AddButton", "Start", ".start", 50f, 200f);
}

Advanced Features (WIP)

⚠️ Warning: The following features are currently in development and may not work as expected. Use with caution in production environments.

Removing Elements Dynamically

ZUI supports removing buttons and elements at runtime:

// Remove a specific button by text
Call("RemoveButton", "Old Button");

// Remove any element by ID
Call("RemoveElement", "elementId");

Example use case - Toggle button:

private bool _godModeEnabled = false;

private void RegisterToggleExample()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "Main");
    Call("AddCategory", "Toggle Features");
    Call("AddButton", "Enable God Mode", ".togglegod");
}

// In your command handler
private void HandleToggleGod()
{
    _godModeEnabled = !_godModeEnabled;
    
    // Remove old button
    Call("RemoveButton", _godModeEnabled ? "Enable God Mode" : "Disable God Mode");
    
    // Add new button with opposite state
    Call("AddButton", 
         _godModeEnabled ? "Disable God Mode" : "Enable God Mode", 
         ".togglegod");
}

Button Callbacks (WIP)

Instead of using chat commands, you can register buttons with direct C# callbacks:

// Traditional approach (commands)
Call("AddButton", "Heal", ".heal");

// Callback approach (WIP)
Call("AddButtonWithCallback", "Heal", new Action(() => 
{
    HealPlayer(); // Direct method call
}));

When to use callbacks:

  • When you don't want to expose commands to chat
  • For internal mod logic that shouldn't be command-accessible
  • When you need to pass parameters or complex logic

Example:

private void RegisterCallbackButtons()
{
    Call("SetPlugin", "MyMod");
    Call("SetTargetWindow", "CallbackDemo");
    Call("SetUI", 500, 300);
    Call("SetTitle", "Callback Demo");
    
    Call("AddButtonWithCallback", "Heal", new Action(() => 
    {
        Log.LogInfo("Healing player via callback!");
        ApplyHeal();
    }));
    
    Call("AddButtonWithCallback", "Save Data", new Action(() => 
    {
        Log.LogInfo("Saving data via callback!");
        SavePlayerData();
    }));
}

Note: These WIP features may have bugs or unexpected behavior. If you encounter issues, please use the traditional command-based approach or report bugs to the ZUI developer.


Complete Example Projects

Full Integration Example

Here's a complete, production-ready mod integration:

using BepInEx;
using BepInEx.Unity.IL2CPP;
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using UnhollowerRuntimeLib;

[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
[BepInDependency("Zanakinz.ZUI", BepInDependency.DependencyFlags.SoftDependency)]
public class Plugin : BaseUnityPlugin
{
    private static Type _zuiType;
    
    public override void Load()
    {
        Log.LogInfo($"Loading {MyPluginInfo.PLUGIN_NAME}...");
        
        // Initialize core functionality
        InitializeCoreFunctionality();
        
        // Initialize UI if ZUI is available
        if (InitZUI())
        {
            RegisterAllUI();
            Log.LogInfo("ZUI integration enabled");
        }
        else
        {
            Log.LogInfo("ZUI not found - continuing without UI");
        }
    }
    
    private void InitializeCoreFunctionality()
    {
        // Your mod's core logic that works without ZUI
        Log.LogInfo("Core functionality initialized");
    }
    
    private bool InitZUI()
    {
        if (!IL2CPPChainloader.Instance.Plugins.ContainsKey("Zanakinz.ZUI"))
            return false;
        
        var assembly = AppDomain.CurrentDomain.GetAssemblies()
            .FirstOrDefault(a => a.GetName().Name == "ZUI");
        
        if (assembly == null) return false;
        
        _zuiType = assembly.GetType("ZUI.API.ZUI");
        return _zuiType != null;
    }
    
    private void RegisterAllUI()
    {
        RegisterMainMenuButtons();
        RegisterControlPanel();
        RegisterStatusWindow();
    }
    
    private void RegisterMainMenuButtons()
    {
        Call("SetPlugin", "MyMod");
        Call("SetTargetWindow", "Main");
        Call("AddCategory", "Quick Actions");
        Call("AddButton", "Heal", ".heal");
        Call("AddButton", "God Mode", ".god");
    }
    
    private void RegisterControlPanel()
    {
        Call("SetPlugin", "MyMod");
        Call("SetTargetWindow", "ControlPanel");
        Call("SetUI", 600, 500);
        Call("SetTitle", "Control Panel");
        
        var layout = new LayoutHelper(20f, 45f);
        
        Call("AddText", "<size=16><b>Player Controls</b></size>", 
             layout.Margin, layout.NextY());
        Call("AddButton", "Heal", ".heal", layout.Margin, layout.NextY());
        Call("AddButton", "Speed Boost", ".speed", layout.Margin, layout.NextY());
        
        Call("AddText", "<size=16><b>Admin Controls</b></size>", 
             layout.Margin, layout.NextY() + 20f);
        Call("AddButton", "Save World", ".save", layout.Margin, layout.NextY());
    }
    
    private void RegisterStatusWindow()
    {
        Call("SetPlugin", "MyMod");
        Call("SetTargetWindow", "Status");
        Call("SetUI", 400, 300);
        Call("SetTitle", "Server Status");
        
        Call("AddText", "Server: Online", 20f, 50f);
        Call("AddText", "Players: 5/10", 20f, 100f);
        Call("AddButton", "Refresh", ".refresh", 20f, 150f);
    }
    
    private void Call(string methodName, params object[] args)
    {
        if (_zuiType == null) return;
        
        try
        {
            var method = _zuiType.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .FirstOrDefault(m => m.Name == methodName && 
                                     m.GetParameters().Length == args.Length);
            
            if (method != null)
            {
                method.Invoke(null, args);
            }
            else
            {
                Log.LogWarning($"ZUI method not found: {methodName}");
            }
        }
        catch (Exception ex)
        {
            Log.LogError($"Error calling ZUI.{methodName}: {ex.Message}");
        }
    }
}

public class LayoutHelper
{
    private float _currentY;
    private readonly float _margin;
    private readonly float _spacing;
    
    public LayoutHelper(float margin = 20f, float spacing = 50f)
    {
        _margin = margin;
        _spacing = spacing;
        _currentY = margin;
    }
    
    public float NextY()
    {
        float y = _currentY;
        _currentY += _spacing;
        return y;
    }
    
    public float Margin => _margin;
}

Tips & Best Practices

  1. Always use soft dependencies - Your mod should work without ZUI
  2. Use constants - Define layout values as constants for easy updates
  3. Test without ZUI - Ensure your mod doesn't crash when ZUI is missing
  4. Organize logically - Group related buttons in categories
  5. Use the Visual Designer - Preview layouts at https://zanakinz.github.io/ZUI
  6. Handle errors - Wrap ZUI calls in try-catch blocks
  7. Keep it simple - Start with basic features, add complexity gradually

Related Pages


Ready to build your own UI? Start with a simple example and expand from there!