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: