C# Array operations in Unity
Array
整理一下 C# Array(陣列)的筆記,紀錄對陣列常用的操作。
結論:若需要使用程式對 Array 進行大量的資料操作,請改用 List。
範例都以以下程式碼為基礎,在 Start 撰寫以下的展示程式碼。請注意引用到 UnityEditor,請留意發佈的編譯(Compile)問題。
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
public class Example : MonoBehaviour
{
[SerializeField]
string[] names = new string[0];
}
有一個字串的資料陣列,SerializeField,設計者可以透過 UnityEditor 介面編輯,輸入初始資料。(進階閱讀:不同的陣列編輯器)
預設資料
可以改用以下宣告,設定預設值:
[SerializeField]
string[] names = new string[] { "Darjeeling", "Assam", "Orange Pekoe", "Rose Hip" };
或者是使用 Reset,一個一個元素(Array element)設定初始值:
void Reset()
{
names = new string[4];
names[0] = "Darjeeling";
names[1] = "Assam";
names[2] = "Orange Pekoe";
names[3] = "Rose Hip";
}
列舉元素
使用 index 列舉:
for (var i = 0; i < names.Length; i++)
{
Debug.Log(names[i]);
}
使用 C# foreach 列舉(個人傾向使用):
foreach (var name in names)
{
Debug.Log(name);
}
兩段程式碼的其輸出結果一樣:
Darjeeling
Assam
Orange Pekoe
Rose Hip
新增元素
這很為難,陣列是固定長度的資料容器(Data container),沒有提供類似的函數,需要迂迴來達成該功能。
原生方式,使用 System.Array.Resize 來增加一個長度,然後塞值。
Array.Resize(ref names, names.Length + 1);
names[names.Length - 1] = "Rukuriri";
透過暫存的容器,使用 System.Collections.Generic.List 這個常用且強大的容器來增加,最後輸出新增元素後的陣列。
var temp = new List<string>(names);
temp.Add("Rukuriri");
names = temp.ToArray();
Unity 專屬,引擎提供的 UnityEditor.ArrayUtility,包含一些對於陣列的操作。使用 ILSpy 去看實做方式,也使用暫存的容器 List 來達成。
ArrayUtility.Add(ref names, "Rukuriri");
新增一堆元素
假設要新增多個元素:
var newNames = new string[] { "Rukuriri", "St Gloriana" };
可以使用暫存的容器 List 來達成。
var temp = new List<string>(names);
temp.AddRange(newNames);
names = temp.ToArray();
或是 UnityEditor 的擴充:
ArrayUtility.AddRange(ref names, newNames);
移除指定位置的元素
使用 List 來達成:
var temp = new List<string>(names);
temp.RemoveAt(2);
names = temp.ToArray();
UnityEditor 的擴充:
ArrayUtility.RemoveAt(ref names, 2);
移除某個元素
使用 List 來達成:
var temp = new List<string>(names);
temp.Remove("Assam");
names = temp.ToArray();
UnityEditor 的擴充:
ArrayUtility.Remove(ref names, "Assam");
搜尋某個元素
搜尋是否有存在指定資料的元素,例如 Darjeeling 是否存在:
var exist = Array.IndexOf(names, "Darjeeling") >= 0;
var exist = Array.LastIndexOf(names, "Darjeeling") >= 0;
差異在搜尋順序,一個從 [0] 開始搜尋,另一個從陣列尾開始搜尋。
搜尋某個條件的元素
搜尋是否存在滿足條件的元素,例如名稱開頭為 Dar:
var exist = Array.FindIndex(names, (v) => { return v.StartsWith("Dar"); });
var exist = Array.FindLastIndex(names, (v) => { return v.StartsWith("Dar"); });
差異在搜尋順序,一個從 [0] 開始搜尋,另一個從陣列尾開始搜尋。
搜尋並列出符合某條件元素們(Elements)
搜尋名稱中,所有 D 開頭的名稱:
var searchNames = Array.FindAll(names, (v) => { return v.StartsWith("D"); });
注意這方式會產生另一個陣列來放結果。
轉換
將陣列元素全部轉換,例如將名稱轉換成小寫:
var lowercaseNames = Array.ConvertAll(names, (v) => { return v.ToLower(); });
// lowercaseNames: darjeeling, assam, orange pekoe, rose hip
注意這方式會產生另一個陣列來放結果。
排序 Sort
var data = new string[] { "a10", "a1", "a9", "a20", "a2" };
Array.Sort(data);
// data = a1, a10, a2, a20, a9
不得不提 UnityEditor.EditorUtility 的自然排列(Natural sort)實作,可以用以下程式碼進行自然排序:
var data = new string[] { "a10", "a1", "a9", "a20", "a2" };
Array.Sort(names, EditorUtility.NaturalCompare);
// data = a1, a2, a9, a10, a20
結果感覺好多了不是?
關於 Linq
Linq 是 C# Enumerable 的擴充,之前這一篇有再提其 Enumerable 概念,但 Linq 能做的事情實在是太多,因此這邊只列出兩個範例。
搜尋 D 開頭的名字:
foreach (var name in names.Where((v)=> { return v.StartsWith("D"); }))
{
Debug.Log(name);
}
// Output:
// Darjeeling
全部轉換成小寫:
foreach (var name in names.Select((v)=> { return v.ToLower(); }))
{
Debug.Log(name);
}
// Output:
// darjeeling
// assam
// orange pekoe
// rose hip
甚至這兩個做結合,先搜尋 D 字母開頭的名稱,在轉換成小寫:
foreach (var name in names.Where((v) => { return v.StartsWith("D"); }).Select((v) => { return v.ToLower(); }))
{
Debug.Log(name);
}
// Output:
// darjeeling
想法
很明顯的,拿個固定長度 Array 進行資料新增或是刪除,是相當不容易的一件事。若需要在遊戲中修改資料陣列長度,改用 List 吧。
public class Example : MonoBehaviour
{
[SerializeField]
List<string> names = new List<string>();
}
或是有初始值的宣告:
public class Example : MonoBehaviour
{
[SerializeField]
List<string> names = new List<string>(){ "Darjeeling", "Assam", "Orange Pekoe", "Rose Hip" };
}
沒有留言: