例えば
ガチャのレア1は1000本、レア2は50本、レア3は1本、で完全確率でレア1~レア3までのどれかを抽選したい。 といったような、本数ベースで抽選するようなケース。
作っておくと、ちょっと便利
public class MyUtil
{
//渡された重み付け配列からIndexを得る
public static int GetRandomIndex(params int[] weightTable)
{
var totalWeight = weightTable.Sum();
var value = Random.Range(1, totalWeight + 1);
var retIndex = -1;
for (var i = 0; i < weightTable.Length; ++i)
{
if (weightTable[i] >= value)
{
retIndex = i;
break;
}
value -= weightTable[i];
}
return retIndex;
}
}
使い方
public void Start()
{
var weightTable = new int[]{
1000,
50,
1
};
int index1 = MyUtil.GetRandomIndex(1000, 50, 1);
Debug.Log("index1:" + index1);
int index2 = MyUtil.GetRandomIndex(weightTable);
Debug.Log("index2:" + index2);
}
params
付けて可変長引数扱いなので、
int index1 = MyUtil.GetRandomIndex(1000, 50, 1);
のように、直接重みを記述してもよいし、
int index2 = MyUtil.GetRandomIndex(weightTable);
のように、重み付け配列を用意・使用してもよいです。
検証
10万回抽選
var count = new int[3];
for (int i = 0; i < 100000; ++i)
{
var index = MyUtil.GetRandomIndex(1000,50,1);
count[index]++;
}
for (int index = 0; index < count.Length; index++)
{
Debug.Log(index + ":" + count[index]);
}
出力
0:94990
1:4909
2:101
概ね期待通りぽいので、良さそうです。
なお、重み配列に0を入れて。
var index = MyUtil.GetRandomIndex(1000,0,10);
として、同じく10万回回すと
出力
0:99011
1:0
2:989
のように、0を指定したindexは抽選されなくなるので、有料ガチャなので、レア1は輩出しない。といったケースもすぐ対応できてやや便利です。
これを上手く活用すると完全確率ではなく、本当のくじびき形式(取ったくじを箱に戻さない)も
var weightTable = new int[]{1000,50,1};
var count = new int[3];
for (int i = 0; i < 500; ++i)
{
var index = MyUtil.GetRandomIndex(weightTable);
count[index]++;
weightTable[index]--;
}
for (int index = 0; index < count.Length; index++)
{
Debug.Log("取得" + index + ":" + count[index]);
}
for (int index = 0; index < weightTable.Length; index++)
{
Debug.Log("残り" + index + ":" + weightTable[index]);
}
のように、得られたindexを添え字にしてweightTableから獲得した分減らして行くことで実現できますね。(回しすぎるとカラになりますが・・・)
ちなみに
- 重みに全て0入れたりすると、-1が返ってくるので、そのまま検証処理に投げたりすると、配列範囲外起こします。
- 本数ベースではなくて、確率ベース(レア1は95%、レア2は4.9%、レア3は0.1% のような)でのランダムは本来また違った作りが必要になりますが、全部整数になるように桁を上げてしまえば同じ結果が得られると思います。 →
MyUtil.GetRandomIndex(950,49,1);