Example & Tutorials
Updated 8 hours agoExamples & Tutorials
Step-by-step guides and practical examples to help you master ZUI integration.
📋 Table of Contents
- Basic Main Menu Integration
- Creating a Simple Custom Window
- Building an Admin Control Panel
- Dynamic Content Updates
- Working with Images
- Advanced Features (WIP)
- Complete Example Projects
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
.healcommand - 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
- Always use soft dependencies - Your mod should work without ZUI
- Use constants - Define layout values as constants for easy updates
- Test without ZUI - Ensure your mod doesn't crash when ZUI is missing
- Organize logically - Group related buttons in categories
- Use the Visual Designer - Preview layouts at https://zanakinz.github.io/ZUI
- Handle errors - Wrap ZUI calls in try-catch blocks
- Keep it simple - Start with basic features, add complexity gradually
Related Pages
- API Reference - Complete method documentation
- Best Practices - Design patterns and optimization
- Visual Designer - Web-based UI designer tool
- Color & Styling - Rich text formatting guide
- Working with Images - Image integration guide
- Troubleshooting - Common issues and solutions
Ready to build your own UI? Start with a simple example and expand from there!