空から大量にコインを降らせようとしたら爆散した話

こちらは [Unity #2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/unity2) の 1日目の記事です 🎅

# 背景

**kintone hack 2019**や**Cybozu Days 2020**で、[kintoneのアプリのデータをUnity内で可視化するプロジェクト](https://logmi.jp/business/articles/322313)を披露してきました。

このプロジェクトの中でも自分が気に入ってるのは、kintone内の案件管理データベースから直近の売上金額を集計し、**売上の分だけプレイヤーの目の前にコインを大量に降らせる**という、ファンキーでリッチなデータ可視化方法でした。

![kintonehackfallingcoins.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/e190dc63-742d-74b3-8434-6ce757b5fdb9.gif)

金だー！金だー！

実はこの大量コイン生成を実現するのに何回か失敗したので、この記事ではコインの降らし方について案内をします。なお、kintoneからデータを取得する方法については[別記事](https://qiita.com/will-yama/items/489b1f411e59bc95af26)で書き上げているので、この記事では割愛します。

# コインを生成してみた
コインをUnityの環境内に生成するのは簡単です。生成したいオブジェクトをprehabとして準備し、Instantiateすれば生成されます。Instantiateする際の角度はランダムになるように調整もできます。

```c#
Vector3 CoinPosition = new Vector3(0,3,0);
float CoinRotationX = Random.Range(0.0f,360.0f);
float CoinRotationY = Random.Range(0.0f,360.0f);
float CoinRotationZ = Random.Range(0.0f,360.0f);
Instantiate(CoinPrefab, CoinPosition, Quaternion.Euler(CoinRotationX,CoinRotationY,CoinRotationZ));
```


![onecoinfall.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/23ae6e4b-6aaf-1276-b9b0-8f04ee2bc231.gif)

コツン⌒☆*

簡単ですね。では、これを大量に降らせるようにコードをいじってみましょう。


# コインを複数生成してみた

コインを複数生成するので、単純にループ処理でInstantiateすれば良いのかなっと思ってforループで実装してみました。これでコインが連続でチャリンチャリーンっと滝のように流れてくるはずです。

```c#
int NumberOfCoins = 100;

for(int i=0; i<NumberOfCoins; i++)
{
    Vector3 CoinPosition = new Vector3(0,3,0);
    float CoinRotationX = Random.Range(0.0f,360.0f);
    float CoinRotationY = Random.Range(0.0f,360.0f);
    float CoinRotationZ = Random.Range(0.0f,360.0f);
    Instantiate(CoinPrefab, CoinPosition, Quaternion.Euler(CoinRotationX,CoinRotationY,CoinRotationZ));
}
```

![100coinfall.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/9481f13d-7230-c6e0-3277-61c047e1623f.gif)



(　ﾟдﾟ)

(つд⊂)ｺﾞｼｺﾞｼ

(；ﾟдﾟ)

(つд⊂)ｺﾞｼｺﾞｼ
　　
（；ﾟ Дﾟ） …?! what?

爆　☆　散

爆散しました・・・何が起きたんでしょうか・・・おそらく同じ場所にBox Colliderがついているオブジェクトが一瞬に生成されたことで、オブジェクト同士が反発してしまい、爆散してしまったんだと思います。最初からクライマックスですね。



# コインの生成位置をずらしてみた

ほぼ同じタイミングで特定の位置にコインが生成されるとコイン同士が反発しあって爆散するので、コインの生成位置を1つ1つずらしながら生成すれば良いのでは？という考えで実装をさらに進めてみましょう。位置をずらすけれど、だいたい同じエリアに生成させたい・・・となると螺旋状に降らせるのが良いかもしれません。

簡単な実装方法として、X軸とY軸をAdjuster変数で少しずつずらして、一周したらY軸を少しあげるようにしてみました。キレイな螺旋ではありませんが、爆散を防ぐには十分だと思います。

```c#
int NumberOfCoins = 100;

Vector3 AdjusterA = new Vector3(0.25f, 0, 0);
Vector3 AdjusterB = new Vector3(0, 0, 0.25f);
Vector3 AdjusterC = new Vector3(-0.25f, 0, 0);
Vector3 AdjusterD = new Vector3(0, 0.5f, -0.25f);

Vector3 CoinPosition = new Vector3(0,3,0);

for(int i=0; i<NumberOfCoins; i++)
{
    switch (i % 4)
    {
        case 0:
            CoinPosition = CoinPosition + AdjusterA;
            break;
        case 1:
            CoinPosition = CoinPosition + AdjusterB;
            break;
        case 2:
            CoinPosition = CoinPosition + AdjusterC;
            break;
        case 3:
            CoinPosition = CoinPosition + AdjusterD;
            break;
    }
    float CoinRotationX = Random.Range(0.0f,360.0f);
    float CoinRotationY = Random.Range(0.0f,360.0f);
    float CoinRotationZ = Random.Range(0.0f,360.0f);
    Instantiate(CoinPrefab, CoinPosition, Quaternion.Euler(CoinRotationX,CoinRotationY,CoinRotationZ));
}
```

![100coinrotation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/c800b797-4163-3470-7130-4992228cab07.gif)

ドバドバドバドバドバー⌒☆*

コインが爆散してませんし、これは中々良い感じにコインを降らせたのではないでしょうか。

# コインの生成タイミングを遅らせてみた

実は先程のコインを降らせる方法に欠点がありました。

カメラが小さな範囲しか捉えてないのであればこの方法でも良いんですが、生成するコインが増えれば増えるほどコインがタワーのように高くなってしまいます。

少しズームアウトして全体を見てみましょう：

30コインの場合↓
![30coins4secs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/56f5e689-f112-02d0-3f28-64dffc284360.gif)


100コインの場合↓
![100coins4secs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/09a8b00c-2101-2675-a6c6-23585f51eb9e.gif)


300コインの場合↓
![300coins4secs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/453042fc-fa83-e96d-2ca7-a2a69945172c.gif)


んー、これは見た目がかなり悪いですね。

4コインごとにY軸の値を増やしてるので、コインが増えたら当然その分コインのタワーは高くなります。

Y軸という「位置」をずらすことにより爆散を防いでいたのですが、こんどは生成する「時間」をずらしてみましょう。Unityでは[WaitForSeconds() 関数](https://docs.unity3d.com/ScriptReference/WaitForSeconds.html)を使用すれば、コインとコインが生成される間にディレイを設定することができ、例え同じ位置に生成されても先に落ちたコインと次に落ちるコインがぶつからないように調整することが可能です。なお、WaitForSeconds() 関数はcoroutineの中でしか使用が出来ないので、下記のように関数化して修正する必要があります。Y軸の修正も忘れずにしておきましょう。

```c#
void Start()
{
    StartCoroutine("InstantiateCoinsWithDelay");
}

private IEnumerator InstantiateCoinsWithDelay()
{
    int NumberOfCoins = 100;

    Vector3 AdjusterA = new Vector3(0.25f, 0, 0);
    Vector3 AdjusterB = new Vector3(0, 0, 0.25f);
    Vector3 AdjusterC = new Vector3(-0.25f, 0, 0);
    Vector3 AdjusterD = new Vector3(0, 0, -0.25f);

    Vector3 CoinPosition = new Vector3(0,3.5f,0);

    for(int i=0; i<NumberOfCoins; i++)
    {
        switch (i % 4)
        {
            case 0:
                CoinPosition = CoinPosition + AdjusterA;
                break;
            case 1:
                CoinPosition = CoinPosition + AdjusterB;
                break;
            case 2:
                CoinPosition = CoinPosition + AdjusterC;
                break;
            case 3:
                CoinPosition = CoinPosition + AdjusterD;
                break;
        }

        float CoinRotationX = Random.Range(0.0f,360.0f);
        float CoinRotationY = Random.Range(0.0f,360.0f);
        float CoinRotationZ = Random.Range(0.0f,360.0f);
        Instantiate(CoinPrefab, CoinPosition, Quaternion.Euler(CoinRotationX,CoinRotationY,CoinRotationZ));
        yield return new WaitForSeconds(0.1f);
    }
}
```

![coinswithdelay.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/ed5b8a1a-429d-ef43-8fd0-62fc3be0ce78.gif)

ぽこぽこぽこぽこー⌒☆*

良い感じですね。円を描きながら(いや、実際は四点を順番に回ってるだけなので四角か)、コインとコインの生成の間にディレイを入れているので、爆散せずに済んでいます。


# コインをさらに自然に生成してみた

いや、自然にコインを生成するってなんやねん ( ´Д｀)っ))Дﾟ)・∵.

っと思いながら書いてますが、ようは雨が降るようなイメージでコインを降らしてみたいんですよね。

なのでコインの人工的な螺旋状の降り方を一旦やめて、自然界での天気のように「特定のエリア内に」かつ「ランダムな場所に」かつ「ランダムな間隔で」コインを降らせようと思います。


```c#
void Start()
{
    StartCoroutine("InstantiateRandomCoins");
}

private IEnumerator InstantiateRandomCoins()
{
    int NumberOfCoins = 300;
    Vector3 CoinPosition = new Vector3(0,0,0);
    
    for(int i=0; i<NumberOfCoins; i++)
    {
        CoinPosition = new Vector3(Random.Range(0,1.6f),Random.Range(4.0f,4.8f),Random.Range(0,1.6f));
        float CoinRotationX = Random.Range(0.0f,360.0f);
        float CoinRotationY = Random.Range(0.0f,360.0f);
        float CoinRotationZ = Random.Range(0.0f,360.0f);
        Instantiate(CoinPrefab, CoinPosition, Quaternion.Euler(CoinRotationX,CoinRotationY,CoinRotationZ));
        yield return new WaitForSeconds(Random.Range(0.01f,0.07f));
    }
}
```
![coinsfromkintonecloud.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100542/d744bbcd-adb6-53f7-f739-35e0c8ffee91.gif)

雨っぽく見えるように、ついでに雲のオブジェクトも配置してみました。どうでしょうか、少しは自然界の雨のように見えますでしょうか。人工的なコインを大量に降らせてるから雨もくそもないんですけどね。アハハ。自分でも何作ってんのか良くわからんです。

「雨なんて降って・・・」

「いや」「雨だよ」

# 参考

今回の記事に使用したアセットや参考資料です

- コインのアセット: [Medieval Gold](https://assetstore.unity.com/packages/3d/props/medieval-gold-14162)
- 女の子のアセット: [Free Voxel Girl](https://assetstore.unity.com/packages/3d/characters/humanoids/free-voxel-girl-95532
)
- 雲のアセット: [3LE Low Poly Cloud Pack](https://assetstore.unity.com/packages/3d/3le-low-poly-cloud-pack-65911
)
- 参考Qiita記事1: [UnityからkintoneのREST APIを叩く方法](https://qiita.com/will-yama/items/19078a905ef4207977e4)
- 参考Qiita記事2: [kintoneアプリに溜めたゲームスコア情報をUnity内に表示させる](https://qiita.com/will-yama/items/489b1f411e59bc95af26)