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

背景

曾几何时,在Winform中,使用MessageBox对话框是如此happy,后来还有人封装了可以选择各种图标和带隐藏详情的MessageBox,现在Unity3d UGui就没有了这样的好事情了,所有的UI都需要自己来搞定了,幸好还有各种插件,Inventory Pro中的对话框方案不失一种通用,可复用的方案。

YY(自己的想法)

所谓通用对话框,如果是自己实现的话有以下几点需要解决,窗体显示控制,窗体UI布局,窗体文字显示,窗体事件回调,窗体显示动画控制,窗体显示声音控制,窗体与其他窗体的关系,功能虽然小涉及的方面和知识却不少,自己做真的很不容易,所以别再自己造轮子了。

插件实现的效果

简单的确认对话框提示

当扔物品的时候会提示是否确认对话框。

稍微复杂一些的购买物品对话框

当购买物品时会显示出一个购买的物品,物品数量金额的对话框

简单确认对话框的使用

1、使用UGUI来设计一个自己使用的对话框,基本几个元素Title,description ,two buttons;

2、给对话框绑定Draggable Window(Script)使其具有拖拽功能

3、添加Animator,定义对话框显示的时候具有动画效果

4、添加UI Windows(script)使其具有打开关闭,声音,动画的效果

5、Confirmation Dialog(script)使其具有事件回调,model对话框的属性,文字绑定等对话框固有的属性

至此简单的对话框就做好了,这里我们充分见识了绑定技术、组件技术、UI解耦和框架的强大威力

复杂对话框的使用

这里只要知道Item Int Val Dialog(scirpt)其实是ConfirmDialog类的一个子类,剩下的东西就很自然了,这里不详细展开了。

分析

功能需求确定了,如何实现这些功能可能就需要用到一些模式,以及一些经验了,先看一下类图

根据前一节的脑图,类图我们逐个分析,InventoryUIDialogBase 是一个抽象类,也是与UI进行绑定的主体,其没有一个无用的属性,这里重点关注几个字段和属性,UIWindow类是通用的窗口显示和动画控制组件,InventoryMessage是字符串Message的封装类。

1)窗体UI布局

UI布局是通过Unity3d UGUI拖拽的方式设计上去的,这个很简单,首先做到了UI分离

2)窗体文字显示

窗体文字的显示首先是通过后台与UI做的绑定,这里使用Unity3d的组件设计时绑定技术(这里做过WPF的同学有是否有印象MVVM中的绑定),这里关键是文字信息,实际发现其实Dialog类并不关心显示的什么string,而是Inventory Pro提供的(类图中的Message类)一层封装后得到的结果,这里为什么要单独拿出来实际是为了做国际化以及一些文字性的扩展,比如颜色,字体显示的方案。

InventoryLangDataBase类对于所有的消息体文字进行了集中处理,而且本身也是Asset,这里有两种好处一种就是可以集中管理,一种就是为国际化文字。

因为Unity3d UGUI可以做文字颜色和字体的格式化操作,这里完全可以扩展添加有颜色和字体大小的文字重载

3)窗体显示控制,窗体显示动画控制,窗体显示声音控制

窗体显示的控制,完全利用Unity3d平台的组件化功能,通过UIWindow专门拿出来控制,这里看到UIWinow类是必须加载Animator动画类的

窗体的动画控制,由主体DialogBase进行设计时的动画效果绑定,由UIWindow类在控制显示和关闭时进行动画的Play,这里还用到了协程

窗体显示声音控制,由全局类静态方法 InventoryUIUtility.AudioPlayOneShot 来播放即可

3)窗体与其他窗体的关系

这个功能类似于网页中的遮罩或者winform里的模态(ModelDialog)对话框,这里没有现成的东西可以使用只能自己写了,这里如何关闭UGUI的事件处理主要是通过CanvasGroup这个插件来控制

4) 窗体事件回调

窗体中的事件回调交给了Dialog子类来处理,具体是在重载的ShowDialog方法中添加了委托的事件回调函数,然后通过代码绑定的方式(这里是onClick.AddListener,而不是UI手动可视化绑定)进行了按钮事件的绑定,这里有很大的灵活性。我比较喜欢这种通过代码定义显示委托的方式,来完成事件的回调(c++系可能叫做函数指针),同比匿名委托,泛型委托(Action或者Func),Lambda表达式,代码可读性更强

其它

这里留了一个小疑问,对话框的触发显示是如何实现的,我们的(MessageBox.Show)在哪里呢?

看过前面的文章的同学应该知道,Inevntory Pro有一个全局setting类,需要进行一些配置,其中就需要窗体元素与SettingManger脚本进行绑定,而SettingManger是一个单列全局类

最后是如何显示对话框的代码了,看到ShowDialog方法了吗,两个按钮的事件回调函数 Lambda表达式特别显眼

写在最后

分析总结完毕后有一些想法

1、好的框架使开发变得的easy,扩展很方便,通过以上的分析和例子看的出来很容易就能扩展出来一些简单的类似Confirm对话框,而且是对修改封闭,对新增开放的;

2、一个司空见惯的小功能,如果做好了完全可以覆盖到Unity3d的许多知识,剩下的只是不断进行这样的重复,重建你的神经网络即可,总有一天Unity3d的技术就这样印在你的大脑之中;

3、如果你真的看懂了本文,分析一下其实所有的UI系统都是相通的只是API和使用的技术不同而已,只是有些API封装的死,有些封装的松散一些。换句话说如果你自己在某种UI体系中完成一种自己的实现,换到另一个UI体系一样可以实现的;

4、微软体系如Winform过渡的封装是否是好事情?有些时候是好事情,有些时候就未必。根据手上的资源合理的选择技术才是根本;

5、关于使用轮子和造轮子的纠结,这也是一组矛盾,不造轮子就不能深刻的体会技术,造轮子需要大量的时间可造出来未必有已经造好的轮子设计的好,你会选择哪一种呢?

本文首发于蛮牛,次发于博客园,特此说明

蛮牛地址

核心代码

UIWindow

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

namespace Devdog.InventorySystem
{
    /// <summary>
    /// Any window that you want to hide or show through key combination or a helper (UIShowWindow for example)
    /// </summary>
    [RequireComponent(typeof(Animator))]
    [AddComponentMenu("InventorySystem/UI Helpers/UIWindow")]
    public partial class UIWindow : MonoBehaviour
    {

        public delegate void WindowShow();
        public delegate void WindowHide();

        #region Variables 

        /// <summary>
        /// Should the window be hidden when the game starts?
        /// </summary>
        [Header("Behavior")]
        public bool hideOnStart = true;

        /// <summary>
        /// Keys to toggle this window
        /// </summary>
        public KeyCode[] keyCombination;

        /// <summary>
        /// The animation played when showing the window, if null the item will be shown without animation.
        /// </summary>
        [Header("Audio & Visuals")]
        public AnimationClip showAnimation;

        /// <summary>
        /// The animation played when hiding the window, if null the item will be hidden without animation.
        /// </summary>
        public AnimationClip hideAnimation;

        public AudioClip showAudioClip;
        public AudioClip hideAudioClip;

        /// <summary>
        /// The animator in case the user wants to play an animation.
        /// </summary>
        public Animator animator { get; set; }
        protected RectTransform rectTransform { get; set; }

        [NonSerialized]
        private bool _isVisible = false;
        /// <summary>
        /// Is the window visible or not? Used for toggling.
        /// </summary>
        public bool isVisible
        {
            get
            {
                return _isVisible;
            }
            protected set
            {
                _isVisible = value;
            }
        }

        private IEnumerator showCoroutine;
        private IEnumerator hideCoroutine;

        /// <summary>
        /// All the pages of this window
        /// </summary>
        [HideInInspector]
        private List<UIWindowPage> pages = new List<UIWindowPage>();

        public UIWindowPage defaultPage
        {
            get;
            private set;
        }

        #endregion

        #region Events

        /// <summary>
        /// Event is fired when the window is hidden.
        /// </summary>
        public event WindowHide OnHide;

        /// <summary>
        /// Event is fired when the window becomes visible.
        /// </summary>
        public event WindowShow OnShow;

        #endregion

        public void AddPage(UIWindowPage page)
        {
            pages.Add(page);

            if (page.isDefaultPage)
                defaultPage = page;
        }

        public void RemovePage(UIWindowPage page)
        {
            pages.Remove(page);
        }

        public virtual void Awake()
        {
            animator = GetComponent<Animator>();
            if (animator == null)
                animator = gameObject.AddComponent<Animator>();

            rectTransform = GetComponent<RectTransform>();

            if (hideOnStart)
                HideFirst();
            else
            {
                isVisible = true;
            }
        }

        public virtual void Update()
        {
            if (keyCombination.Length == 0)
                return;

            bool allDown = true;
            foreach (var key in keyCombination)
            {
                if (Input.GetKeyDown(key) == false)
                {
                    allDown = false;
                }
            }

            if (allDown)
                Toggle();

        }

        #region Usefull UI reflection functions 

        /// <summary>
        /// One of our children pages has been shown
        /// </summary>
        public void NotifyPageShown(UIWindowPage page)
        {
            foreach (var item in pages)
            {
                if (item.isVisible && item != page)
                    item.Hide();
            }
        }

        protected virtual void SetChildrenActive(bool active)
        {
            foreach (Transform t in transform)
            {
                t.gameObject.SetActive(active);
            }

            var img = gameObject.GetComponent<UnityEngine.UI.Image>();
            if(img != null)
                img.enabled = active;
        }

        public virtual void Toggle()
        {
            if (isVisible)
                Hide();
            else
                Show();
        }

        public virtual void Show()
        {
            if (isVisible)
                return;

            isVisible = true;
            animator.enabled = true;

            SetChildrenActive(true);
            if (showAnimation != null)
            {
                animator.Play(showAnimation.name);
                if (showCoroutine != null)
                {
                    StopCoroutine(showCoroutine);

                }

                showCoroutine = _Show(showAnimation);
                StartCoroutine(showCoroutine);
            }

            // Show pages
            foreach (var page in pages)
            {
                if (page.isDefaultPage)
                    page.Show();
                else if (page.isVisible)
                    page.Hide();
            }

            if (showAudioClip != null)
                InventoryUIUtility.AudioPlayOneShot(showAudioClip);

            if (OnShow != null)
                OnShow();
        }

        public virtual void HideFirst()
        {
            isVisible = false;
            animator.enabled = false;

            SetChildrenActive(false);
            rectTransform.anchoredPosition = Vector2.zero;
        }

        public virtual void Hide()
        {
            if (isVisible == false)
                return;

            isVisible = false;

            if (OnHide != null)
                OnHide();

            if (hideAudioClip != null)
                InventoryUIUtility.AudioPlayOneShot(hideAudioClip);

            if (hideAnimation != null)
            {
                animator.enabled = true;
                animator.Play(hideAnimation.name);

                if (hideCoroutine != null)
                {
                    StopCoroutine(hideCoroutine);
                }

                hideCoroutine = _Hide(hideAnimation);
                StartCoroutine(hideCoroutine);
            }
            else
            {
                animator.enabled = false;
                SetChildrenActive(false);
            }
        }

        /// <summary>
        /// Hides object after animation is completed.
        /// </summary>
        /// <param name="animation"></param>
        /// <returns></returns>
        protected virtual IEnumerator _Hide(AnimationClip animation)
        {
            yield return new WaitForSeconds(animation.length + 0.1f);

            // Maybe it got visible in the time we played the animation?
            if (isVisible == false)
            {
                SetChildrenActive(false);
                animator.enabled = false;
            }
        }

        /// <summary>
        /// Hides object after animation is completed.
        /// </summary>
        /// <param name="animation"></param>
        /// <returns></returns>
        protected virtual IEnumerator _Show(AnimationClip animation)
        {
            yield return new WaitForSeconds(animation.length + 0.1f);

            if (isVisible)
                animator.enabled = false;
        }

        #endregion
    }
}

InventoryUIDialogBase

using UnityEngine;
using System.Collections;
using Devdog.InventorySystem.Dialogs;
using UnityEngine.UI;

namespace Devdog.InventorySystem.Dialogs
{

    public delegate void InventoryUIDialogCallback(InventoryUIDialogBase dialog);

    /// <summary>
    /// The abstract base class used to create all dialogs. If you want to create your own dialog, extend from this class.
    /// </summary>
    [RequireComponent(typeof(Animator))]
    [RequireComponent(typeof(UIWindow))]
    public abstract partial class InventoryUIDialogBase : MonoBehaviour
    {
        [Header("UI")]
        public Text titleText;
        public Text descriptionText;

        public UnityEngine.UI.Button yesButton;
        public UnityEngine.UI.Button noButton;

        /// <summary>
        /// The item that should be selected by default when the dialog opens.
        /// </summary>
        [Header("Behavior")]
        public Selectable selectOnOpenDialog;

        /// <summary>
        /// Disables the items defined in InventorySettingsManager.disabledWhileDialogActive if set to true.
        /// </summary>
        public bool disableElementsWhileActive = true;

        protected CanvasGroup canvasGroup { get; set; }
        protected Animator animator { get; set; }
        public UIWindow window { get; protected set; }

        public virtual void Awake()
        {
            canvasGroup = GetComponent<CanvasGroup>();
            if (canvasGroup == null)
                canvasGroup = gameObject.AddComponent<CanvasGroup>();

            animator = GetComponent<Animator>();
            window = GetComponent<UIWindow>();

            window.OnShow += () =>
            {
                SetEnabledWhileActive(false); // Disable other UI elements

                if (selectOnOpenDialog != null)
                    selectOnOpenDialog.Select();
            };
            window.OnHide += () =>
            {
                SetEnabledWhileActive(true); // Enable other UI elements
            };
        }

        public void Toggle()
        {
            window.Toggle();
            if(window.isVisible)
                SetEnabledWhileActive(false); // Disable other UI elements
            else
                SetEnabledWhileActive(true); // Enable other UI elements
        }

        /// <summary>
        /// Disables elements of the UI when a dialog is active. Useful to block user actions while presented with a dialog.
        /// </summary>
        /// <param name="enabled">Should the items be disabled?</param>
        protected virtual void SetEnabledWhileActive(bool enabled)
        {
            if (disableElementsWhileActive == false)
                return;

            foreach (var item in InventorySettingsManager.instance.disabledWhileDialogActive)
            {
                var group = item.gameObject.GetComponent<CanvasGroup>();
                if (group == null)
                    group = item.gameObject.AddComponent<CanvasGroup>();

                group.blocksRaycasts = enabled;
                group.interactable = enabled;
            }
        }
    }
}

ConfirmationDialog

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

namespace Devdog.InventorySystem.Dialogs
{
    public partial class ConfirmationDialog : InventoryUIDialogBase
    {

        /// <summary>
        /// Show this dialog.
        /// <b>Don‘t forget to call dialog.Hide(); when you want to hide it, this is not done auto. just in case you want to animate it instead of hide it.</b>
        /// </summary>
        /// <param name="title">Title of the dialog.</param>
        /// <param name="description">The description of the dialog.</param>
        /// <param name="yes">The name of the yes button.</param>
        /// <param name="no">The name of the no button.</param>
        /// <param name="yesCallback"></param>
        /// <param name="noCallback"></param>
        public virtual void ShowDialog(string title, string description, string yes, string no, InventoryUIDialogCallback yesCallback, InventoryUIDialogCallback noCallback)
        {
            SetEnabledWhileActive(false);

            window.Show(); // Have to show it first, otherwise we can‘t use the elements, as they‘re disabled.

            titleText.text = title;
            descriptionText.text = description;
            yesButton.GetComponentInChildren<Text>().text = yes;
            noButton.GetComponentInChildren<Text>().text = no;

            yesButton.onClick.RemoveAllListeners();
            yesButton.onClick.AddListener(() =>
            {
                if (window.isVisible == false)
                    return;

                SetEnabledWhileActive(true);
                yesCallback(this);
                window.Hide();
            });

            noButton.onClick.RemoveAllListeners();
            noButton.onClick.AddListener(() =>
            {
                if (window.isVisible == false)
                    return;

                SetEnabledWhileActive(true);
                noCallback(this);
                window.Hide();
            });
        }

        /// <summary>
        /// Show the dialog.
        /// <b>Don‘t forget to call dialog.Hide(); when you want to hide it, this is not done auto. just in case you want to animate it instead of hide it.</b>
        /// </summary>
        /// <param name="title">The title of the dialog. Note that {0} is the item ID and {1} is the item name.</param>
        /// <param name="description">The description of the dialog. Note that {0} is the item ID and {1} is the item name.</param>
        /// <param name="yes">The name of the yes button.</param>
        /// <param name="no">The name of the no button.</param>
        /// <param name="item">
        /// You can add an item, if you‘re confirming something for that item. This allows you to use {0} for the title and {1} for the description inside the title and description variables of the dialog.
        /// An example:
        ///
        /// ShowDialog("Are you sure you want to drop {0}?", "{0} sure seems valuable..", ...etc..);
        /// This will show the item name at location {0} and the description at location {1}.
        /// </param>
        /// <param name="yesCallback"></param>
        /// <param name="noCallback"></param>
        public virtual void ShowDialog(string title, string description, string yes, string no, InventoryItemBase item, InventoryUIDialogCallback yesCallback, InventoryUIDialogCallback noCallback)
        {
            ShowDialog(string.Format(string.Format(title, item.name, item.description)), string.Format(description, item.name, item.description), yes, no, yesCallback, noCallback);
        }
    }
}
时间: 2024-10-07 19:36:51

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

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

背景 游戏中的UI系统或者叫做GUI窗口系统主要有:主要装备窗口(背包,角色窗口也是一种特殊窗口).确实提示窗口(如购买确认).信息提示窗口(一遍没有按钮,ContexntMenu)和特殊窗口(聊天记录或者技能树),前篇已经介绍分析了Inventory Pro确认提示窗口的设计和实现方式,这篇主要讲一下信息提示窗口的实现.本以为提示窗口是比较简单的,毕竟没有按钮事件交互的问题,但是分析了下源代码还是让我有些惊讶,插件作者在提示窗口中考虑到了性能问题,由于本人一直在PC端开发程序没有移动端的经验,

asp在后台弹出confirm确认对话框并获取用户选择的值做出相应的操作

在asp项目中,这种情况是经常出现的,前段时间通过查找资料以及自己尝试,找到一种解决方案,但是不知是否有更好的方案,以后发现再进行记录. 一.思路 在本次项目中,在一个函数中需要让用户判断,并根据用户的选择进行相关的操作,最开始是希望在用户需要确认的地方弹出确认框,然后程序暂停,用户选择之后再继续运行,于是在需要确认的地方插入js代码,但是通过调试发现,在函数中间插入弹出对话框的js代码,整个函数执行完毕之后才会弹出确认对话框,当然也就不能暂停根据用户的选择执行后面的代码,之后也找不到解决方法.

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.可以在窗口使用物品的功能,物

jQuery UI实现的自定义confirm确认框简单介绍

jQuery UI实现的自定义confirm确认框简单介绍:本章节介绍一下jQuery UI自定义了一个confirm的确认对话框效果.通过html代码自定义对话框的显示界面和外观,可以自定义confirm框的按钮.本例中定义了一个confirm确认按钮和一个cancel取消按钮.html代码: <button id="callConfirm">Confirm!</button> <div id="dialog" title="

ASP.NET点击按钮弹出确认对话框方法

开发asp.net网页应用程序的时候,有些页面的按钮需要增加一个确认对话框,比如: 实现这个功能比较简单,代码这样写: Button.Attributes["onclick"] = "javascript:return confirm('您确定删除吗?一旦删除将无法恢复!');"; 或 Button.Attributes.Add("OnClick", "javascript:return confirm('您确定删除吗?一旦删除将无法恢

jquery mobile 实现自定义confirm确认框效果

类似删除的效果,在执行之前,一般需要添加确认对话框,点确认的话执行,取消按钮就不执行,传统的js if(confirm('确定删除吗?')) { //执行代码 } 这种效果比较丑,使用jquery mobile优化一下 需要引用的文件: <script src="~/Scripts/jquery-1.10.2.min.js"></script><link href="~/Scripts/Mobile/jquery.mobile-1.4.0.min

JavaScript点击按钮显示 确认对话框

//JavaScript点击按钮显示确认对话框 <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>My First Script</title> <script type=&q