zoukankan      html  css  js  c++  java
  • Unity-动态显示窗口制作思路

    此教程来自siki学院的<<暗黑战神>>课程

    这次需要记录的是动态显示窗口的制作方式,它的效果是弹出一条游戏Tips,上面可以显示你想显示的内容,随后消失。

    显然,我们只需要制作一个动画,动画中改变Text组件的位置即可实现此效果。

    然而现在的问题是,不能让这个动画立刻播放,我们需要在特定的时候去播放它,并在特定的时刻停止。

    那么怎么实现呢?

    我们可以在控制动态显示窗口的脚本中,设置一个方法,当要显示动态Tips时,设置改变Text内容,并激活对应的游戏物体,手动控制动画播放。注意,游戏物体不激活时即使动画是自动播放的,也不会播放,只有激活了,才会从头开始播放

    动画播放后需要停止,因此需要把游戏物体取消激活,但我们在代码中怎么准确地控制它能延时取消激活呢?

    这个问题和之前说的异步加载资源服务有点类似,传送门如下:

    https://www.cnblogs.com/czw52460183/p/11044456.html

    当时的思路是在Update中用委托实时查看加载进度,但这里就不行,因为我们没有什么方法可以实时获取到动画播放进度,怎么办呢?

    答案是可以使用协程,我们只需要在开启协程后,让它在固定等待一段时间后调用回调方法即可,在这个回调方法中去关闭物体的激活状态即可,而这个等待的时间,就设置成动画的持续时长即可,这样,即使我们没有办法掌握动画的实际播放进度,也可以实现延时关闭。

    参考代码如下:(由于只是讲述思路,这里代码中的细节就不详细叙述了,因为用到的一些方法都是之前制作的,这里没给出具体信息,只看一下思路即可)

    /****************************************************
        文件:DynamicWnd.cs
        作者:czw52460183
        邮箱: czw52460183@163.com
        日期:2019/6/22 10:8:16
        功能:动态UI元素界面
    *****************************************************/
    
    using System;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class DynamicWnd : WindowRoot 
    {
        public Text txtTips;
        public Animation tipsAni;
    
        //本窗口初始化时需要:
        //1.使txtTips所属的游戏物体不激活
        //注意,游戏物体不激活时即使动画是自动播放的,也不会播放,只有激活了,才会从头开始播放
        protected override void InitWnd()
        {
            base.InitWnd();
            SetActive(txtTips,false);
        }
    
        /// <summary>
        /// 设置界面的展示内容
        /// </summary>
        public void SetTips(string tips)
        {
            //激活txtTips所属的游戏物体
            SetActive(txtTips, true);
            //设置展示内容
            SetText(txtTips,tips);
            //播放动画
            tipsAni.Play();
            //利用协程实现延时关闭激活状态
            AnimationClip clip = tipsAni.clip;
            StartCoroutine(AniPlayDone(clip.length,()=>{
                //延时到动画结束后关闭激活状态
                SetActive(txtTips,false);
            }));
        }
    
        //协程,延时后调用回调方法
        private IEnumerator AniPlayDone(float sec, Action cb)
        {
            yield return new WaitForSeconds(sec);
            if(cb != null)
            {
                cb();
            }
        }
    }

      Unity中是单线程的,我的理解是,开启协程后,系统会在每帧检查yield return的条件是否满足,满足则执行yield return后面的方法,不满足则继续执行开启协程语句后面的指令,这就使得虽然Unity是单线程的,但协程会给人一种多线程的感觉。

      但使用协程也会带来新的问题,就是由于它模拟出了多线程的效果,自然也会出现多线程有的问题,假如我们的系统在两个不同的地方同时调用了SetTips方法,会导致第一个SetTips在开启协程后继续执行下面的指令,此时第一个动画在播放,但是还没到进入yield return的时间,这时系统在执行后面的指令,即开始执行第二个SetTips指令,此时又会设置展示内容,因此就把第一个动画设置的展示内容覆盖掉了,导致的结果就是只展示了第二个SetTips的指令。

      怎么办呢?

      可以使用一个队列,当每一条SetTips请求进来时,把它加入到队列中,而在每一帧中对队列中的元素数量进行检查,若队列中有元素,就取出对应内容并进行展示内容的设置。注意,在向队列中添加或取出元素时,由于可能出现竞争,因此要加锁。  

      代码如下:

    /****************************************************
        文件:DynamicWnd.cs
        作者:czw52460183
        邮箱: czw52460183@163.com
        日期:2019/6/22 10:8:16
        功能:动态UI元素界面
    *****************************************************/
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class DynamicWnd : WindowRoot 
    {
        public Text txtTips;
        public Animation tipsAni;
    
        //本窗口初始化时需要:
        //1.使txtTips所属的游戏物体不激活
        //注意,游戏物体不激活时即使动画是自动播放的,也不会播放,只有激活了,才会从头开始播放
        protected override void InitWnd()
        {
            base.InitWnd();
            SetActive(txtTips,false);
        }
    
        //用队列来存放要展示的内容
        private Queue<string> tipsQueue = new Queue<string>();
    
        /// <summary>
        /// 把展示的内容添加到队列中
        /// </summary>
        public void AddTips(string tips)
        {
            //添加前要为队列加锁,防止冲突
            lock(tipsQueue)
            {
                tipsQueue.Enqueue(tips);
            }
        }
    
        //每一帧检测队列中是否有元素,有就取出并展示
        private void Update()
        {
            if(tipsQueue.Count > 0)
            {
                //取出前也要为队列加锁,防止冲突
                lock (tipsQueue)
                {
                    string tips = tipsQueue.Dequeue();
                    SetTips(tips);
                }
            }
        }
    
        /// <summary>
        /// 设置界面的展示内容
        /// </summary>
        private void SetTips(string tips)
        {
            //激活txtTips所属的游戏物体
            SetActive(txtTips, true);
            //设置展示内容
            SetText(txtTips,tips);
            //播放动画
            tipsAni.Play();
            //利用协程实现延时关闭激活状态
            AnimationClip clip = tipsAni.clip;
            StartCoroutine(AniPlayDone(clip.length,()=>{
                //延时到动画结束后关闭激活状态
                SetActive(txtTips,false);
            }));
        }
    
        //协程,延时后调用回调方法
        private IEnumerator AniPlayDone(float sec, Action cb)
        {
            yield return new WaitForSeconds(sec);
            if(cb != null)
            {
                cb();
            }
        }
    }

    红色的是要修改的地方,这里相当于是把协程带来的冲突问题转移到了Update中。

    但这样的修改是否能解决问题?显然不行,虽然冲突问题转移到了Update中,但Update是每一帧执行一次的,速度很快,所以面临的状况和之前其实一样,第一个设置内容请求(这里是AddTips)使协程开启后,由于Update执行间隔很短,第二个请求依然会在动画播放完成前进入SetTips。

    我们可以在这基础上再加一个标志变量,标记当前是否有Tips在展示,只有没有展示时才从队列中取元素并展示,当动画播放完,即协程返回时,才将标记变量重新恢复为无展示状态。

    代码如下:

    /****************************************************
        文件:DynamicWnd.cs
        作者:czw52460183
        邮箱: czw52460183@163.com
        日期:2019/6/22 10:8:16
        功能:动态UI元素界面
    *****************************************************/
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class DynamicWnd : WindowRoot 
    {
        public Text txtTips;
        public Animation tipsAni;
    
        //本窗口初始化时需要:
        //1.使txtTips所属的游戏物体不激活
        //注意,游戏物体不激活时即使动画是自动播放的,也不会播放,只有激活了,才会从头开始播放
        protected override void InitWnd()
        {
            base.InitWnd();
            SetActive(txtTips,false);
        }
    
        //标志变量,代表当前是否有Tips在展示
        private bool isTipsShow = false; 
        //用队列来存放要展示的内容
        private Queue<string> tipsQueue = new Queue<string>();
    
        /// <summary>
        /// 把展示的内容添加到队列中
        /// </summary>
        public void AddTips(string tips)
        {
            //添加前要为队列加锁,防止冲突
            lock(tipsQueue)
            {
                tipsQueue.Enqueue(tips);
            }
        }
    
        //每一帧检测
        private void Update()
        {
            //只有当队列中有元素,且当前没有Tips在展示时才从队列中取元素展示
            if (tipsQueue.Count > 0 && isTipsShow == false)
            {
                //取出前也要为队列加锁,防止冲突
                lock (tipsQueue)
                {
                    string tips = tipsQueue.Dequeue();
                    //标记当前为正在展示状态
                    isTipsShow = true;
                    SetTips(tips);
                }
            }
        }
    
        /// <summary>
        /// 设置界面的展示内容
        /// </summary>
        private void SetTips(string tips)
        {
            //激活txtTips所属的游戏物体
            SetActive(txtTips, true);
            //设置展示内容
            SetText(txtTips,tips);
            //播放动画
            tipsAni.Play();
            //利用协程实现延时关闭激活状态
            AnimationClip clip = tipsAni.clip;
            StartCoroutine(AniPlayDone(clip.length,()=>{
                //延时到动画结束后关闭激活状态
                SetActive(txtTips,false);
                //标记当前为无展示状态
                isTipsShow = false;
            }));
        }
    
        //协程,延时后调用回调方法
        private IEnumerator AniPlayDone(float sec, Action cb)
        {
            yield return new WaitForSeconds(sec);
            if(cb != null)
            {
                cb();
            }
        }
    }

    红色为添加内容,具体不再介绍。

    完毕。

  • 相关阅读:
    C++ 容器元素的存储和获取
    【C++沉思录】代理类
    mysql 编码测试
    理解字符编码
    linux mount
    mysql delimiter
    mysql 求时间段平均值
    mysql Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
    PostgreSQL体系架构与内存结构
    PostgreSQL中的The Oversized-Attribute Storage Technique(TOAST:超大属性存储技术)
  • 原文地址:https://www.cnblogs.com/czw52460183/p/11071261.html
Copyright © 2011-2022 走看看