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();
            }
        }
    }

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

    完毕。

  • 相关阅读:
    HDU 1058 Humble Numbers
    HDU 1421 搬寝室
    HDU 1176 免费馅饼
    七种排序算法的实现和总结
    算法纲要
    UVa401 回文词
    UVa 10361 Automatic Poetry
    UVa 537 Artificial Intelligence?
    UVa 409 Excuses, Excuses!
    UVa 10878 Decode the tape
  • 原文地址:https://www.cnblogs.com/czw52460183/p/11071261.html
Copyright © 2011-2022 走看看