背景
游戲中的UI系統或者叫做GUI窗口系統主要有:主要裝備窗口(背包,角色窗口也是一種特殊窗口)、確實提示窗口(如購買確認)、信息提示窗口(一遍沒有按鈕,ContexntMenu)和特殊窗口(聊天記錄或者技能樹),前篇已經介紹分析了Inventory Pro確認提示窗口的設計和實現方式,這篇主要講一下信息提示窗口的實現。本以為提示窗口是比較簡單的,畢竟沒有按鈕事件交互的問題,但是分析了下源代碼還是讓我有些驚訝,插件作者在提示窗口中考慮到了性能問題,由於本人一直在PC端開發程序沒有移動端的經驗,所以在移動端對於性能優化還是比較關注的。
聲明:本文首發於蠻牛,次發博客園,本人原創。 原文鏈接1,原文鏈接2
插件效果及使用
左下角即為信息提示窗口NoticeUI,當信息提示比較多時,具有滾動條和超出自動隱藏的功能,是通過對象池技術實現,提高性能和效率
通過拖拽的方式創建好UI界面,紅框中我們看到了組件樹的結構和類型
在NoticeUI上綁定NoticeUI腳本,設置好每一行顯示的預設NoticeMessageUI,ScrollRect等相關屬性,基本就已經完成了關於信息提示窗口的實現了
源代碼分析
類圖分析
經過這段時間的學習,我真的慢慢愛上了VS的類圖分析了,希望新手同學也能習慣這點。VS的類圖很強大能自動生成關聯關系和繼承接口等信息,是特別舒心的類圖工具。
A、先看下Message模型(Model)類,InventoryNoticeMessage繼承了InventoryMessage,繼承后擁有的字段有,消息,標題,顏色,消失延時,時間看到這些字段我們大致也可以猜到信息提示窗口有哪些功能了吧(其實是可以擴展的),這里需要重點關注下Show方法(后面源碼分析再表述)
B、NoticeUI和NoticeMessageUI都是MonoBehavior的子類,也就是說他們都是組件,分析其字段有具有ScrollRect和Text說明他們都是需要和UI進行綁定的。這里特變關注下VS使用雙箭頭表示組合關聯,所以NoticeUI組合關聯NoticeMessageUI,而繼承了IPoolableObject接口顧名思義它具有入對象池的能力,也就是可以加入對象池,我們也看到了NoticeUI有一個InventoryPool<NoticeMessageUI>,我們大概可以猜到NoticeUI中List<NoticMessageUI>和InevntoryPool<NoticeMessageUI>猥瑣的關系。
調用流程分析
調用的流程其實可以畫一個流程圖,這里只是簡單的描述一下
1、InventoryNoticeMessage.Show() –>2、 全局NoticeUI.AddMessage()->3、InventoryPool池對象NoticeMessageUI.SetMessage()->4、NoticMessageUI通過setAictive(true)進行顯示->5、NoticeUI.Update,通過循環調用NoticMessageUI的showTime.deltaTime做控制隱藏
1、InventoryNoticeMessage.Show()
通過以上代碼我們看的出來其實notice也是一個全局的UI,所以才可以通過單例來訪問,應該是有固定區域的。
2、 全局NoticeUI.AddMessage()
NoticeUI中的AddMessage就比較復雜了,主要要處理幾個事情A、事件觸發;B、滾動處理;C、對象池獲取NoticeMessageUI並激活顯示D、List<NoticeMessageUI>和InventoryPool<NoticeMessageUI>好基友的處理(對象池的回收及引用數組的移除)
3、InventoryPool池對象NoticeMessageUI.SetMessage()
SetMessage() 就像它的方法名一樣好像什么也沒有做的樣子,只是設置了一些簡單字段的內容以及顯示時間,實際的顯示激活卻是在4對象池獲取的時候置位的。
4、NoticMessageUI通過setAictive(true)進行顯示
對象池的使用和回收是通過池對象的activeSelf屬性來確定的,這個開關有一箭雙雕的意思,既通過它來控制對象池的使用和回收,又用於控制UI對象的演示與否。
5、NoticeUI.Update,通過循環調用NoticMessageUI的showTime.deltaTime控制隱藏
通過顯示時間來控制信息的隱藏
隱藏函數使用了動畫效果,由於動畫是有顯示時間的,所以通過一個字段isHiding做為狀態判斷。
核心源碼
NoticeUI

using UnityEngine; using UnityEngine.EventSystems; using System; using System.Collections; using System.Collections.Generic; using Devdog.InventorySystem.Models; using Devdog.InventorySystem.UI.Models; using UnityEngine.UI; namespace Devdog.InventorySystem { /// <summary> /// How long a message should last. /// Parse to int to get time in seconds. /// </summary> public enum NoticeDuration { Short = 2, Medium = 4, Long = 6, ExtraLong = 8 } [AddComponentMenu("InventorySystem/Windows/Notice")] public partial class NoticeUI : MonoBehaviour { #region Events /// <summary> /// Note that it also fired when message == null or empty, even though the system won't process the message. /// This is because someone might want to implement their own system and just use the event as a link to connect the 2 systems. /// </summary> /// <param name="title"></param> /// <param name="message"></param> /// <param name="duration"></param> /// <param name="parameters"></param> public delegate void NewMessage(InventoryNoticeMessage message, params System.Object[] parameters); public event NewMessage OnNewMessage; #endregion [Header("General")] public NoticeMessageUI noticeRowPrefab; [InventoryRequired] public RectTransform container; public ScrollRect scrollRect; public AudioClip onNewMessageAudioClip; /// <summary> /// When more messages come in the last items will be removed. /// </summary> [Header("Messages")] public int maxMessages = 50; /// <summary> /// Remove the item after the show time has passed, if false, the item will continue to exist. /// </summary> public bool destroyAfterShowTime = true; /// <summary> /// All show times are multiplied by this value, if you want to increase all times, use this value. /// </summary> public float showTimeFactor = 1.0f; [NonSerialized] protected List<NoticeMessageUI> messages = new List<NoticeMessageUI>(8); private InventoryPool<NoticeMessageUI> pool; public virtual void Awake() { pool = new InventoryPool<NoticeMessageUI>(noticeRowPrefab, maxMessages); } public virtual void Update() { if (destroyAfterShowTime == false) return; foreach (var message in messages) { message.showTime -= Time.deltaTime; if (message.showTime < 0.0f) { message.Hide(); } } } public virtual void AddMessage(string message, NoticeDuration duration = NoticeDuration.Medium) { AddMessage(message, duration); } public virtual void AddMessage(string message, NoticeDuration duration, params System.Object[] parameters) { AddMessage(string.Empty, message, duration, parameters); } public virtual void AddMessage(string title, string message, NoticeDuration duration, params System.Object[] parameters) { AddMessage(new InventoryNoticeMessage(title, message, duration)); } public virtual void AddMessage(InventoryNoticeMessage message) { // Fire even if we do the nullcheck, just incase other people want to use their own implementation. if (OnNewMessage != null) OnNewMessage(message, message.parameters); if (string.IsNullOrEmpty(message.message)) return; bool scrollbarAtBottom = false; if (scrollRect != null && scrollRect.verticalScrollbar != null && scrollRect.verticalScrollbar.value < 0.05f) scrollbarAtBottom = true; // Incase we don't actually want to display anything and just port the data to some other class through events. if (noticeRowPrefab != null) { var item = pool.Get(); //var item = GameObject.Instantiate<NoticeMessageUI>(noticeRowPrefab); item.transform.SetParent(container); item.transform.SetSiblingIndex(0); // Move to the top of the list item.SetMessage(message); if (onNewMessageAudioClip != null) InventoryUIUtility.AudioPlayOneShot(onNewMessageAudioClip); messages.Add(item); } if (messages.Count > maxMessages) { StartCoroutine(DestroyAfter(messages[0], messages[0].hideAnimation.length)); messages[0].Hide(); messages.RemoveAt(0); } if (scrollbarAtBottom) scrollRect.verticalNormalizedPosition = 0.0f; } protected virtual IEnumerator DestroyAfter(NoticeMessageUI item, float time) { yield return new WaitForSeconds(time); pool.Destroy(item); } } }
NoticeMessageUI

using System; using System.Collections; using System.Collections.Generic; using Devdog.InventorySystem.Models; using UnityEngine; using UnityEngine.UI; namespace Devdog.InventorySystem.UI.Models { /// <summary> /// A single message inside the message displayer /// </summary> [RequireComponent(typeof(Animator))] public partial class NoticeMessageUI : MonoBehaviour, IPoolableObject { public UnityEngine.UI.Text title; public UnityEngine.UI.Text message; public UnityEngine.UI.Text time; public AnimationClip showAnimation; public AnimationClip hideAnimation; [HideInInspector] public float showTime = 4.0f; public DateTime dateTime { get; private set; } [NonSerialized] protected Animator animator; [NonSerialized] protected bool isHiding = false; // In the process of hiding public virtual void Awake() { animator = GetComponent<Animator>(); if (showAnimation != null) animator.Play(showAnimation.name); } public virtual void SetMessage(InventoryNoticeMessage message) { this.showTime = (int)message.duration; this.dateTime = message.time; if (string.IsNullOrEmpty(message.title) == false) { if (this.title != null) { this.title.text = string.Format(message.title, message.parameters); this.title.color = message.color; } } else title.gameObject.SetActive(false); this.message.text = string.Format(message.message, message.parameters); this.message.color = message.color; if (this.time != null) { this.time.text = dateTime.ToShortTimeString(); this.time.color = message.color; } } public virtual void Hide() { // Already hiding if (isHiding) return; isHiding = true; if (hideAnimation != null) animator.Play(hideAnimation.name); } public void Reset() { isHiding = false; } } }
InventoryNoticeMessage

using System; using System.Collections.Generic; using System.Threading; using UnityEngine; namespace Devdog.InventorySystem.Models { [System.Serializable] public partial class InventoryNoticeMessage : InventoryMessage { public DateTime time; public Color color = Color.white; public NoticeDuration duration = NoticeDuration.Medium; /// <summary> /// Required for PlayMaker... /// </summary> public InventoryNoticeMessage() { } public InventoryNoticeMessage(string title, string message, NoticeDuration duration, params System.Object[] parameters) : this(title, message, duration, Color.white, DateTime.Now, parameters) { } public InventoryNoticeMessage(string title, string message, NoticeDuration duration, Color color, params System.Object[] parameters) : this(title, message, duration, color, DateTime.Now, parameters) { } public InventoryNoticeMessage(string title, string message, NoticeDuration duration, Color color, DateTime time, params System.Object[] parameters) { this.title = title; this.message = message; this.color = color; this.time = time; this.parameters = parameters; } public override void Show(params System.Object[] param) { base.Show(param); this.time = DateTime.Now; if (InventoryManager.instance.notice != null) InventoryManager.instance.notice.AddMessage(this); } } }