Unityでゲームのガチャシステム

## はじめに
Unityでゲームのガチャシステムを作るのに今回は重み付きの抽選方法を使っていきます。
重み付きの抽選方法を選んだ理由は、確率を合計100％にしなくてもいいので計算が楽でピックアップ対応しやすいと思ったからです。
ガチャの排出率、内容は実際のゲーム（ テイルズオブザレイズ ）のガチャの排出率、内容を参考にしています。

## 環境
　Windows10
　Unity 2019.4.11f1

## 作業
抽選を行って、コンソールにアイテム名を表示するスクリプトを作成していきます。
[サイト](https://ekulabo.com/random-with-weight)を参考にしながら作業を進めます。

まずは、レアリティ別のガチャを作ります。
### 状況設定
|　　　レア度　　  |　確率(10連 1/10回)　|　確率(1連、10連 9/10回)　|
|:-:|:-:|:-:|
|星6魔境|5%|5%|
|星5鏡技|6%|6%|
|星4装備|89%|15%|
|星3装備|0%|74%|

レアリティ別の排出率をスクリプトで読み込めるようにテキストにまとめます。
```:RarityData.txt
74,15,6,5
0,89,6,5
```
RarityDataは、
上の行が確率(1連、10連 9/10回)で下の行が確率(10連 1/10回)<rb>
左から ☆3 , ☆4 , ☆5 , ☆6 の排出率(%)になっています。<rb>

```cs:Lottery.cs
    [SerializeField]
    TextAsset RarityData = default;

    float[,] RarityInfo;
    int rollNum = 0;
    int lotteryType;//0=1連,1=10連(1/10回)

    string[] raw;
    string[] column;

    void Start()
    {
        //textの読み込み
        raw = RarityData.text.Split('\n');
        column = raw[0].Split(',');
        RarityInfo = new float[column.Length,raw.Length];
        for (int i = 0; i < raw.Length; i++)
        {
            column = raw[i].Split(',');
            for (int j = 0; j < column.Length; j++)
                RarityInfo[j, i] = float.Parse(column[j]);
        }
    }

    void Update()
    {
        for(int i=0;i<rollNum;i++)
        {
            if (i == 9)//最後の1回
                lotteryType = 1;
            else
                lotteryType = 0;
            GetDropItem();
        }
        rollNum = 0;
    }

    void GetDropItem()
    {
        //レア度の抽選
        int itemRarity = ChooseRarity() + 3;

        Debug.Log(itemRarity);
    }

    int ChooseRarity()
    {
        //確率の合計値を格納
        float total = 0;

        //確率を合計する
        for (int i = 0; i < RarityInfo.GetLength(0); i++)
                total += RarityInfo[i,lotteryType];

        //Random.valueでは0から1までのfloat値を返すので
        //そこにドロップ率の合計を掛ける
        float randomPoint = Random.value * total;

        //randomPointの位置に該当するキーを返す
        for(int i = 0;i< RarityInfo.GetLength(0); i++)
        {
            if (randomPoint < RarityInfo[i, lotteryType])
            {
                return i;
            }
            else
            {
                randomPoint -= RarityInfo[i, lotteryType];
            }
        }
        return 0;
    }

    //ボタン用
    public void lotteryTypeOne()
    {
        rollNum = 1;
    }
    public void lotteryTypeTen()
    {
        rollNum = 10;
    }
```
InspectorのRarityDataの欄にRarityData.txtをドラッグアンドドロップします。
ボタンを2つ作り、それぞれに lotteryTypeOne と lotteryTypeTen を設定します。

### 結果
確率(1連、10連 9/10回)を10000回
![Debug1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/9f854c38-5888-5656-7ae8-9812e4365ba2.png)
確率(10連 1/10回)を10000回
![Debug2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/c9ae1dc0-8ee5-948e-9929-0c231ba72b4f.png)
概ね設定した確率通りになりました。
確率(1連、10連 9/10回)と確率(10連 1/10回)をあわせて10000回
![Debug3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/1cde22f4-c3a2-07c3-ea60-87dbe9981d10.png)
確率(1連、10連 9/10回)より 3 が減って 4 , 5 , 6 が増えました。

これでレアリティ別のガチャが出来ました。
次に、レアリティに応じたアイテムの中からさらに抽選していきます。


### 状況設定
|　レア度　|　名前　|　確率　||　レア度　|　名前　|　確率　|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|星6魔境|空を羽ばたいて|1.000||星4装備|クリスタルミラー|2.500|
|星6魔境|不器用に舞う剣|1.000||星4装備|龍眼鏡|2.500|
|星6魔境|我が道を行く狙撃者|1.000||星4装備|ミスティシェイプ|2.500|
|星6魔境|浜辺のエンジェル|0.500||星4装備|ツヴァイチャクラム|2.500|
|星6魔境|EX-clipse ボーカル|0.500||星4装備|アヌビス|2.500|
|星6魔境|君と歩く浜辺|0.500||星4装備|ミルキーウェイ|2.500|
|星6魔境|絵馬で願掛け|0.500||星3装備|シルバーミラー|4.933|
|星5鏡技|天然剣士 コレット|1.250||星3装備|レフリフレクト|4.933|
|星5鏡技|狙い鋭いスナイパー リタ|1.250||星3装備|光鏡|4.933|
|星5鏡技|哀傷の鏡士 ミリーナ|0.500||星3装備|鬼灯|4.933|
|星5鏡技|「想像」の使い手 ミリーナ|0.500||星3装備|ロイヤルミラー|4.933|
|星5鏡技|新たなる歩み ミリーナ|0.500||星3装備|シェルフレーム|4.933|
|星5鏡技|天使化する身体 コレット|0.500||星3装備|トーロイド|4.933|
|星5鏡技|爽涼スプラッシュ コレット|0.500||星3装備|スティングソーサー|4.933|
|星5鏡技|魔導器とともに リタ|0.500||星3装備|サイケデリカ|4.933|
|星5鏡技|白花の髪飾り リタ|0.500||星3装備|エヴァーブルー|4.933|
|||||星3装備|パーフェクトグリーン|4.933|
|||||星3装備|カクゴノキロク|4.933|
|||||星3装備|マフラー|4.933|

アイテム別の排出率をテキストにまとめます。
```:ItemData.txt
6,空を羽ばたいて,1.000
6,不器用に舞う剣,1.000
6,我が道を行く狙撃者,1.000
6,浜辺のエンジェル,0.500
6,EX-clipse ボーカル,0.500
6,君と歩く浜辺,0.500
6,絵馬で願掛け,0.500
5,天然剣士 コレット,1.250
5,狙い鋭いスナイパー リタ,1.250
5,哀傷の鏡士 ミリーナ,0.500
5,「想像」の使い手 ミリーナ,0.500
5,新たなる歩み ミリーナ,0.500
5,天使化する身体 コレット,0.500
5,爽涼スプラッシュ コレット,0.500
5,魔導器とともに リタ,0.500
5,白花の髪飾り リタ,0.500
4,クリスタルミラー,2.500
4,龍眼鏡,2.500
4,ミスティシェイプ,2.500
4,ツヴァイチャクラム,2.500
4,アヌビス,2.500
4,ミルキーウェイ,2.500
3,シルバーミラー,4.933
3,レフリフレクト,4.933
3,光鏡,4.933
3,鬼灯,4.933
3,ロイヤルミラー,4.933
3,シェルフレーム,4.933
3,トーロイド,4.933
3,スティングソーサー,4.933
3,サイケデリカ,4.933
3,エヴァーブルー,4.933
3,パーフェクトグリーン,4.933
3,カクゴノキロク,4.933
3,マフラー,4.933
```
ItemDataは、
左から レア度 , 名前 , 排出率(%) になっています。
排出率をほかの同じレアリティのアイテムより高くするとピックアップになります。

以下のようにスクリプトに変更を加えます。

```cs:Lottery.cs
    [SerializeField]
    TextAsset RarityData = default;
    [SerializeField]
    TextAsset itemDeta = default;

    float[,] RarityInfo;
    string[,] itemInfo;
    int rollNum = 0;
    int lotteryType;//0=1連,1=10連(1/10回)

    string[] raw;
    string[] column;

    enum Item
    {
        Rarity,
        Name,
        Rate,
    }

    void Start()
    {
        //textの読み込み
        raw = RarityData.text.Split('\n');
        column = raw[0].Split(',');
        RarityInfo = new float[column.Length, raw.Length];
        for (int i = 0; i < raw.Length; i++)
        {
            column = raw[i].Split(',');
            for (int j = 0; j < column.Length; j++)
                RarityInfo[j, i] = float.Parse(column[j]);
        }
        raw = itemDeta.text.Split(char.Parse("\n"));
        column = raw[0].Split(char.Parse(","));
        itemInfo = new string[column.Length, raw.Length];
        for (int i = 0; i < raw.Length; i++)
        {
            column = raw[i].Split(char.Parse(","));
            for (int j = 0; j < column.Length; j++)
                itemInfo[j, i] = column[j];
        }
    }

    void Update()
    {
        for (int i = 0; i < rollNum; i++)
        {
            if (i % 10 == 9)//最後の1回
                lotteryType = 1;
            else
                lotteryType = 0;

            GetDropItem();
        }
        rollNum = 0;
    }

    void GetDropItem()
    {
        //レア度の抽選
        int itemRarity = ChooseRarity() + 3;

        //レア度に応じたアイテムの抽選
        int itemId = ChooseItem(itemRarity);

        Debug.Log(itemInfo[(int)Item.Name, itemId]);
    }

    int ChooseRarity()
    {
        //確率の合計値を格納
        float total = 0;

        //確率を合計する
        for (int i = 0; i < RarityInfo.GetLength(0); i++)
            total += RarityInfo[i, lotteryType];

        //Random.valueでは0から1までのfloat値を返すので
        //そこにドロップ率の合計を掛ける
        float randomPoint = Random.value * total;

        //randomPointの位置に該当するキーを返す
        for (int i = 0; i < RarityInfo.GetLength(0); i++)
        {
            if (randomPoint < RarityInfo[i, lotteryType])
            {
                return i;
            }
            else
            {
                randomPoint -= RarityInfo[i, lotteryType];
            }
        }
        return 0;
    }
    int ChooseItem(int rarity)
    {
        float total = 0;

        for (int i = 0; i < itemInfo.GetLength(1); i++)
            if (int.Parse(itemInfo[(int)Item.Rarity, i]).Equals(rarity))
                total += float.Parse(itemInfo[(int)Item.Rate, i]);

        float randomPoint = Random.value * total;

        for (int i = 0; i < itemInfo.GetLength(1); i++)
        {
            if (int.Parse(itemInfo[(int)Item.Rarity, i]).Equals(rarity))
            {
                if (randomPoint < float.Parse(itemInfo[(int)Item.Rate, i]))
                {
                    return i;
                }
                else
                {
                    randomPoint -= float.Parse(itemInfo[(int)Item.Rate, i]);
                }
            }
        }
        return 0;
    }

    //ボタン用
    public void lotteryTypeOne()
    {
        rollNum = 1;
    }
    public void lotteryTypeTen()
    {
        rollNum = 10;
    }
```
InspectorのItemDetaの欄にItemData.txtをドラッグアンドドロップします。

### 実行結果
10連を4回まわしてみました。
![Debug4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/3aa184f0-ab87-929a-cb59-2ca5b2d407b4.png)![Debug5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/8a89cab8-e7ee-eec9-e47c-ef498bc76abd.png)
![Debug6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/216eef96-2790-e2ce-1b96-7f8a3ee9e6f1.png)![Debug7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634649/4b51cbc5-4bfb-c99a-943d-5e14629b5f65.png)


**☆6＝1，☆5＝2，☆4＝9，☆3＝28　という結果になりました。**
☆6が出ました。やったー

## まとめ
これでピックアップにも対応したゲームのガチャシステムができました。
参考にしたゲームと似たような結果になったと思います。

## 参考
https://ekulabo.com/random-with-weight