よくある重み付きの抽選機能の実装例の紹介です。
重み付き抽選とは要素ごとに選ばれる確率が違う抽選方法です。
例えば以下のように各々確率が違うものをランダムで選びます。
- Aは50%
- Bは25%
- Cは20%
- Dは5%
確認環境
- Unity 2022.3.5f1
- VisualStudio 2022
Editor 上のみで動作を確認
使い方
まずは使い方です
// Sample.cs // フルーツを重み付き抽選で選ぶ public void Select() { // 抽選確立のリストを作成する List<LotteryItem<Fruit>> items = new(); items.Add(new LotteryItem<Fruit>(Fruit.Apple, 50.5f)); // 50.5% items.Add(new LotteryItem<Fruit>(Fruit.Banana, 25.5f)); // 25.5% items.Add(new LotteryItem<Fruit>(Fruit.Orange, 15.0f)); // 15.0% items.Add(new LotteryItem<Fruit>(Fruit.Grape, 5.0f)); // 5.0% items.Add(new LotteryItem<Fruit>(Fruit.Pineapple, 4.0f)); // 4.0% // 抽選する Fruit result = RandomUtil.SelectOne(items); } public enum Fruit { Apple, Banana, Orange, Grape, Pineapple }
実装コード
合計で 100になるようにサンプルを作りましたが重みは100でなくても大丈夫です。100より多くても少なくてもその割合で要素が抽選されます。
// RandomUtil.cs using System.Collections.Generic; using System.Linq; using UnityEngine; public static class RandomUtil { // 重み付き抽選を行う(配列用) public static T SelectOne<T>(LotteryItem<T>[] list) { float total = 0; for (int i = 0; i < list.Length; i++) { total += list[i].Weight; } float value = Random.Range(0, total); for (int i = 0; i < list.Length; i++) { value -= list[i].Weight; if (value <= 0) return list[i].Value; } return default; } // 重み付き抽選を行う(リスト用:ちょっと動作が遅い) public static T SelectOne<T>(List<LotteryItem<T>> list) { float total = 0; for (int i = 0; i < list.Count; i++) { total += list[i].Weight; } float value = Random.Range(0, total); for (int i = 0; i < list.Count; i++) { value -= list[i].Weight; if (value <= 0) return list[i].Value; } return default; } } [System.Serializable] public readonly struct LotteryItem<T> { public readonly T Value; public readonly float Weight; public LotteryItem(T value, float weight) { Value = value; Weight = weight; } }
余談ですが、この実装は seed の指定がないため起動ごとに適当な seed が使用されるので再現性が無いです。もしかするとデバッグ時に困るケースがあります。
もし、必要なら UnityEngine.Random.state をどこかにもってそれを指定する仕組みが必要です。その場合 static メソッドではなくクラスにしてインスタンスの中に Random.State を持つなど対応しましょう。