前言
無論是遊戲開發或互動裝置設計,使用者介面 (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,最後用協程依序執行:
- 開啟 Image 的 raycastTarget,防止使用者點擊到後面的東西。
- 淡入黑色的 Image。
- 載入 Index 為 2 的場景。
- 淡出黑色的 Image。
- 關閉 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}
執行效果: