[Unity] 方法帶入 Action 參數,讓程式更易讀且更靈活

發表日期:
2021.04.09
/
分類:
平常我們的程式都是由上到下按照順序執行,只要沒放錯位置,後面的功能都有辦法讀到前面方法輸出的結果。 但是如果碰到非同步 Async、協程 Coroutine 之類的

平常我們的程式都是由上到下按照順序執行,只要沒放錯位置,後面的功能都有辦法讀到前面方法輸出的結果。

但是如果碰到非同步 Async、協程 Coroutine 之類的會大幅改變程式的執行順序時,原本作法就不管用。在這篇文章中要教你如何用「Action」來解決這個問題,而不是用一個迴圈來頻繁檢查前面的作業是否已完成,是個能讓程式更靈活、更易讀的寫法。

Action 是 System 內的功能,它有分成無帶參數有帶參數兩種用法。它在調用常會使用 Lambda 運算式,初次使用的開發者們可能會被這個外星文嚇跑,但其實這個東西並沒有你想得那麼複雜,其實它的邏輯很簡單!

基礎語法一

Action 在其他同樣是 C# 語言的框架中都能用,我們這邊就以 Unity 來說明。

以往我們對參數的認知都是「死」的,比方說我輸入整數 1,這個參數在方法中就只能做運算或再丟給其他方法使用,它不會為這個方法帶來新的東西。

那我們用 Action 來將方法變成參數,輸入到這個 RunJob 中如何?

public void RunJob(System.Action onComplete)
{
    Debug.Log("作業中...");
    onComplete();
}

你可以發現我們輸入的 onComplete 參數居然可以使用調用方法的寫法來呼叫!這代表我們能在這個方法中的任意位置調用 onComplete,能依照你給的東西來改變程式最終的結果。

那麼我們要怎麼把方法當參數輸入進去呢?

一、再寫一個方法

目前我們是不額外帶參數的 Action,所以我們可以再寫一個無參數的方法 PrintTips,並再調用 RunJob 時將 PrintTips 直接當參數帶入,僅名稱就好,不可加上括弧。
有括弧就是方法調用了,語法錯誤 ❌⛔

private void Start()
{
    RunJob(PrintTips);
}
public void PrintTips()
{
    Debug.Log("初次登入時請記得修改密碼!");
    Debug.Log("不能忘喔!");
}

二、Lambda

Lambda 的規則說起來麻煩,就不解釋了。我們這邊聚焦在要怎麼直接上手用它。請參考以下:

RunJob(() => {
    Debug.Log("初次登入時請記得修改密碼!");
    Debug.Log("不能忘喔!");
});

首先因為現在沒有讓 Action 也帶入參數,所以要用 () 空括弧來代表 => 後的方法內容,大括弧中的內容就是你的自由發揮空間。

如果裡面只有一行程式碼,還可以省略掉大括弧與結尾分號:

RunJob(() => Debug.Log("初次登入時請記得修改密碼!"));

兩種方式的輸出結果都一樣:

實作練習一

請使用一個無帶參數的 Action 並搭配方法,在開始時印出「開始任務…」後過三秒,再經由輸入的 Action 中打印「任務完成!」字串。

參考解答

using System.Collections;
using UnityEngine;
public class ActionTutorial : MonoBehaviour
{
    private void Start()
    {
        RunJob(() => {
            Debug.Log("任務完成!");
        });
    }
    public void RunJob(System.Action onComplete)
    {
        Debug.Log("開始任務...");
        StartCoroutine(JobTask(onComplete));
    }
    private IEnumerator JobTask(System.Action onComplete)
    {
        yield return new WaitForSecondsRealtime(3);
        onComplete();
    }
}

基礎語法二

前面我們講過無帶參數的用法了,現在來介紹有帶參數要怎麼寫。

在寫方法的時候,在定義的 Action 類別後加上 <類別>。例如你想它帶一個整數,那就是 System.Action<int>,如果要帶布林值,那就寫 System.Action<bool>

最後在調用 Action 時帶入你定義的型別資料,就可以成功帶入囉!

public void RunJob(System.Action<int> onComplete)
{
    Debug.Log("開始隨機取號...");
    onComplete(Random.Range(0, 1000));
}

如果想要帶入更多筆資料,除了用 Struct、Class 之外,也可以這樣做:

public void RunJob(System.Action<int, string> onComplete)
{
    Debug.Log("開始隨機取號...");
    onComplete(Random.Range(0, 1000), "成功的男人背後都有一條脊椎");
}

用逗點分隔就能加參數,很有趣吧?(最多可以加 16 個)

那要怎麼接收帶入的參數呢?

一、用另一個方法

假設 RunJob 的 Action 有帶入一個整數,那就在輸入的方法中加入一個整數參數即可。

private void Start()
{
    RunJob(PrintTips);
}
public void PrintTips(int value)
{
    Debug.Log("你抽到的號碼是" + value);
    Debug.Log("恭喜你囉!");
}

多參數的寫法如下,以此類推。

public void PrintTips(int value, string data)
{
    Debug.Log("你抽到的號碼是" + value);
    Debug.Log("恭喜你囉!");
    Debug.Log(data);
}

二、Lambda

如果你僅帶入一個值,那可以用 xyz、abc 或你隨便亂取的名稱來做為暫存用變數,最後就能在定義的區塊隨意調用它。

RunJob(x => {
    Debug.Log("你抽到的號碼是" + x);
    Debug.Log("恭喜你囉!");
});

多參數的寫法如下,需用括弧包住多筆參數,以此類推。

RunJob((x, y) => {
    Debug.Log("你抽到的號碼是" + x);
    Debug.Log("恭喜你囉!");
    Debug.Log(y);
});

兩種方法的輸出結果都一樣:

實作練習二

請用有帶參數的 Action 並搭配方法,輸出一個範圍 -1000~1000 隨機取值的 XY 二維座標,並判斷這個座標是否在地底(看 Y 是否小於 0)。

參考解答

using UnityEngine;
public class ActionTutorial : MonoBehaviour
{
    private void Start()
    {
        RunJob((a, b) => {
            Debug.Log("座標: " + a);
            Debug.Log("是否在地底: " + b);
        });
    }
    public void RunJob(System.Action<Vector2, bool> onComplete)
    {
        int xPos = Random.Range(-1000, 1000);
        int yPos = Random.Range(-1000, 1000);
        bool isUnderGround = yPos < 0;
        onComplete(new Vector2(xPos, yPos), isUnderGround);
    }
}
comments powered by Disqus