# How to Create a Custom UI Panel with TEngine's UIModule: Complete Integration Guide

> Learn how to create a custom UI panel in TEngine by integrating with UIModule. Follow this complete guide to build your own UI windows and display them easily.

- Repository: [ALEX/tengine](https://github.com/alex-rachel/tengine)
- Tags: how-to-guide
- Published: 2026-02-24

---

**To create a custom UI panel in TEngine, inherit from the `UIWindow` base class, apply the `WindowAttribute` to define the prefab path and layer, override lifecycle methods like `OnCreate` and `OnRefresh`, and use `UIModule.Instance.ShowUI<T>()` to display it.**

TEngine's UI framework centers around the `UIModule` singleton, which manages a hierarchical stack of windows through the `UIWindow` base class. Creating a custom UI panel requires understanding this relationship and implementing the specific lifecycle hooks provided by the engine's architecture.

## Understanding TEngine's UI Architecture

Before writing code, you need to understand three core components that handle window management, rendering order, and lifecycle events.

### The UIModule Singleton

The `UIModule` (located in [`UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIModule.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIModule.cs)) serves as the central manager for all UI operations. It maintains a window stack, handles asynchronous loading, manages depth sorting, and controls visibility states. You access it via `UIModule.Instance` to open, close, or query panels.

### The UIWindow Base Class

Every custom panel must inherit from `UIWindow` (defined in [`UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIWindow.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIWindow.cs)). This abstract base class provides virtual methods for the window lifecycle—`OnCreate`, `OnRefresh`, `OnUpdate`, and `OnDestroy`—along with utilities like `SetUIFit()` for notch-safe layouts.

### The WindowAttribute Metadata

The `WindowAttribute` (found alongside [`UIWindow.cs`](https://github.com/alex-rachel/tengine/blob/main/UIWindow.cs)) maps your class to a specific prefab asset and configures its behavior. Key properties include:

- `Location`: The prefab path under Resources or AssetBundles (e.g., `"UI/Prefabs/ExamplePanel"`)
- `WindowLayer`: The UI layer enum (typically `UILayer.UI`)
- `FullScreen`: Boolean indicating if the panel occupies the full screen
- `HideTimeToClose`: Delay in seconds before auto-closing (0 for no auto-close)

## Step-by-Step Implementation

Follow these steps to create a fully integrated custom UI panel.

### 1. Create the Script File

Create a new C# script in `UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/` or your preferred subfolder.

### 2. Inherit from UIWindow

Declare your class with the `UIWindow` base type.

### 3. Apply WindowAttribute

Decorate the class with `[WindowAttribute]` to specify the prefab location and layer properties.

### 4. Implement Lifecycle Methods

Override the virtual methods you need:

- `OnCreate()`: Called once when the prefab instantiates—cache UI references here
- `OnRefresh()`: Called each time the window becomes visible
- `OnUpdate()`: Called every frame while the panel is active
- `OnDestroy()`: Cleanup listeners and references

### 5. Reference UI Elements

Use `transform.Find()` or `GetComponent()` to cache references to buttons, text fields, and other UI elements during `OnCreate`.

### 6. Handle Notch Safety

Call `SetUIFit()` with `liuHaiFit: true` on your `RectTransform` to ensure compatibility with device notches.

## Complete Code Example

Here is a production-ready implementation of a custom message panel:

```csharp
using UnityEngine;
using GameLogic;

[WindowAttribute(
    Location = "UI/Prefabs/ExamplePanel",
    WindowLayer = UILayer.UI,
    FullScreen = false,
    HideTimeToClose = 0)]
public class ExamplePanel : UIWindow
{
    private Text _messageText;
    private Button _closeBtn;

    protected override void OnCreate()
    {
        // Cache components
        _messageText = transform.Find("MessageText").GetComponent<Text>();
        _closeBtn = transform.Find("CloseButton").GetComponent<Button>();
        
        // Bind events
        _closeBtn.onClick.AddListener(() => Close());
        
        // Apply notch-safe fitting
        SetUIFit(GetComponent<RectTransform>(), liuHaiFit: true);
    }

    public void SetMessage(string text)
    {
        if (_messageText != null)
            _messageText.text = text;
    }

    protected override void OnRefresh()
    {
        // Refresh logic called when panel becomes visible
    }

    protected override void OnUpdate()
    {
        // Frame updates while panel is shown
    }

    protected override void OnDestroy()
    {
        // Prevent memory leaks
        if (_closeBtn != null)
            _closeBtn.onClick.RemoveAllListeners();
    }
}

```

## Opening and Closing Your Custom Panel

Use the `UIModule` API to manage panel visibility from other game logic.

### Opening Panels

Call `ShowUI<T>()` for synchronous opening or `ShowUIAsync<T>()` for asynchronous loading:

```csharp
public class Demo : MonoBehaviour
{
    void Start()
    {
        // Open immediately
        UIModule.Instance.ShowUI<ExamplePanel>();
        
        // Or open with data
        ShowMessagePanel("Welcome to TEngine");
    }

    void ShowMessagePanel(string message)
    {
        UIModule.Instance.ShowUI<ExamplePanel>();
        var panel = UIModule.Instance.GetUIAsyncAwait<ExamplePanel>().Result;
        panel?.SetMessage(message);
    }
}

```

### Hiding and Closing Panels

From inside the panel, call instance methods:

```csharp
public void OnCloseButtonClicked()
{
    Hide();   // Makes invisible but keeps in stack
    // or
    Close();  // Destroys and removes from stack
}

```

From external scripts, use the generic module methods:

```csharp
// Hide (preserve instance)
UIModule.Instance.HideUI<ExamplePanel>();

// Close (destroy instance)
UIModule.Instance.CloseUI<ExamplePanel>();

```

## Key Source Files

Understanding these implementation files helps when debugging or extending functionality:

- **[`UIModule.cs`](https://github.com/alex-rachel/tengine/blob/main/UIModule.cs)**: Core manager at [`UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIModule.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIModule.cs) handling window stacks and resource loading
- **[`UIWindow.cs`](https://github.com/alex-rachel/tengine/blob/main/UIWindow.cs)**: Base class at [`UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIWindow.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/GameScripts/HotFix/GameLogic/Module/UIModule/UIWindow.cs) providing lifecycle hooks and safe-fit helpers
- **[`WindowAttribute.cs`](https://github.com/alex-rachel/tengine/blob/main/WindowAttribute.cs)**: Metadata decorator defining prefab paths and layer properties

## Summary

Creating a custom UI panel in TEngine requires understanding the relationship between `UIModule` and `UIWindow`.

- Inherit from `UIWindow` and apply `[WindowAttribute]` to declare prefab paths and layers
- Override lifecycle methods (`OnCreate`, `OnRefresh`, `OnUpdate`, `OnDestroy`) to implement panel logic
- Use `UIModule.Instance.ShowUI<T>()` and `HideUI<T>()` to manage visibility through the engine's stack system
- Reference `transform.Find()` and `SetUIFit()` for safe element binding and device compatibility

## Frequently Asked Questions

### How do I specify which prefab to use for my custom UI panel?

Apply the `[WindowAttribute]` to your class and set the `Location` property to the Resource or AssetBundle path, such as `[WindowAttribute(Location = "UI/Prefabs/MyPanel")]`. The `UIModule` uses this path to load the GameObject when `ShowUI<T>()` is called.

### What is the difference between Hide() and Close() in TEngine's UIWindow?

`Hide()` makes the panel invisible but keeps it in the `UIModule` window stack, preserving its state for quick redisplay. `Close()` destroys the GameObject and removes it from the stack entirely, requiring a full reload via `ShowUI<T>()` the next time you need it.

### Can I pass data to a UI panel when opening it?

Yes, but not directly through `ShowUI<T>()`. First call `UIModule.Instance.ShowUI<T>()` to ensure the panel exists, then use `UIModule.Instance.GetUIAsyncAwait<T>()` to retrieve the instance and call public methods like `SetMessage()` to pass initialization data.

### Where should I cache UI element references like Buttons and Text components?

Override `OnCreate()` in your `UIWindow` subclass and use `transform.Find("ButtonName").GetComponent<Button>()` to cache references. This method runs once when the prefab instantiates, ensuring components exist before you interact with them.