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

紅色為添加內容,具體不再介紹。

 

完畢。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM