Unity的C#编程教程_54_静态类型 Static Types 详解及应用练习

C# Static Types

1.Working with Static Types

  • 所谓的 static type,使用的是 static 关键字

    • 比如可以有 static class,static method,或者 static variable
    • static type 存在于栈 stuck 中(内存),生命周期与程序运行一致
  • 任务说明:

    • 我们设计一个程序用于接收信息,比如一堆数字,然后用程序加总
    • 假设每次按下空格键加 10

传统的做法是首先新建一个 Score 的类,用于存放累加后的结果:

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

public class Score : MonoBehaviour
{

    public int num;

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

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

这个脚本可以挂载到一个游戏对象 Score 上面

然后再用一个脚本进行累加操作:

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

public class StaticTest : MonoBehaviour
{

    public Score score; // 实例化一个 Score 的类

    // Start is called before the first frame update
    void Start()
    {
        score = GameObject.Find("Score").GetComponent<Score>(); // 初始化
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            score.num += 10; // 对于类下面的 num 变量加 10
        }
    }
}

这个脚本也挂载到一个游戏对象下面,运行游戏后,按下空格键就可以看到 Score 里面的 Num 每次会加 10

然后我们尝试把 Score 类下面的 num 变量改为 static:

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

public class Score : MonoBehaviour
{

    public static int num;

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

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

计数程序就要做对应修改:

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

public class StaticTest : MonoBehaviour
{

    // public Score score; // 不再需要实例化

    // Start is called before the first frame update
    void Start()
    {
        //score = GameObject.Find("Score").GetComponent<Score>(); // 也不需要初始化
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //score.num += 10;
            Score.num += 10; // 直接对于类下面的 num 变量加 10
            Debug.Log(Score.num); // Inspector 中无法显示,需要通过 debug 进行显示
        }
    }
}

静态变量直接存储在内存中,无需通过类的实例化来分配内存,所以可以直接对其进行操作。

Static 的东西没有办法直接在 unity 的 inspector 中看到,所以可以通过 debug 的方式查看数值变化。

在运行大型项目的时候,由于静态数据会占据内存,需要额外关注。

不仅仅是变量可以 static,类和方法也可以是 static 的。

当你使用 static class 的时候,这个类无法继承别的父类,所以自然 static class 无法继承 MonoBehaviour。

2.Practical Example of Working with Static Types

  • 实际运用 static type
  • 创建一个 enemy spawn manager
    • 显示 enemy 的数量,使用 UI 展现数字
    • 使用 cube 代表 enemy,设定为 prefab

首先创建一个 cube,命名为 Emeny,然后拖拽到 assets 中作为 prefab,原窗口中的 Enemy 可以删除,然后创建脚本 SpawnManager:

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

public class SpawnManager : MonoBehaviour
{
    public GameObject enemyPrefab;
    public static int enemyCount; // 这里使用静态变量

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

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Instantiate(enemyPrefab); // 每次按下空格键生成 enemy
        }
    }
}

另外创建一个 Enemy 脚本,挂载到 Enemy 的 Prefab 上,用于控制 Enemy 的激活和关闭:

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

public class Enemy : MonoBehaviour
{
    private UIManager _ui; // 实例化

    public void OnEnable() // unity 自带的方法,生成游戏对象的时候自动调用
    {
        SpawnManager.enemyCount++; // 激活的时候,enemy数量+1
        // 静态变量可以直接操作,非常方便!

        _ui = GameObject.Find("UIManager").GetComponent<UIManager>(); // 初始化
        _ui.UpdateEnemyNum(); // 调用方法更新数字

        Delet(); // 调用销毁方法
    }

    public void OnDisable() // unity 自带的方法,销毁游戏对象的时候自动调用
    {
        SpawnManager.enemyCount--; // 关闭的时候,enemy数量-1
        // 静态变量可以直接操作,非常方便!

        _ui.UpdateEnemyNum(); // 调用方法更新数字
    }

    void Delet() // 销毁方法
    {
        Destroy(this.gameObject, Random.Range(2.0f, 4.0f)); // 2~4秒后销毁自己
    }
}

这里我们发现,把 enemyCount 设置为静态变量后,操作起来非常方便!不需要有实例化和初始化过程,直接可以对其进行操作!

将 Enemy 的 Prefab 拖拽到 SpawnManager 脚本上。

创建一个空的游戏对象,命名为 UIManager,并挂载同名脚本,用于显示现存的 Enemy 数量:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // 直接 using UI 库方便使用

public class UIManager : MonoBehaviour
{
    public Text enemyNumText; // 实例化一个 Text

    public void UpdateEnemyNum()
    {
        enemyNumText.text = "Enemy Number: " + SpawnManager.enemyCount;
    }
}

在场景中新建一个 Text 游戏对象,按照喜好修改大小颜色等参数,然后将其拖拽到 UI Manger 的脚本中。

运行游戏,按下空格键就会生成 Enemy,UI会显示数字增加,当 Enemy 自动销毁后,数字会减小。

注意几点:

  • static 的方法无法使用非 static 的变量。
  • 静态的即不变的,在某个类中只有一个,不会因实例化对象的不同而不同。
  • 静态类不能实例化,不能使用 new 关键字创建静态类类型的变量。

Instance Members vs Static Members

  • 区别 instance 和 static
    • instance:实例化以后才存在
    • static:程序运行开始到结束都会存在,如果 class 中有一个静态变量,那么这个变量为所有该类的实例化进行共享
    • 这个问题也是面试常见题目
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class ItemTest // 自定义一个类
{
    public string name;
    public int id;

    public static int itemNum; // 总的道具数量,设为静态变量
}


public class InstanceTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 当我们要使用自定义的类的时候,首先要进行实例化 instance
        ItemTest sword = new ItemTest();
        ItemTest spear = new ItemTest();
        // 这里创建的两个实例,拥有不同的变量 name 和 id
        // 但是 itemNum 这个变量只有一个,每个实例都共享
        // 这个时候我们无法通过 sword.itemNum 来访问这个变量
        // 可以直接通过 ItemTest.itemNum 来进行访问
        
    }

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

那静态变量如何使用的,举个例子,通过构造函数:

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


public class ItemTest // 自定义一个类
{
    public string name;
    public int id;

    public static int itemNum; // 总的道具数量,设为静态变量

    public ItemTest() // 每次实例化的时候自动调用构造函数
    {
        itemNum++; // 相当于每次实例化一个道具,道具总数+1
    }
}


public class InstanceTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 当我们要使用自定义的类的时候,首先要进行实例化 instance
        ItemTest sword = new ItemTest();
        ItemTest spear = new ItemTest();
        ItemTest axe = new ItemTest();

        Debug.Log("Item number: " + ItemTest.itemNum);
        // 前面生成了 3 个实例,所以这个得到的数字就是 3
        
    }

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

比如在线网游中,显示的当前在线玩家数量,就可以通过这个方法实现。

Utility Helper Classes

  • 静态方法和静态类
    • SDK:Software Development Kit
    • utility classes:用来让开发流程更流畅
    • 同时为多个应用程序开发时,我们不希望手动地复制粘贴一个通用方法,这个时候我们可以有一个 utility helper class 涵盖该通用的方法。比如我们开发多个计算机视觉程序,每个程序都要用到图片预处理,而且预处理的步骤是相同的,那我们就可以用一个 utility helper class 来涵盖该预处理的方法。

我们就可以把这个 utility helper class 设置为静态:

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

public static class UtilityHelper // 静态类不能继承父类,所以这里不能继承 MonoBehaviour
{

    // 静态类下面的所有成员都必须是静态的,包括变量和方法等

    public static void CreatCube() // 这个方法用于创建一个 cube
    {
        GameObject.CreatePrimitive(PrimitiveType.Cube); // 创建一个预设的 cube
    }

    public static void ChangePosition(GameObject obj) // 这个方法用于改变游戏对象位置
    {
        obj.transform.position += new Vector3(1, 0, 0); // 把游戏对象沿着 x 轴移动 1
    }
}

然后我们创建一个 cube 作为 Player 并挂载同名脚本:

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

public class Player : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 按下空格键
        {
            UtilityHelper.CreatCube(); // 调用生成 cube 的方法
        }

        if (Input.GetKeyDown(KeyCode.C)) // 按下 C 键
        {
            UtilityHelper.ChangePosition(this.gameObject); // 改变 player 的位置
        }
    }
}

以此来调用静态类。

这里我们使用的 utility helper class 下面可以放置多种静态方法,便于我们在其他程序段落中调用。

当然,我们也可以根据需求使用具有返回值的静态方法。需要注意的是,静态类和静态方法的生命周期是程序开始到程序结束,所以一直会占用到内存!

Challenge: Random Color Helper

  • 任务说明:
    • 创建一个 helper class,用于改变游戏对象的颜色
    • 该改变颜色的方法需要传入游戏对象和目标颜色
    • 测试:按下空格键改变 player 的颜色
    • 额外任务:改变为随机颜色

首先创建 helper class:

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

public static class UtilityHelper // 静态类不能继承父类,所以这里不能继承 MonoBehaviour
{
    public static void ChangeColor(GameObject obj, Color color) // 改变游戏对象颜色
    {
        obj.GetComponent<MeshRenderer>().material.color = color;
    }
}

然后创建 Player 并挂载同名脚本:

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

public class Player : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.C)) // 按下 C 键
        {
            UtilityHelper.ChangeColor(this.gameObject, Color.red);
            // player 变为红色
        }
    }
}

随机改变颜色:

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

public static class UtilityHelper // 静态类不能继承父类,所以这里不能继承 MonoBehaviour
{

    public static void ChangeColor(GameObject obj, Color color, bool randomColor = false) // 改变游戏对象颜色
    {
        if (randomColor)
        {
            color = new Color(Random.value, Random.value, Random.value);
            // Random.value 返回的是 0.0~1.0 的值,包含了0.0和1.0两个边界
        }
        obj.GetComponent<MeshRenderer>().material.color = color;    
    }
}

对应修改 player 脚本:

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

public class Player : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

        if (Input.GetKeyDown(KeyCode.C)) // 按下 C 键
        {
            UtilityHelper.ChangeColor(this.gameObject, Color.red, true);
            // player 变为随机颜色
        }
    }
}

Initializing Static Members with a Static Constructor

  • 初始化一个静态对象
    • 和构造函数初始化一个实例对象类似,这里使用静态构造函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Players // 玩家的类
{
    public string name; // 姓名
    public int id; // id
    public int level; // 等级

    public static string group; // 组织为静态变量

    public Players() // 一般的实例化类构造函数,初始化非静态变量
    {
        Debug.Log("Instance Members initialized");
    }

    static Players() // 静态构造函数,初始化静态变量
    {
        group = "LOL";
        Debug.Log("Static Members initialized");
    }
}

public class InitialTest : MonoBehaviour
{

    public Players p1;
    public Players p2;

    // Start is called before the first frame update
    void Start()
    {
        p1 = new Players(); // 初始化 p1
        p2 = new Players(); // 初始化 p2
    }

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

在 console 中可以看到,先显示一个 “Static Members initialized”,然后显示 2 个 “Instance Members initialized”,静态变量只初始化一次,而且是在其他初始化之前进行。

注意:静态变量的的初始化在实例化之前进行,因为静态变量会在所有实例化对象之间进行共享。

Tags: