[Unity 教學] UI 動畫與轉場效果

2024.08.22 / Unity 引擎
DOTween 是一個開源、免費的 Unity 動畫控制套件,開發者可輕易的透過 DO 系列的擴充方法於程式中執行物件的各種動態運動。DOTween 是非同步的,因此我們可透過事件監聽或協程 (Coroutine) 來準確的安排動畫的順序與播放時機。

前言

無論是遊戲開發或互動裝置設計,使用者介面 (User Interface, UI) 始終是一個很重要的關鍵,好的 UI 設計能讓使用者更輕易的與作品互動。除了遊戲中能在物件中加上動畫,UI 其實也可以。適當的使用動畫效果可以加強整體系統的順暢度與可用性,例如在換場景時使用全螢幕的淡入淡出效果以掩蓋載入時的卡頓感、等待某系統的回應時於畫面中加上「載入中」的動畫圖示以告知使用者系統仍在運作,並不是當機。以上種種都是 UI 動畫可達到的效果。

如果要在 Unity 中製作動畫,我們可以用最陽春的 Animation 來實現。但有時候我們會希望動畫能使用程式來進行更細微的控制,這時就應該使用開源免費的 DOTween 來實現高效能的動畫控制。

安裝 DOTween

首先前往 DOTween Download 網頁,點擊 Download DOTween 按鈕,取得最新版本的 DOTween 套件 (本文以 1.2.765 版為範例)。

使用 7-Zip 開啟 DOTween_1_2_765.zip 壓縮檔,並開啟 Unity 專案,直接拖曳 7-Zip 中的 DOTWeen 資料夾到 Unity 的 Asset 資料夾中。

等待套件完成載入後,將會跳出一個視窗告知你 DOTween 已載入,點擊 Open DOTween Utity Panel 開啟套件介面。

點擊 Setup DOTween… 按鈕來初始化套件。

最後可勾選你需要啟用的 DOTween 功能,包括音效、物理、Sprite 或 UI 等,這邊我們維持全選的狀態,直接點擊 Apply 完成設定。

若沒有出現其他問題,可關閉 DOTween 的相關視窗。

基礎操作

UI 移動、旋轉與縮放

新增一個置中的 Image 到 Canvas 中,再建立 UIController 腳本並新增到 Image。如果你想,當然也可以新增不同的 UI 介面,不限於 Image。

移動

在撰寫移動 UI 的程式碼時要盡量避免直接改變 transform.position,因為 transform.position 預設都是以左下角為原點,如果 UI 的原點是中央或其他位置的話,還要多寫一個座標轉換計算。為了省麻煩,移動位置一律在 RectTransform 中進行。

引用 DG.Tweening 命名空間後,直接在 RectTransform 後呼叫 DOAnchorPos 即可播放動畫。DOAnchorPos 的第一參數為想要移動到的座標;第二參數為動畫的時間。假如我們要在兩秒內移動到座標 (200, 200),可撰寫:

 1using UnityEngine;
 2using DG.Tweening;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private RectTransform _rectTransform;
 7
 8    private void Start()
 9    {
10        _rectTransform = GetComponent<RectTransform>();
11        _rectTransform.DOAnchorPos(new Vector2(200, 200), 2);
12    }
13}

執行效果:

DOAnchorPos 還有 DOAnchorPosX、DOAnchorPosY、DOAnchorPosZ 的變體,能單獨改變 XYZ 的值,可更方便的使用。

旋轉

UI 的旋轉可直接於 transform 中呼叫 DORotate,第一參數為想要旋轉到的度數;第二參數為動畫的時間。假如我們要在兩秒內逆時鐘旋轉 180 度,可撰寫:

 1using UnityEngine;
 2using DG.Tweening;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private void Start()
 7    {
 8        transform.DORotate(new Vector3(0, 0, 180), 2);
 9    }
10}

執行效果:

如果你有旋轉大於 180 度的需求,這時會發現它會變成順時鐘旋轉;如果數值大於 360 時,它會自動減去 360 度。這時我們需要加入 DORotate 的第三參數 RotateMode 來修正。RotateMode 的預設值是 Fast,假設我們想要讓它在兩秒內旋轉 390 度,就需要將 RotateMode 改為 FastBeyond360。

 1using UnityEngine;
 2using DG.Tweening;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private void Start()
 7    {
 8        transform.DORotate(new Vector3(0, 0, 390), 2, RotateMode.FastBeyond360);
 9    }
10}

執行效果:

縮放

縮放可直接於 transform 下呼叫 DOScale,DOScale 有兩種用法:

1. XYZ 同時縮放相同值

如果要同時縮放 XYZ 且數值相同的話,第一參數可直接輸入數字,第二參數同樣為動畫時間。

 1using UnityEngine;
 2using DG.Tweening;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private void Start()
 7    {
 8        transform.DOScale(2, 2);
 9    }
10}

2. 個別設定縮放值

第一參數更換為 Vector3,並精準的設定 XYZ 的目標縮放值。

 1using UnityEngine;
 2using DG.Tweening;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private void Start()
 7    {
 8        transform.DOScale(new Vector3(2, 2, 1), 2);
 9    }
10}

執行效果:

DOScale 還有 DOScaleX、DOScaleY、DOScaleZ 的變體,能單獨改變 XYZ 的值,可更方便的使用。

Image 透明度、顏色與填滿

DOTween 支援對 Image 的直接操作,本文我們介紹透明度、顏色與填滿三種動畫效果:

透明度

取得 Image 後,直接於 Image 呼叫 DOFade。第一參數為目標的透明度,數值範圍是浮點數 0 ~ 1;第二參數為動畫時間,可撰寫:

 1using DG.Tweening;
 2using UnityEngine;
 3using UnityEngine.UI;
 4
 5public class UIController : MonoBehaviour
 6{
 7    private Image _image;
 8
 9    private void Start()
10    {
11        _image = GetComponent<Image>();
12        _image.DOFade(0, 2);
13    }
14}

執行效果:

顏色

顏色的變化可於 Image 下呼叫 DOColor。第一參數為想要變成的顏色,第二參數為動畫時間。DOColor 的第一參數預設只接受 Color,但 Color 只能輸入範圍 0 ~ 1 的浮點數,比較難一眼看出 RGB 色碼,也可能不精準。因此我們可以先用 Color32 輸入範圍 0 ~ 255 的整數 RGBA,再轉型回 Color 給 DOColor 使用。

假如我們要將 Image 於兩秒內變成橘色,可撰寫 (第 13 行的註解即是 Color32 的用法):

 1using DG.Tweening;
 2using UnityEngine;
 3using UnityEngine.UI;
 4
 5public class UIController : MonoBehaviour
 6{
 7    private Image _image;
 8
 9    private void Start()
10    {
11        _image = GetComponent<Image>();
12        _image.DOColor(new Color(1, .6f, 0), 2);
13        // _image.DOColor((Color)new Color32(255, 165, 0, 255), 2);
14    }
15}

執行效果:

填滿

Image 有一個隱藏屬性為 Image Type,其中 Image Type 的 Filled 可讓 Image 根據數值而實現填滿的效果。為了讓 Image 能正確顯示影像,我們須要先建立一個 Sprite Mode 為 Multiple 的圖片。首先在 Asset 下建立 Textures 資料夾,並在資料夾中滑鼠右鍵 > Create > 2D > Sprites > Square 建立一個方型圖片。

確保 Square 的 Sprite Mode 為 Multiple。

接下來將 Square 拖曳到 Image 的 Source Image 中,Image Type 將會顯示出來,這時就能將它改成 Filled 模式。

最後我們可以於 Image 下呼叫 DOFillAmount。第一參數為要變成的填滿數值,對應 Image 物件下的 Fill Amount 屬性;第二參數為動畫時間。Image 中還有 Fill Method、Fill Origin 等屬性能調整,可自由進行搭配來使用。

假如我們要讓它兩秒內填滿歸零,可撰寫:

 1using DG.Tweening;
 2using UnityEngine;
 3using UnityEngine.UI;
 4
 5public class UIController : MonoBehaviour
 6{
 7    private Image _image;
 8
 9    private void Start()
10    {
11        _image = GetComponent<Image>();
12        _image.DOFillAmount(0, 2);
13    }
14}

執行效果:

CanvasGroup 群組透明度

如果有多個 UI 同時要改變相同的透明度,我們可將這些 UI 放到同一個物件下,並在父物件中新增 CanvasGroup 組件。為了方便存取,我們將 UIController 移動到父物件。

取得 CanvasGroup 後,即可直接呼叫 DOFade 改變透明度。第一參數為目標的透明度;第二參數為動畫時間。假如我們想要 CanvasGroup 在兩秒內變成透明的,可撰寫:

 1using DG.Tweening;
 2using UnityEngine;
 3
 4public class UIController : MonoBehaviour
 5{
 6    private CanvasGroup _canvasGroup;
 7
 8    private void Start()
 9    {
10        _canvasGroup = GetComponent<CanvasGroup>();
11        _canvasGroup.DOFade(0, 2);
12    }
13}

執行效果:

實戰應用

介面滑入滑出

首先以 _originPosY 紀錄介面的原始 Y 座標,並通過 IPointerEnterHandler 與 IPointerExitHandler 接收滑鼠進入與離開的事件。並在實作方法中先呼叫 DOKill 確保動畫停止,再以 DOAnchorPosY 來改變介面的 Y 座標。

 1using DG.Tweening;
 2using UnityEngine;
 3using UnityEngine.EventSystems;
 4
 5public class UIController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
 6{
 7    private RectTransform _rectTransform;
 8    private float _originPosY;
 9
10    private void Start()
11    {
12        _rectTransform = GetComponent<RectTransform>();
13        _originPosY = _rectTransform.anchoredPosition.y;
14    }
15
16    public void OnPointerEnter(PointerEventData _)
17    {
18        _rectTransform.DOKill();
19        _rectTransform.DOAnchorPosY(45, .5f);
20    }
21
22    public void OnPointerExit(PointerEventData _)
23    {
24        _rectTransform.DOKill();
25        _rectTransform.DOAnchorPosY(_originPosY, .5f);
26    }
27}

執行效果:

載入進度圖示

首先比較先前建立 Square 的方式建立一個 Circle,並拖曳到 Image 的 Source Image 中。再通過協程 (Coroutine) 來週期性的改變 Image 的 fillClockwise 與播放 DOFillAmount 動畫。

 1using DG.Tweening;
 2using System.Collections;
 3using UnityEngine;
 4using UnityEngine.UI;
 5
 6public class UIController : MonoBehaviour
 7{
 8    private Image _image;
 9
10    private void Start()
11    {
12        _image = GetComponent<Image>();
13        StartCoroutine(Play());
14    }
15
16    private IEnumerator Play()
17    {
18        while (true)
19        {
20            _image.fillClockwise = true;
21            yield return _image.DOFillAmount(1, 1).WaitForCompletion();
22            _image.fillClockwise = false;
23            yield return _image.DOFillAmount(0, 1).WaitForCompletion();
24        }
25    }
26}

執行效果:

場景轉換淡入淡出

由於場景轉換會刪除原場景的物件,因此我們需要將負責淡入淡出的 UI 加入 DontDestroyOnLoad 之中。為方便操作,我們要將全黑的 Image 與 EventSystem 物件放到 Canvas 之下,並將 UIController 移動到 Canvas。

接下來建立一個新場景,並將它新增到 Build Settings 的 Scenes In Build 當中。

最後撰寫程式碼,首先將有 UIController 的 Canvas 加入 DontDestroyOnLoad,再取得 Canvas 下的 Image,最後用協程依序執行:

  1. 開啟 Image 的 raycastTarget,防止使用者點擊到後面的東西。
  2. 淡入黑色的 Image。
  3. 載入 Index 為 2 的場景。
  4. 淡出黑色的 Image。
  5. 關閉 Image 的 raycastTarget。
 1using DG.Tweening;
 2using System.Collections;
 3using UnityEngine;
 4using UnityEngine.SceneManagement;
 5using UnityEngine.UI;
 6
 7public class UIController : MonoBehaviour
 8{
 9    private Image _image;
10
11    private void Start()
12    {
13        DontDestroyOnLoad(gameObject);
14        _image = GetComponentInChildren<Image>();
15        StartCoroutine(Load());
16    }
17
18    private IEnumerator Load()
19    {
20        _image.raycastTarget = true;
21        yield return _image.DOFade(1, 1).WaitForCompletion();
22        yield return SceneManager.LoadSceneAsync(1);
23        yield return _image.DOFade(0, 1).WaitForCompletion();
24        _image.raycastTarget = false;
25    }
26}

執行效果:

參考資料

相關文章

Ted Liou

雲科碩士在讀中,專注於 Unity C#、TouchDesigner 技術,常把技術筆記分享到部落格,偶爾還直接挪用文章來當教材的研究生。