Unity的C#編程教程_55_Properties 詳解及應用練習

目錄:

C# Properties

1.Properties

  • 什麼是屬性
    • 其實就是一種變數
    • 可以從中獲取數據
    • 可以運行函數
    • 控制訪問修飾符 control access modifiers

來看案例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
        // 為什麼通過這個語句可以進行賦值操作呢?
        // 滑鼠移動到 position 上面可以看到 {get;set;}
        // 說明我們可以從中獲取數據,也可以設置數據

        // 另外一個例子是:Time.deltaTime 增量時間(代表時間修正到真實時間)
        // 如果滑鼠移動到說明可以看到 {get;}
        // 這表示只能獲取,不能設置
        // 所以我們不能賦值,即不能寫成 Time.deltaTime = 5; 這樣
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

  • 如何定義我們自己的 property

假設我們設置一個 bool 變數監控遊戲是否結束:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{
    public bool isGameOver; // 指示遊戲是否結束

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void GameOver() // 當玩家死亡的時候觸發調用該方法
    {
        isGameOver = true;
        // 遊戲結束變為 true 的時候,遊戲 UI 顯示 GAME OVER
    }
}

其實我們可以直接在 property 的內部進行相應的操作:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{
    private bool isGameOver; // 指示遊戲是否結束
    // 設置為 private 後僅有該腳本本身可以訪問該變數
    // 我們希望該變數變為只讀

    public bool IsGameOver // property 的聲明,使用 pascal casing 命名方式
    {
        get
        {
            return isGameOver; // 讀取對應數值
        }

        set
        {
            isGameOver = value; // 賦值操作,這裡 value 的數據類型需要和目標一致,這裡為 bool
        }

    }

    // Start is called before the first frame update
    void Start()
    {
        isGameOver = false; // 遊戲開始,初始化為 false
    }

    // Update is called once per frame
    void Update()
    {
        
    }

}

當我們設置了 property 的聲明後,在下面的語句中輸入 isGameOver 後,滑鼠移動到上面就可以看到 {get; set; },如果僅僅設置了 get,那會顯示 {get; }。

然後我們把修改 UI 的鏈接內置到 property 中:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{
    private bool isGameOver; // 指示遊戲是否結束
    // 設置為 private 後僅有該腳本本身可以訪問該變數
    // 我們希望該變數變為只讀

    public bool IsGameOver // property 的修飾符,使用 pascal casing 命名方式
    {
        get
        {
            return isGameOver; // 讀取對應數值
        }

        set
        {
            if (value == true)
            {
                Debug.Log("GAME OVER"); // 假設鏈接到 UI 上顯示 GAME OVER
                
            }
            isGameOver = value; // 賦值操作,這裡 value 的數據類型需要和目標一致,這裡為 bool     
        }
        

    }

    // Start is called before the first frame update
    void Start()
    {
        IsGameOver = false; // 遊戲開始,初始化為 false
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 假設按下空格鍵表示玩家死亡
        {
            IsGameOver = true; // 觸發遊戲結束的指示  
        }
    }

}

特別注意:這裡在腳本里輸入 isGameOver 後有兩個選擇,一個是 F (isGameOver),另外一個是 P (IsGameOver),我們想要調用 get 和 set,就必須使用 P 的這個 IsGameOver。

2.Auto Properties

  • 什麼是 auto property
    • 控制誰可以訪問裡面的資訊
    • 設置只讀,不能修改:刪除 set 部分即可
    • 原來我們設置的時候,首先需要一個 private 變數,然後通過 property 來訪問,但是現在 auto property,只需要一個語句

舉例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{

    public bool IsGameOver { get; set; } // 設為 auto property

    // Start is called before the first frame update
    void Start()
    {
        IsGameOver = false;
        // 這裡 auto property 可以當作普通的變數使用
        // 但是和一般的 property 的區別在於,不能在裡面添加額外的程式碼
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 假設按下空格鍵表示玩家死亡
        {
            IsGameOver = true; // 觸發遊戲結束的指示  
        }
    }

}

如果我們想要 IsGameOver 僅限至於本腳本可以更改,外部腳本只能訪問而不能修改其值,在 set 前面添加 private 即可:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{

    public bool IsGameOver { get; private set; } // 設為 auto property

    // Start is called before the first frame update
    void Start()
    {
        IsGameOver = false;
        // 這裡 auto property 可以當作普通的變數使用
        // 但是和一般的 property 的區別在於,不能在裡面添加額外的程式碼
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 假設按下空格鍵表示玩家死亡
        {
            IsGameOver = true; // 觸發遊戲結束的指示  
        }
    }

}

根據需要,如果需要子類可以訪問修改,那設置成 protected set 即可。

總結一下,property 是讓我們擁有了一個可變性更強的,更智慧的「變數」。

但是 property 有個缺點,就是無法在 inspector 中查看(除非進入 debug 模式)。

3.When to Use Properties

  • 什麼時候使用 properties 呢?
    • 通常用在 manager classes 中
    • 比如整個遊戲的控制腳本,監控遊戲角色是否死亡,控制主體遊戲進程,目前遊戲關卡等
    • 比如遊戲角色死亡狀態,只有主進程 game manager 可以設定,其他腳本只能進行只讀訪問,那就適合設置為 property
    • 還比如說遊戲得分,也是類似的
    • 這樣的設置方法是為了更為合理,減少遊戲 bug,減少遊戲漏洞,減少程式的安全問題。如果隨便一個腳本就可以把角色改為死亡狀態,或者直接改掉總得分,那絕對會出問題!
    • 另外,在最佳的編程實踐中,所有變數都應該默認是 private 的,除非該變數有不得不設置為 public 的理由
    • unity 中 properties 不會在 inspector 中顯示
    • property 不能像普通的變數那樣,在命名的時候進行初始化賦值,所以需要在 start 方法中進行初始化
    • 另外一種情況使用 property,在 spawn manager 中,希望監控生成了多少 enemy 總數,因為這個數據不需要我在 inspector 中進行修改

Challenge: Declaring Properties

  • 任務說明
    • 設計 2 個 properties
    • 一個是:速度,要求對於別的腳本只讀
    • 一個是:名字
    • 嘗試使用 auto properties 和 declaring properties
    • 在 declaring properties 中使用 Debug.Log 顯示該方法被調用
    • 在 start 方法中測試
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PropertyTest : MonoBehaviour
{

    public int Speed { get; private set; } // 速度,對於別的腳本只讀
    private string _playerName; // 名字

    public string PlayerName
    {
        get
        {
            Debug.Log("get method called");
            return _playerName;
        }

        set
        {
            Debug.Log("set method called");
            _playerName = value;
        }
    }


    // Start is called before the first frame update
    void Start()
    {
        Speed = 16;
        Speed += 10;
        Debug.Log("Speed: " + Speed);

        PlayerName = "Mike";
        PlayerName += "s";
        Debug.Log("Player name: " + PlayerName);
    }

    // Update is called once per frame
    void Update()
    {

    }

}

Tags: