close

列舉 (enum):也有人稱作為 "狀態機",因為列舉常常被人拿來當作狀態判斷的使用。enum 所佔的記憶體為 32 位元 (bit),這是在預設的情況底下。
因為 enum 可以更改型別,共有 byte、sbyte、short、ushort、int、uint、long、ulong。
所以佔據的記憶體容量必須看你是使用什麼型別而定,不過系統預設為 int。


enum 第一筆列舉值為 0,接下來則遞增。例如:

 
// 其中 GameStatus.Menu 就會被預設為 0, GameStatus.Loading 就是 1, 依此類推
enum GameStatus
{
  Menu,
  Loading,
  Playing,
  Pause,
  GameOver
}
 


enum 取值方式為:

 
GameStatus status = GameStatus.Menu;

// 轉換為 int, 其值為 0
int i = (int)status;
// 轉換為 string, 其值為 "Menu"
string s = status.ToString();
 


enum 也可以直接指定數值或者指定運算式。例如:

 
// 這時候 GameStatus.Menu 就會被預設為 1, GameStatus.Loading 就是 2, 依此類推
// 但是 GameStatus.GameOver 則為 3
enum GameStatus
{
  Menu = 1,
  Loading,
  Playing,
  Pause,
  GameOver = Loading + 1
}
 


有一點值得注意的是,建議第一項預設值給予 0,因為當你預設值中不包含 0 的話,可能會造成程式中判斷錯誤。

 
// 沒有設定預設值為 0 
enum GameStatus
{
  Menu = 1,
  Loading,
  Playing,
  Pause,
  GameOver
}

// 當你建立一個 enum 時, 並沒有給他參數的話, 系統會預設 0 給它
// 其中 status = 0,
// (int)status = 0,
// status.ToString() = 0,
GameStatus status = new GameStatus();


// 但如果有預設值為 0 的話
enum GameStatus
{
  Menu = 0,
  Loading,
  Playing,
  Pause,
  GameOver
}

// 當你建立一個 enum 時, 並沒有給他參數的話, 系統會預設 0 給它
// 其中 status = Menu,
// (int)status = 0,
// status.ToString() = Menu,
GameStatus status = new GameStatus();
 


enum 本身也接受列舉值相同。例如:

 
// 這樣是可接受的, 但這是沒有意義的, 只會造成開發上的麻煩
enum GameStatus
{
  Menu = 0,
  Loading = 0,
  Playing = 1,
  Pause = 1,
  GameOver = 1
}

// 若有兩個列舉值皆為 0 的話, 系統會預設為最後一筆列舉
// 其中 status = Loading ,
// (int)status = 0,
// status.ToString() = Loading ,
GameStatus status = new GameStatus();
 


enum 本身的列舉值是會遞增的,所以在預設值的時候記得要由小到大。例如:

 
enum GameStatus
{
  Menu = 0,
  Loading = 5,
  Playing,      // 該值會被遞增為 6
  Pause = 11,
  GameOver      // 該值會被遞增為 12
}
 


以下是不建議的,雖然語法上沒錯誤,但是只會讓列舉值重複,造成開發上的麻煩

 
enum GameStatus
{
  Menu = 0,
  Loading = 5,
  Playing,      // 該值會被遞增為 6
  Pause = 5,
  GameOver      // 該值會被遞增為 6
}
 


關於 enum 有個簡單用法,那就是狀態判斷,例如說遊戲中的狀態判斷,很多人會用以下方式來做判斷:

 
// 方式 1, 直接用 int 來判斷, 該方法方便, 但是非常的不直覺, 甚至開發人員本身還會忘記該數值所代表的意思
// 0 = 開頭選單, 1 = 載入畫面, 2 = 遊戲進行中, 3 = 遊戲暫停, 4 = 遊戲結束
int status = 0;

if ( status == 0 )
{
  // 開頭選單處理......
}
if ( status == 2 )
{
  // 遊戲進行中處理......
}


// 方式 2, 直接用 bool 來判斷, 該方法方便, 也非常的直覺, 但是這會造成程式過於冗長
// 開頭選單
bool isMenu = true;

// 載入畫面,
bool isLoading = true;

// 遊戲進行中
bool isPlaying = true;

// 遊戲暫停
bool isPause = true;

// 遊戲結束
bool isGameOver = true;

if ( isMenu )
{
  // 開頭選單處理......
}
if ( isPlaying )
{
  // 遊戲進行中處理......
}
 


所以這邊會建議使用 enum 來做狀態的判斷,以下方法不但直覺而且所佔的記憶體也少,不管你設了幾個列舉,他都只占一個 int (32 bit) 的記憶體位置

 
enum GameStatus
{
  Menu,      // 開頭選單
  Loading,   // 載入畫面
  Playing,   // 遊戲進行中
  Pause,     // 遊戲暫停
  GameOver   // 遊戲結束
}

GameStatus status = GameStatus.Menu;

if ( status == GameStatus.Menu )
{
  // 開頭選單處理......
}
if ( status == GameStatus.Playing )
{
  // 遊戲進行中處理......
}
 


在 enum 的上方加上 [Flags] 還能夠將 enum 定義為位元旗標,列舉值的定義上,可使用整數或者 16 進制。
但是在列舉值的定義上,建議盡量以 2 進制的進位去定義,避免運算後的值去重複。例如:

 
// 以下是不推薦的
[Flags]
enum Test
{
  T1 = 0,
  T2 = 1,
  T3 = 2,
  T4 = 4,
  T5 = 6,
  t6 = 8
}

Test t = T3 | T4;

// 這時候預想 t.ToString() 值應該為 "T3, T4", (int)t 值應該為 6
// 但因為 6 列舉值已經被定義成 T5 了, 所以正常運作下來的結果, t.ToString() 值為 "T5", (int)t 值為 6

// 所以建議列舉值盡量為 0000、0001、0010、0100、1000、0001 0000、0010 0000 
// 其值為 0、1、2、4、8、16、32 等等的 2 進制進位,避免計算後會有重複的問題發生
 


將 enum 定義為位元旗標在狀態判斷上非常的好用,下面我們舉個玩家狀態的例子:

 
// 這邊列舉值的定義我們使用 16 進制,
// 16 進制為, 0x1, 0x2, 0x3, ... , 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10
// 0xa 也可以為 0xA, 後面的英文可以大寫, 其值為 10
// 0xf 其值為 15
// 0x10 其值為 16, 因為是 16 進制, 所以 0xf 在加 1 就會進位
[Flags]
enum PlayerBuffs
{
  None = 0x00,  // 一般狀態, 0x00 = 0000 = 0
  HP = 0x01,    // 血量加持狀態, 0x01 = 0001 = 1
  MP = 0x02,    // 魔力加持狀態, 0x02 = 0010 = 2
  Def = 0x04,   // 防禦力加持狀態, 0x04 = 0100 = 4
  Atk = 0x08    // 攻擊力加持狀態, 0x08 = 1000 = 8
  Speed = 0x10  // 速度加持狀態, 0x10 = 0001 0000 = 16
}

// 建立一個玩家狀態, 其值為預設 0 = None
PlayerBuffs playerBuffs = new PlayerBuffs();
 

 

以下為添加狀態的用法:

 
// 若要添加狀態, 可使用 "|" (OR 運算子)
// 添加血量加持, 添加後其值為 1 = HP
playerBuffs = playerBuffs | PlayerBuffs.HP;

// 添加防禦力加持, 添加後其值為 5 = HP, Def
playerBuffs = playerBuffs | PlayerBuffs.Def;
 

在這邊做個計算講解,"|" (OR 運算子) 意思為 有 1 則 1,都 0 則 0。
系統會自動轉換為 2 進制去做運算所以
None | HP = 0000 | 0001 = 0001 = 1
HP | Def = 0001 | 0100 = 0101 = 5
所以其值為 "HP, Def" 列舉值為 0101 = 5。

因為我們沒有列舉值 5,所以結果才能夠為 "HP, Def",假如說 Atk 我們不是定義為 8 的話,而是定義為 5,這時候值就會為 "Atk",因為列舉值 5 為 Atk。
這就產生一個 BUG,我明明是 "血量加持、防禦力加持",為什麼結果卻變成 "攻擊力加持"?程式也可能會判斷成 "血量加持、防禦力加持、攻擊力加持" 三種狀態都有的情況。
現在大家知道為什麼列舉值建議為 2 進制的進位了吧,因為要避免這些判斷錯誤的問題發生。

 

以下為判斷狀態的用法:

 
// 若要判斷擁有的狀態, 可使用 "&" (AND 運算子)
// 要使用判斷運算時, 記得要加上括弧 (), 否則程式會報錯
// 是否擁有血量加持狀態
if ( (playerBuffs & PlayerBuffs.HP) == PlayerBuffs.HP )
{
  // 血量加持處理...
}

// 是否擁有魔力加持狀態
if ( (playerBuffs & PlayerBuffs.MP) == PlayerBuffs.MP )
{
  // 魔力加持處理...
}

// 是否擁有防禦力加持狀態
if ( (playerBuffs & PlayerBuffs.Def) == PlayerBuffs.Def )
{
  // 防禦力加持處理...
}

// 是否擁有攻擊力加持狀態
if ( (playerBuffs & PlayerBuffs.Atk) == PlayerBuffs.Atk )
{
  // 攻擊力加持處理...
}

// 是否擁有速度加持狀態
if ( (playerBuffs & PlayerBuffs.Speed) == PlayerBuffs.Speed )
{
  // 速度加持處理...
}
 

在這邊做個計算講解,"&" (AND 運算子) 意思為 都 1 則 1,否則皆 0。
系統會自動轉換為 2 進制去做運算所以
(playerBuffs & PlayerBuffs.HP) == PlayerBuffs.HP 會轉換為
0101 & 0001 = 0001 所以 0001 == 0001 結果為 true

(playerBuffs & PlayerBuffs.MP) == PlayerBuffs.MP 會轉換為
0101 & 0010 = 0000 所以 0000 == 0010 結果為 false

(playerBuffs & PlayerBuffs.Def) == PlayerBuffs.Def 會轉換為
0101 & 0100 = 0100 所以 0100 == 0100 結果為 true

 

以下為移除狀態的用法:

 
// 若要移除某個狀態, 可使用 "^" (XOR 運算子)
// 移除血量加持, 移除後其值為 4 = Def
if ( (playerBuffs & PlayerBuffs.HP) == PlayerBuffs.HP )
{
  playerBuffs = playerBuffs ^ PlayerBuffs.HP;
}
 

在這邊做個計算講解,"^" (XOR 運算子) 意思為 有 1 則 1,都 1 則 0,都 0 則 0。
系統會自動轉換為 2 進制去做運算所以
playerBuffs ^ PlayerBuffs.HP 會轉換為
0101 ^ 0001 = 0100


大家可能會好奇,為什麼移除之前還要經過 if 在判斷一次,這是因為 "^" (XOR 運算子) 有 1 則 1 的規則,如果我們的狀態中,並沒有 "血量加持" 的話,在這次的移除反而會變成添加。
我們示範一下,若再次執行移除的話會發生什麼事
playerBuffs ^ PlayerBuffs.HP 會轉換為
0100 ^ 0001 = 0101
"血量加持" 又被添加回來了,所以大家在執行移除之前,記得再次做個判斷,避免發生這類的錯誤發生。


以上的講解是不是讓你覺得 enum 用來判斷狀態上非常的好用且方便了呢?
很多人在做狀態的判斷時,都會添加一大多 bool,或者用 int 1 = 血量、2 = 魔力、3 = 血量 + 魔力 之類的。
不但程式又臭又長,判斷式也要撰寫一大堆,而且非常容易出現人為的錯誤,重點是!還要浪費一大堆記憶體來存放這些變數。

而 enum 不管你宣告的列舉有多少筆,它都只占一筆的記憶體的位置,所以!趕快學起來吧!

 

若對 位元旗標(Flags) 有興趣的朋友,可以前往 "Unity Layer - 運算相關" 這篇文章看看,該篇文章可算是 位元旗標(Flags) 的實戰篇。

 

arrow
arrow

    岳 發表在 痞客邦 留言(0) 人氣()