Unity3D 通用提示窗口实现分析(Inventory Pro学习总结)

背景

游戏中的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);
        }
    }
}

时间: 2024-10-15 04:53:39

Unity3D 通用提示窗口实现分析(Inventory Pro学习总结)的相关文章

Unity3d UGUI 通用Confirm确认对话框实现(Inventory Pro学习总结)

背景 曾几何时,在Winform中,使用MessageBox对话框是如此happy,后来还有人封装了可以选择各种图标和带隐藏详情的MessageBox,现在Unity3d UGui就没有了这样的好事情了,所有的UI都需要自己来搞定了,幸好还有各种插件,Inventory Pro中的对话框方案不失一种通用,可复用的方案. YY(自己的想法) 所谓通用对话框,如果是自己实现的话有以下几点需要解决,窗体显示控制,窗体UI布局,窗体文字显示,窗体事件回调,窗体显示动画控制,窗体显示声音控制,窗体与其他窗

Unity3D 装备系统学习Inventory Pro 2.1.2 总结

前言 写在最前面,本文未必适合纯新手,但有一些C#开发经验的还是可以看懂的,虽然本人也是一位Unity3D新人,但是本文只是自己在学习Inventory Pro的学习总结,而不是教程,本人觉得要读懂理解Inventory Pro 2.1.2 这样的插件源码,你还是需有了一部分Unity3D的基础知识.但为什么说你有一定C#开发经验也是能看懂的呢?(有点绕),我想表达的意思是,Unity3D无非是一种技术或者工具,而装备系统是游戏逻辑的一种业务,其实如果侧重点在于业务,技术和工具不是那么重要,希望

通用窗口类 Inventory Pro 2.1.2 Demo1(下续篇 ),物品消耗扇形显示功能

本篇想总结的是Inventory Pro中通用窗口的具体实现,但还是要强调下该插件的重点还是装备系统而不是通用窗口系统,所以这里提到的通用窗口类其实是通用装备窗口类(其实该插件中也有非装备窗口比如NoticeUI等). 本篇涉及的功能用加出标出,具体的功能如下: 1.实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能 2.装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置 3.窗口具有拖拽功能 4.窗口物品具有拖拽,及窗口间拖拽 5.可以在窗口使用物品的功能,物

通用窗口类 Inventory Pro 2.1.2 Demo1(下)

本篇想总结的是Inventory Pro中通用窗口的具体实现,但还是要强调下该插件的重点还是装备系统而不是通用窗口系统,所以这里提到的通用窗口类其实是通用装备窗口类(其实该插件中也有非装备窗口比如NoticeUI等). 本篇涉及的功能用加出标出,具体的功能如下: 1.实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能 2.装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置 3.窗口具有拖拽功能 4.窗口物品具有拖拽,及窗口间拖拽 5.可以在窗口使用物品的功能,物

通用窗口类 Inventory Pro 2.1.2 Demo1(中)

本篇想总结的是Inventory Pro中通用窗口的具体实现,但还是要强调下该插件的重点还是装备系统而不是通用窗口系统,所以这里提到的通用窗口类其实是通用装备窗口类(其实该插件中也有非装备窗口比如NoticeUI等). 本篇涉及的功能用加出标出,具体的功能如下: 1.实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能 2.装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置 3.窗口具有拖拽功能 4.窗口物品具有拖拽,及窗口间拖拽 5.可以在窗口使用物品的功能,物

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上) 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)用户态.内核态和中断处理过程 (二)系统调用概述 系统调用概述和系统调用的三层皮 (三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用 使用库函数API获取系统当前时间 C代码中嵌入汇编代码的方法(复习) 使用C代码中嵌入汇编代码触发系统调

LINUX内核分析第一周学习总结——计算机是如何工作的

LINUX内核分析第一周学习总结——计算机是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.汇编代码的工作过程 1.实验过程 int a(int x) { return x + 2; } int b(int x) { return a(x); } int main(void) { return b(5) + 1; } 汇编代码如下: 2.代码分析 二.计算机工作的

《信息安全系统设计基础+Linux 内核分析》第一次学习总结

<信息安全系统设计基础+Linux 内核分析>第一次学习总结 教材学习内容总结 学习了<庖丁解牛>的第一章.知道的概念有: 存储程序计算机 = 冯诺依曼计算机,主要思想是:将程序存放在计算机存储器中,然后按存储器中的程序的首地址来执行程序的第一条指令,接下来就是一步一步按照程序中的编写好的指令来一步一步执行,直至程序结束. 冯诺依曼体系结构的要点如下图.底层是:RAM,ROM,运算器(ALU),控制器,寄存器. 由图可知:寄存器是在CPU中的,而RAM,ROM不是在CPU中的,它们

通用窗口类 Inventory Pro 2.1.2 Demo1(上)

插件功能 按照Demo1的实现,使用插件来实现一个装备窗口是很easy的,虽然效果还很原始但是也点到为止了,本篇涉及的功能用加粗标出,具体的功能如下: 1.实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能 2.装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置 3.窗口具有拖拽功能 4.窗口物品具有拖拽,及窗口间拖拽 5.可以在窗口使用物品的功能,物品有消耗扇形显示功能 具体效果图如下所示: 插件使用 1.具体在UGUI 中的Canvas中创建一个Invent