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.