Unity 極簡UI框架
寫ui的時候一般追求控制邏輯和顯示邏輯分離,經典的類似於MVC,其餘大多都是這個模式的衍生,實際上書寫的時候M是在整個遊戲的底層,我更傾向於將它稱之為D(Data)而不是M(Model),而C(Ctrl)負責接收用戶的各類UI事件,例如點擊,滑動,還有其他遊戲邏輯板塊發過來的事件或消息,處理這些消息並更新V(View)當中的各類顯示數據,這裡更新數據的方式可以抽象為兩種:
1.外部事件觸發View更新,這時不用在意底層數據更新,因為在刷新View之前這些改變的數據可以在其他邏輯版塊中直接更新完。
2.UI內部點擊,滑動等事件觸發View更新,這種情況下有可能需要更新底層數據,但最好不要直接修改和調用,而是選擇向外部發送事件和消息的方式來告知外部需要更新數據。
無論是上面兩種情況中的哪一種,都不是View直接參与外部邏輯聯繫,而是藉助中間的Ctrl來聯繫,Ctrl中處理UI與外部對接的所有邏輯,並能夠及時的更新View。
再來分析下Ctrl,我們發現Ctrl的控制流程是可以固定下來,抽象如下:
1.進入一個View介面之前,得到View組件,初始化View中各個元素的狀態
2.播放一段進入動畫,例如淡入
3.進入動畫播放完成後,對View中的一些元素添加事件偵聽,或對外部的一些事件添加偵聽
4.當偵聽中的事件觸發後,可以選擇是否對View更新,或向外部發送事件,消息
5.同樣的,離開時播放一段動畫,例如淡出
6.離開動畫播放完成後,移除所有事件偵聽,載入一個新的View或場景
定義Ctrl基類:
1 using UnityEngine; 2 using UnityEngine.Events; 3 using UnityEngine.SceneManagement; 4 5 [RequireComponent(typeof(Canvas))] 6 public class HudBase : MonoBehaviour 7 { 8 public GameObject Root; 9 protected Canvas Canvas; 10 private void Awake() 11 { 12 Canvas = GetComponent<Canvas>(); 13 } 14 15 private void Start() 16 { 17 InitState(); 18 Enter(() => AddListeners()); 19 } 20 21 private void OnDestroy() => RemoveListeners(); 22 23 protected virtual void InitState() { } 24 25 protected virtual void AddListeners() { } 26 27 protected virtual void RemoveListeners() { } 28 29 protected void Enter(UnityAction complete) => Canvas.FadeIn(Root, () => complete()); 30 31 protected void ExitTo(string sceneName) => Canvas.FadeOut(Root, () => SceneManager.LoadScene(sceneName)); 32 33 protected T GetViweComponent<T>() where T : HudView => GetComponent<T>(); 34 35 protected void UpdateView<T>(T t) where T : HudView => t.Refresh(); 36 }
View基類:
1 using UnityEngine; 2 3 public class HudView : MonoBehaviour 4 { 5 public virtual void Refresh() { } 6 }
View只有一個自帶的更新視圖的通用方法,數據來源則直接取遊戲底層即可,能夠從Ctrl中直接調用View視圖的更新。
其他通用的UI方法則全部寫在一個統一的地方,例如淡入淡出的函數,向外部發送事件,偵聽事件等,這裡統一寫成了Canvas的擴展方法,便於在基類中也方便直接調用:
1 using System.Collections.Generic; 2 using UnityEngine; 3 using UnityEngine.UI; 4 using UnityEngine.Events; 5 using DG.Tweening; 6 7 public static class HudHelper 8 { 9 //UI 10 public static void FadeIn(this Canvas canvas, GameObject target, TweenCallback action) 11 { 12 var cg = target.GetOrAddComponent<CanvasGroup>(); 13 cg.alpha = 0; 14 cg.DOFade(1, .3f).OnComplete(action); 15 } 16 17 public static void FadeOut(this Canvas canvas, GameObject target, TweenCallback action) 18 { 19 var cg = target.GetOrAddComponent<CanvasGroup>(); 20 cg.DOFade(0, .3f).OnComplete(action); 21 } 22 23 public static void SendEvent<T>(this Canvas canvas, T e) where T : GameEvent => EventManager.QueueEvent(e); 24 25 public static void AddListener<T>(this Canvas canvas, EventManager.EventDelegate<T> del) where T : GameEvent => EventManager.AddListener(del); 26 27 public static void RemoveListener<T>(this Canvas canvas, EventManager.EventDelegate<T> del) where T : GameEvent => EventManager.RemoveListener(del); 28 29 public static void ButtonListAddListener(this Canvas canvas, List<Button> buttons, UnityAction action) 30 { 31 foreach (var bt in buttons) 32 { 33 bt.onClick.AddListener(action); 34 } 35 } 36 37 public static void ButtonListRemoveListener(this Canvas canvas, List<Button> buttons) 38 { 39 foreach (var bt in buttons) 40 { 41 bt.onClick.RemoveAllListeners(); 42 } 43 } 44 }
關於事件隊列可以詳細見之前的隨筆:
//www.cnblogs.com/koshio0219/p/11209191.html
具體的用法如下:(Ctrl)
1 using UnityEngine; 2 3 public class MapCanvasCtrl : HudBase 4 { 5 private MapCanvasView View; 6 public GameObject UnderPanel; 7 8 protected override void InitState() 9 { 10 View = GetViweComponent<MapCanvasView>(); 11 UnderPanel.SetActive(false); 12 UpdateView(View); 13 } 14 15 protected override void AddListeners() 16 { 17 View.Back.onClick.AddListener(() => ExitTo("S_Main")); 18 19 Canvas.ButtonListAddListener(View.TaskPoints, () => 20 { 21 UnderPanel.SetActive(true); 22 Canvas.FadeIn(UnderPanel, () => View.HitOut.onClick.AddListener(() => ExitTo("S_DemoBattle"))); 23 }); 24 } 25 26 protected override void RemoveListeners() 27 { 28 View.HitOut.onClick.RemoveAllListeners(); 29 Canvas.ButtonListRemoveListener(View.TaskPoints); 30 } 31 }
只需要重寫以上三個方法即可。
(View):
1 using System.Collections.Generic; 2 using UnityEngine.UI; 3 using TMPro; 4 5 public class MapCanvasView : HudView 6 { 7 public TextMeshProUGUI Resource; 8 public TextMeshProUGUI Difficulty; 9 public TextMeshProUGUI Reward; 10 11 public Button HitOut; 12 public Button Back; 13 14 public List<Button> TaskPoints = new List<Button>(); 15 16 public override void Refresh() 17 { 18 //這裡更新視圖 19 } 20 }