本範例檔以【滑鼠點擊控制物件移動】提供兩種不同的寫法作為範例比較,供大家參考 Coroutine (協同程序)的應用。本文的移動方式是使用SmoothDamp(平滑移動)
線上玩
範例檔下載 (使用update)
範例檔下載 (使用Coroutine)
延伸閱讀: Unity3D製作計數器「StartCoroutine應用」、協程Coroutine簡介
=======================================
使用 Update() 的作法... (專案下載)
main.cs
- 抓取 unity 場景上有個名叫 sphere (1) 的物件上的 sphere.cs (main.cs:第13行)
- 當我們 按下滑鼠鍵 的時候 (main.cs:第20行),會取得 滑鼠座標 。 (main.cs:第22行)
- 並執行 sphere (1)物件的 sphere,cs 這個script的方法 movto(pos) (main.cs:第23行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class main : MonoBehaviour { sphere selected; GameObject click; // Use this for initialization void Start() { selected = GameObject.Find("sphere (1)").GetComponent<sphere>(); click = GameObject.Find("click"); } // Update is called once per frame void Update() { if (Input.GetMouseButtonDown(0)) { Vector3 pos = Input.mousePosition; selected.moveto(Camera.main.ScreenToWorldPoint(pos)); click.transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition); } } } |
sphere.cs
- 物件內的 update() 方法裡面,放入moving()方法。(sphere.cs:第20行)
- 在 moving() 中判斷全域變數 isMove 是否為 true。 (sphere.cs:第32行)
- 如果 isMove 是 true 的狀態下,才繼續執行後續程式碼。
如果 isMove 是 false 的狀態下,直接跳出。 - 所以說在遊戲進行中會不斷檢查 isMove 是否為 ture/false。
- 物件的全域方法 movto(vector3) 被執行時會將 isMove 改為 ture。 (sphere.cs:第27行)
- 並且設定該物件欲移動的目標 goalPos (sphere.cs:第26行)
- 這時因為 isMove 變成 true 了,所以 sphere.cs:32行 以後的程式碼將會被執行。
- sphere.cs:35行 計算了目前與目標距離的差距
- sphere.cs:38行 檢查差距是否高於0.025f,如果高於的話則執行「移動物件的代碼」
- sphere.cs:41行 就是「移動物件的代碼」,當他被執行就會讓物件朝目標前進。
- sphere.cs:42行 這裡加入了 return ,還沒到達目標時不會執行 sphere.cs:46~47行
- sphere.cs:46行 當物件離目的地距離非常近的時候,就直接把他送到目標位置。
- sphere.cs:47行 都已經到目標位置了,把isMove 改為 false,關閉移動。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class sphere : MonoBehaviour { public float smoothTime = 0.25F; public Vector3 velocity = Vector3.zero; public bool isMove; Vector3 goalPos; void Start() { isMove = false; } void Update() { moving(); } public void moveto(Vector3 pos) { GetComponent<Renderer>().material.color = Random.ColorHSV(0f, 1f, 1f, 1f, 1f, 1f, 0.5f, 0.5f); goalPos = pos; isMove = true; } void moving() { if (isMove == false) return; //計算與目標之間的距離差並儲存到dist float dist = Vector3.Distance(transform.position, goalPos); // 當「物件位置」與「目的位置」距離差距超過0.1f距離單位以上時,if內的程式買將重複執行 if (dist > 0.025f) { //Vector3.SmoothDamp (起始位置、目標位置、當前速度、抵達時間) transform.position = Vector3.SmoothDamp(transform.position, goalPos, ref velocity, smoothTime); return; } // 當「物件位置」與「目的位置」距離差距低於0.1f距離單位時... transform.position = goalPos; isMove = false; } } |
心得:
原則上 Unity 官方API文件都是用Update作範例的 (例如這篇Vector3.SmoothDamp),用起來簡單直覺。但這樣的寫法有個缺點...就是必須額外做個「開關變數」,來控制「移動程序」執行與否。因此 Unity 在執行時將「不斷檢查開關變數」 狀態,似乎是個沒效率的一種作法。============================
使用 Coroutine 的作法... (專案下載)
main.cs
- unity 場景上有個名叫 sphere (1) 的物件並該物件掛載 sphere.cs (main.cs:第13行)
- 當我們 按下滑鼠鍵 的時候,會取得 滑鼠座標 。 (main.cs:第13行)
- 並執行 sphere (1)物件的 sphere,cs 這個script的方法 movto(pos) (main.cs:第13行)
sphere.cs
- 在全域變數中宣告一個迭代器(IEnumerator) 名叫 moveCoroutine (sphere.cs:第9行)
- 並在物件的start()階段,指定該迭代器對應的方法 moving() (sphere.cs:第9行)
- 物件的全域方法 moveto(vector3) 被 main.cs 呼叫執行時... (sphere.cs:第17~28行)
- 停止正在執行的 moveCoroutine (sphere.cs:第20行)
- 指定 moveCoroutine 等於moving方法,並給予滑鼠座標 (sphere.cs:第21行)
- 開始執行 moveCoroutine,也就是執行moving() (sphere.cs:第22行)
sphere.cs:moving(vector3)
- 當moving 被執行時(透過startCoroutine()) (sphere.cs:第22行)
- 首先會檢查物件與目標位置之間的距離差距 (sphere.cs:第33行)
- 若差距低於0.025f個單位,進入while迴圈 (sphere.cs:第37行)
- 在while迴圈中在更新一次與目標的距離差距 (sphere.cs:第40行)
- 當 sphere.cs:第42行 被執行就會讓物件朝目標前進。
- 當程式執行到 sphere.cs:第43行 時,會拋出這個【while迴圈】及【moving】。
- 直到要再執行update()時,會再次進入這個while迴圈 sphere.cs:36行
- 因為是回到這個迴圈之中,所以 sphere.cs:第32、33行不會再次執行。
- 所以是透過 yield return null;這段程式碼,讓while可以跳出後再update階段再進入。
- 當物件與目標位置距離差距低於0.025f時,會跳出while迴圈(sphere.cs:第37行)
- 並執行 sphere.cs:第47行 直接把物件送到目標位置。
- moving這個方法執行完畢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class sphere : MonoBehaviour { public float smoothTime = 0.25F; public Vector3 velocity = Vector3.zero; IEnumerator moveCoroutine; void Start() { //指定 moveCoroutine 使用的迭代器與參數 moveCoroutine = moving(Vector3.zero); } public void moveto(Vector3 pos) { //停止正在進行的 moveCoroutine ,如果沒有預先指定(即14行)會無法通過編譯 StopCoroutine(moveCoroutine); //指定 moveCoroutine 使用的迭代器與參數 --- IEnumberator moving(vector3,GameObject) moveCoroutine = moving(pos); //執行 moveCoroutine StartCoroutine(moveCoroutine); } IEnumerator moving(Vector3 pos) { //這段程式碼只執行一次 float dist = Mathf.Infinity; GetComponent<Renderer>().material.color = Random.ColorHSV(0f, 1f, 1f, 1f, 1f, 1f, 0.5f, 0.5f); // 當「物件位置」與「目的位置」距離差距超過0.1f距離單位以上時,while內的程式買將重複執行 while (dist > 0.025f) { //計算與目標之間的距離差並儲存到dist dist = Vector3.Distance(transform.position, pos); //Vector3.SmoothDamp (起始位置、目標位置、當前速度、抵達時間) transform.position = Vector3.SmoothDamp(transform.position, pos, ref velocity, smoothTime); yield return null; } // 當「物件位置」與「目的位置」距離差距低於0.1f距離單位以上時,下面程式碼會執行後跳出 moving 迭代器 transform.position = pos; } } |
說明&心得:
改用 Coroutine (協程) 的版本中,我們是透過 StartCoroutine(moveCoroutine); 讓moving()立即被執行,並且在執行 yield return null; (sphere.cs:第42行) 時拋出迴圈。在被拋出後會在update階段再次進入迴圈。如果在執行moving()時沒有被拋出的話,moving就結束執行了。我個人認為在這個功能範例中,改用 Coroutine (協程) 的版本使用起來更為自由、更少一些步驟。(開啟關閉容易且不需要開關變數來控制...)
============================
最後提供簡化版寫法
- 不須額外宣告 moveCoroutine ,直接透過參數使用。
- 缺點是這種寫法只能傳送一個參數給moving()。
- 並且這種寫法在VSCODE中,無法顯示moving被誰引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class sphere : MonoBehaviour { public float smoothTime = 0.25F; public Vector3 velocity = Vector3.zero; public void moveto(Vector3 pos) { StopCoroutine("moving"); StartCoroutine("moving", pos); } IEnumerator moving(Vector3 pos) { //這段程式碼只執行一次 float dist = Mathf.Infinity; GetComponent<Renderer>().material.color = Random.ColorHSV(0f, 1f, 1f, 1f, 1f, 1f, 0.5f, 0.5f); // 當「物件位置」與「目的位置」距離差距超過0.1f距離單位以上時,while內的程式買將重複執行 while (dist > 0.025f) { //計算與目標之間的距離差並儲存到dist dist = Vector3.Distance(transform.position, pos); //Vector3.SmoothDamp (起始位置、目標位置、當前速度、抵達時間) transform.position = Vector3.SmoothDamp(transform.position, pos, ref velocity, smoothTime); yield return null; } // 當「物件位置」與「目的位置」距離差距低於0.1f距離單位以上時,下面程式碼會執行後跳出 moving 迭代器 transform.position = pos; } } |
最後在補充...
我自己測試了同時兩千五百個物件在執行下,用update還是Coroutine哪個省效能。
結果圖先奉上,晚點在細說明及提供我測試的專案。
看起來跟這篇說的一樣... Is Using Coroutines Actually Faster Than Update?
何時該用 Coroutine 何時該用Update,大家覺得呢 ^^
Update()
Coroutine()
兩個下載檔案連結都放成 update 了
回覆刪除"專案下載" 的地方
刪除喔喔 感謝提醒 我修正了唷!
刪除