Unity的C#編程教程_54_靜態類型 Static Types 詳解及應用練習
1.Working with Static Types
2.Practical Example of Working with Static Types
Instance Members vs Static Members
Utility Helper Classes
Challenge: Random Color Helper
Initializing Static Members with a Static Constructor
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”,靜態變數只初始化一次,而且是在其他初始化之前進行。
注意:靜態變數的的初始化在實例化之前進行,因為靜態變數會在所有實例化對象之間進行共享。