Xây dựng UI Framework Cơ Bản trong Unity

Khi phát triển game trong Unity, việc xử lý UI panel thường tuân theo một quy trình nhất quán. UI Framework ra đời nhằm tối ưu hóa quá trình này, tái sử dụng code và giảm thiểu lỗi phát sinh.

1. Lớp nền tảng cho UI panel (BasePanel)

Lớp BasePanel chịu trách nhiệm tự động tìm kiếm các thành phần UI và đăng ký sự kiện, giúp lập trình viên không cần viết code lặp lại cho mỗi panel mới.

using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public abstract class BasePanel : MonoBehaviour
{
    // Dictionary lưu trữ các control UI, key là tên control, value là đối tượng UIBehaviour
    protected Dictionary<string, UIBehaviour> controlMap = new Dictionary<string, UIBehaviour>();

    // Danh sách tên mặc định, nếu control có tên nằm trong danh sách này thì sẽ không được thêm vào controlMap
    private static List<string> defaultNames = new List<string>()
    {
        "Image", "Text (TMP)", "RawImage", "Background", "Checkmark",
        "Label", "Text (Legacy)", "Arrow", "Placeholder", "Fill",
        "Handle", "Viewport", "Scrollbar Horizontal", "Scrollbar Vertical"
    };

    protected virtual void Awake()
    {
        // Ưu tiên tìm kiếm các control phức tạp trước
        FindControls<Button>();
        FindControls<Toggle>();
        FindControls<Slider>();
        FindControls<InputField>();
        FindControls<ScrollRect>();
        FindControls<Dropdown>();
        // Các control đơn giản sau
        FindControls<Text>();
        FindControls<TextMeshPro>();
        FindControls<Image>();
    }

    // Phương thức trừu tượng cho hành vi show/hide panel
    public abstract void ShowPanel();
    public abstract void HidePanel();

    // Lấy control theo tên và kiểu
    public T GetUIComponent<T>(string componentName) where T : UIBehaviour
    {
        if (controlMap.TryGetValue(componentName, out UIBehaviour value))
        {
            T target = value as T;
            if (target == null)
                Debug.LogError($"Không tìm thấy component tên '{componentName}' với kiểu {typeof(T)}");
            return target;
        }
        Debug.LogError($"Component '{componentName}' không tồn tại trong panel");
        return null;
    }

    // Các phương thức ảo cho sự kiện (có thể override ở lớp con)
    protected virtual void OnButtonClick(string btnName) { }
    protected virtual void OnSliderChanged(string sliderName, float newValue) { }
    protected virtual void OnToggleChanged(string toggleName, bool isOn) { }

    private void FindControls<T>() where T : UIBehaviour
    {
        T[] allControls = GetComponentsInChildren<T>(true);
        foreach (T control in allControls)
        {
            string ctrlName = control.gameObject.name;
            if (!controlMap.ContainsKey(ctrlName) && !defaultNames.Contains(ctrlName))
            {
                controlMap.Add(ctrlName, control);
                // Đăng ký sự kiện tự động dựa trên kiểu control
                if (control is Button btn)
                {
                    btn.onClick.AddListener(() => OnButtonClick(ctrlName));
                }
                else if (control is Slider slider)
                {
                    slider.onValueChanged.AddListener((val) => OnSliderChanged(ctrlName, val));
                }
                else if (control is Toggle toggle)
                {
                    toggle.onValueChanged.AddListener((val) => OnToggleChanged(ctrlName, val));
                }
            }
        }
    }
}

2. UI Manager - Quản lý tất cả các panel

UIMgr là singleton chịu trách nhiệm quản lý việc hiển thị, ẩn panel và đảm bảo mọi thao tác đều thông qua nó. Panel prefab phải có tên trùng với tên lớp.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public enum EPanelLayer
{
    Bottom,
    Middle,
    Top,
    System
}

public class UIMgr : SingletonBase<UIMgr>
{
    private Camera uiCamera;
    private Canvas mainCanvas;
    private EventSystem eventSystem;

    private Transform bottomLayer;
    private Transform middleLayer;
    private Transform topLayer;
    private Transform systemLayer;

    private Dictionary<string, BasePanel> activePanels = new Dictionary<string, BasePanel>();

    private UIMgr()
    {
        // Khởi tạo UI Camera
        uiCamera = Object.Instantiate(ResourceManager.Instance.Load<GameObject>("UI/UICamera")).GetComponent<Camera>();
        Object.DontDestroyOnLoad(uiCamera.gameObject);

        // Khởi tạo Canvas
        mainCanvas = Object.Instantiate(ResourceManager.Instance.Load<GameObject>("UI/Canvas")).GetComponent<Canvas>();
        mainCanvas.worldCamera = uiCamera;
        Object.DontDestroyOnLoad(mainCanvas.gameObject);

        // Lấy các layer parent
        bottomLayer = mainCanvas.transform.Find("Bottom");
        middleLayer = mainCanvas.transform.Find("Middle");
        topLayer = mainCanvas.transform.Find("Top");
        systemLayer = mainCanvas.transform.Find("System");

        // Khởi tạo EventSystem
        eventSystem = Object.Instantiate(ResourceManager.Instance.Load<GameObject>("UI/EventSystem")).GetComponent<EventSystem>();
        Object.DontDestroyOnLoad(eventSystem.gameObject);
    }

    private Transform GetLayerParent(EPanelLayer layer)
    {
        return layer switch
        {
            EPanelLayer.Bottom => bottomLayer,
            EPanelLayer.Middle => middleLayer,
            EPanelLayer.Top => topLayer,
            EPanelLayer.System => systemLayer,
            _ => middleLayer
        };
    }

    public void OpenPanel<T>(EPanelLayer layer = EPanelLayer.Middle, UnityAction<T> callback = null) where T : BasePanel
    {
        string panelName = typeof(T).Name;
        if (activePanels.TryGetValue(panelName, out BasePanel existingPanel))
        {
            existingPanel.ShowPanel();
            callback?.Invoke(existingPanel as T);
            return;
        }

        // Tải panel bất đồng bộ (có thể đồng bộ nếu cần)
        ResourceManager.Instance.LoadAsync<GameObject>("UI", panelName, (prefab) =>
        {
            Transform parent = GetLayerParent(layer);
            GameObject panelObj = Object.Instantiate(prefab, parent, false);
            T panel = panelObj.GetComponent<T>();
            panel.ShowPanel();
            callback?.Invoke(panel);
            activePanels.Add(panelName, panel);
        });
    }

    public void ClosePanel<T>() where T : BasePanel
    {
        string panelName = typeof(T).Name;
        if (activePanels.TryGetValue(panelName, out BasePanel panel))
        {
            panel.HidePanel();
            Object.Destroy(panel.gameObject);
            activePanels.Remove(panelName);
        }
    }

    public T GetPanel<T>() where T : BasePanel
    {
        string panelName = typeof(T).Name;
        if (activePanels.TryGetValue(panelName, out BasePanel panel))
            return panel as T;
        return null;
    }
}

Lưu ý: Đây là phiên bản cơ bản, chưa bao gồm các xử lý phức tạp như stacking, animation, hoặc pooling. Bạn có thể mở rộng dựa trên nhu cầu dự án.

Thẻ: Unity UI Framework BasePanel C# Game Development

Đăng vào ngày 28 tháng 5 lúc 17:37