前言
當我最初寫游戲時,我經常使用標准Random()函數,然后寫一堆if和else條件來我獲得預期結果。如果結果不太好,我會寫更多的條件進行過濾或者篩選,直到我覺得游戲變得有趣。最近我發現有更好的方法。內置的Random類並沒有問題,問題是使用內置的Random類很難達到我們的預期效果。
現實生活中,以拋硬幣為例,時而會拋出連續多次花或者字。那么如果在游戲中,可能表現為多次連續的暴擊或是硬直,盡管有時暴擊和硬直的概率很低,但依舊有可能連續出現,讓人感覺詭異。那么為了解決類似的問題,前輩們想了很多種方法,比如說特殊值過濾等等。我們今天介紹的是Shuffle Bag技術,可以讓你的隨機數不那么隨機,由你掌控。
什么是Shuffle Bag呢?
Shuffle Bag是一項讓我們可以控制隨機數分布的技術。
其主要原理為:
- 設計一個特定分布數值的集合。
- 把集合中數值轉載至背包中。
- 將背包中數值隨機打亂。
- 從背包中以任意順序(從前往后,從后往前都無所謂)一個一個的抽取數值,抽完不放回,直到背包為空。
- 背包為空后,將數據放回,並重新打亂,之后循環利用。
不難看出,Shuffle Bag 特性如下:
- 不會產生集合之外的數據。
- 它仍然具有隨機性,元素出現的順序還是隨機的。
- 非重復抽取,如果數據集中某個元素只有一個,至多連續出現兩次。
如何實現Shuffle Bag?
原文使用C#,這里使用Unity C#和泛型,大家可以很方便的轉譯為其他語言。實現並不是先把整個背包先隨機打亂,而是每次抽取時,才進行一次隨機交換,這樣做可以分攤性能,不至於在背包數據較多時,打亂背包消耗某幀過多性能。
using System.Collections.Generic;
using UnityEngine;
public class ShuffleBag<T> {
private List<T> data;
private T currentItem;
private int currentPosition = -1;
private int Capacity { get { return data.Capacity; } }
public int Size { get { return data.Count; } }
public ShuffleBag(int initCapacity)
{
data = new List<T>(initCapacity);
}
public void Add(T item, int amount)
{
for (int i = 0; i < amount; i++)
data.Add(item);
currentPosition = Size - 1;
}
public T Next()
{
if (currentPosition < 1)
{
currentPosition = Size - 1;
currentItem = data[0];
return currentItem;
}
var pos = Random.Range(0,currentPosition);
currentItem = data[pos];
data[pos] = data[currentPosition];
data[currentPosition] = currentItem;
currentPosition--;
return currentItem;
}
}
如何使用Shuffle Bag?
在我的項目中,我希望NPC播放受傷動畫的概率為十分之一,並且我不希望看到他偶爾連續播放受傷動畫多次,也不想長時間不播放受傷動畫。
ShuffleBag<bool> hurtBag = new ShuffleBag<bool>(10);
hurtBag.Add(false, 9);
hurtBag.Add(true, 1);
//當觸發受傷時,調用以下邏輯
if(hurtBag.Next())
{
Play();
}
附錄
附上參考的原文鏈接 Shuffle Bags: Making Random() Feel More Random 英文好的同學可以直接看原文 。另外,這個原文題目有些讓人疑惑,所以我的譯文標題做了一些更改。
