隨機數幾乎應用於游戲開發的方方面面,例如,隨機生成的地圖,迷宮,怪物屬性等,在Unity中,使用隨機數非常方便:
1 // 2 // 摘要: 3 // Return a random integer number between min [inclusive] and max [exclusive] (Read 4 // Only). 5 // 6 // 參數: 7 // min: 8 // 9 // max: 10 public static int Range(int min, int max);
1 // 2 // 摘要: 3 // Return a random float number between min [inclusive] and max [inclusive] (Read 4 // Only). 5 // 6 // 參數: 7 // min: 8 // 9 // max: 10 [FreeFunction] 11 public static float Range(float min, float max);
1 // 2 // 摘要: 3 // Returns a random number between 0.0 [inclusive] and 1.0 [inclusive] (Read Only). 4 public static float value { get; }
正常情況下使用以上三種完全夠用了,注意整型的隨機是左開右閉的。當然了,你也可以使用System.Random中的方法來隨機,可以構造出類似於Unity中的擴展方法:
1 static public int Range(this System.Random random, int min, int max) 2 { 3 return random.Next(min, max); 4 } 5 6 static public float Range(this System.Random random, float min, float max) 7 { 8 var r = random.NextDouble(); 9 return (float)(r * (max - min) + min); 10 }
值得注意的是,System.Random需要實例化才能隨機,而UnityEngine.Random是直接使用。
但很多時候,我們除了需要隨機數之外,可能會有保留上次隨機結果的需求,換句話說,從某一時刻起,我們希望每次都能隨機出和上次相同的結果,這個時候就該隨機種子出場了。
舉例來說,當玩家需要重新進入一次他以前隨機出來過的一個迷宮地圖進行二次創作,又比如,我們在開發過程中,某個隨機單位出現了Bug,但如果下次又沒法產生之前隨機結果的話,那么就會出現十分頭疼的狀況了,這樣很可能永遠有個難以排查的潛在Bug一直在開發過程中而又難以再次復現。
所以,強烈建議,只要是做相對比較復雜的隨機行為,我們最好利用隨機種子來執行隨機。
當然了,你說我將所有隨機的數據結果序列化保存到本地,那也沒問題,但相比隨機種子只需要保存一個整型數據來說,哪種方式更可取顯而易見。這樣也可以大大減少游戲保存的數據容量。
說了這么半天,什么是隨機種子呢?
顧名思義,一個種子對應着一個結果,隨機種子對應的就是一個唯一的隨機結果。
1 // 2 // 摘要: 3 // Initializes the random number generator state with a seed. 4 // 5 // 參數: 6 // seed: 7 // Seed used to initialize the random number generator. 8 [NativeMethod("SetSeed")] 9 [StaticAccessor("GetScriptingRand()", StaticAccessorType.Dot)] 10 public static void InitState(int seed);
上面的方法中,參數seed就是傳入的隨機種子,如果在腳本的一開始執行調用了此方法,那么只有當此次隨機種子與上次的種子不相同時,才能隨機出不同的隨機結果,否則隨機的結果總是一樣的。
注意,這里指的隨機結果是指的所有的隨機結果,是一個隨機數表,它從本質上改變的是整個UnityEngine.Random類的所有隨機方法執行的結果,包括最開始列舉的三種中的任意一種。
下面做一個測試就很容易理解了:
1 using UnityEngine; 2 3 public class RanTest : MonoBehaviour 4 { 5 public bool bDebug; 6 //System.Random random; 7 void Start() 8 { 9 //random = new System.Random((int)System.DateTime.Now.Ticks); 10 //string s = ""; 11 //for(int i = 0; i < 233; i++) 12 //{ 13 // s += random.Range(0, 10) + ","; 14 //} 15 //Debug.Log(s); 16 17 int seed = (int)System.DateTime.Now.Ticks; 18 if (bDebug) 19 { 20 seed = PlayerPrefs.GetInt("Seed"); 21 } 22 else 23 { 24 PlayerPrefs.SetInt("Seed", (int)System.DateTime.Now.Ticks); 25 } 26 Random.InitState(seed); 27 string s = ""; 28 for (int i = 0; i < 32; i++) 29 { 30 s += Random.Range(0, 10) + ","; 31 } 32 Debug.Log(s); 33 } 34 }
比如我開了一個Debug模式,如果勾選,則隨機種子是從上次保存的數據中讀取,隨機出來的結果永遠是一樣的,因為我並沒有對保存的數據種子進行任何的更改。
結果如下:
我們發現每次的隨機數都一樣,因為它們都源於同一個隨機種子,無論之后再隨機多少次,結果都是這個隨機數序列,這個種子對應的結果已經被計算機固定了,除非種子更改,不然隨機結果不會變。
當我關閉Debug模式時,正常的隨機種子時刻都不會一樣,這里用到了System.DateTime.Now.Ticks來保證得到和上次的種子絕不相同的整型,也可以使用guid等。
每次在本地備份一次上一次隨機種子的記錄,以便隨時可以再現上一次隨機的結果,只需要輕松勾選Debug即可:
例如,我在第三次時發現了隨機產生的其他Bug,這樣我只用啟動Debug模式反復分析幾遍后把復現的隱藏Bug修改結束后再回到正常模式產生新的隨機數就好。
另外,我們也可以利用System.Random類的構造方法來實現同樣的隨機效果,一個構造方法帶有隨機種子的參數,一個則沒有,原理和上面是一樣的:
1 public Random(); 2 public Random(int Seed);
這個時候改變的就是System.Random類的隨機方法,而非UnityEngine.Random的隨機方法。
所以一開始就決定好整個開發過程中用的隨機類也不容忽視,建議要么就全部用Unity中的,要么就全部用System中的,這樣調整起來自然更得心應手事半功倍。
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=12ri51jwydxyj