Unity ML-Agentsで強化学習できる2Dシューティングゲームをつくってみた

## 概要

Unity ML-Agentsで強化学習させるのに、これまでサンプルを利用していたのですが、Agentや報酬をどうやって設定すればよいのか、いまいち理解できてなかったので、自分でゲームを作ってみました。

ちなみにゲーム開発経験はありません。
なので、ベースとなるゲームについては下記チュートリアルをほぼそのまま参考にさせていただきました。感謝！

【Unity入門】60分で作るシューティングゲーム　第１回
http://nn-hokuson.hatenablog.com/entry/2016/07/04/213231

実際に強化学習させている様子です。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">2DシューティングをUnity ML-Agentsで強化学習させてみた。<br>いい感じに動くようになった！ <a href="https://t.co/b9IdZB32bK">pic.twitter.com/b9IdZB32bK</a></p>&mdash; 甲斐甲@GCPとUnity勉強中 (@k_aik_ou) <a href="https://twitter.com/k_aik_ou/status/1030279378125475840?ref_src=twsrc%5Etfw">2018年8月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 仕様

ゲームの仕様です。まだ実装できてないものもあります。
強化学習をさせてながめてて面白くなりそうな仕様にしました。

- 2D
- シューティング
- ゲームオーバーなし
- 自機からミサイルたくさん発射される
- 自機は左右に移動できる
- 自機が移動すると勝手にミサイルが発射される
- 隕石的なものがランダムに落ちてくる
- 隕石は大きさと質量がランダム
- 隕石にミサイルが当たると破壊できる
- 隕石の質量によって破壊に必要なミサイル数が変わる
- 隕石にミサイルが当たると得点が入る
- 自機に隕石が当たるとマイナス得点が入る
- 獲得した得点によって隕石の量が増える
- 獲得した得点によってミサイルの発射頻度が変わる
- 得点が画面上部に表示される
- 得点はリセットされず、維持される
- 得点の桁数が増えると画面表示量が増えて邪魔になる


## 手順

GitHubに作成したゲームを置いてます。READMEを参照ください。(なげやり

https://github.com/kai-kou/endless


## 強化学習させるのにしたこと

本題です。ゲームを作成するところまでは良かったのですが、Unity ML-Agentsで強化学習させるのに、試行錯誤したので、ポイントをまとめてみます。
Unity ML-Agentsのバージョンは0.4.0になります。

### オブジェクトの配置とスクリプト

![](https://i.imgur.com/VlrzfEb.png)

AcademyオブジェクトとBrainは親子関係になるようにGameObjectを新規作成して配置します。
Brainにはml-agents/unity-environment/Assets/ML-Agents/Scripts/Brain.csを紐つけます。

Academyにはml-agents/unity-environment/Assets/ML-Agents/Examples/Template/Scripts/TemplateAcademy.csをコピペしてファイル名とクラス名を変更したコードを紐つけます。

RocketオブジェクトにはチュートリアルだとRocketControllerスクリプトを用意して紐付けれているけれど、これをml-agents/unity-environment/Assets/ML-Agents/Examples/Template/Scripts/TemplateAgent.csをコピペして紐つけなおします。


### 状態取得

下記記事にある```RayPerception``` を利用して、隕石の位置を検知しようとしましたが、2Dには対応していませんでした。

【Unity ML-Agents】強化学習で物体を避ける
https://qiita.com/God_KonaBanana/items/e56fcee3ae544be7dfca

なので、泥臭くロケットと隕石の位置を自前で取得して```AddVectorObs``` で設定しています。

状態数は固定である必要があるので、最後に足りない分を0で埋めてます。うーん泥臭い。
隕石オブジェクトを取得するにはオブジェクトにTagを設定しています。

```C#:RocketAgent.cs
    public override void CollectObservations()
    {
        AddVectorObs(transform.position.x);
        AddVectorObs(transform.position.y);
        var rockCount = 1;
        foreach (var rock in GameObject.FindGameObjectsWithTag("Rock"))
        {
            AddVectorObs(rock.transform.position.x);
            AddVectorObs(rock.transform.position.y);
            AddVectorObs(rock.GetComponent<RockController>().endurance);
            
            if (rockCount++ >= 20)
            {
                break;
            }
        }
        for (int i = rockCount;i <= 20;i++)
        {
            AddVectorObs(0f);
            AddVectorObs(0f);
            AddVectorObs(0f);
        }
    }

```

### 報酬設定

RocketAgent内で報酬設定するには```AddReward``` をそのまま呼べるのですが、隕石を破壊した際にも報酬を設定したい。となると玉オブジェクトに実装があるので、どうしようと考えて、以下のようにRocketAgentsにpublicなメソッドを追加して対応しました。

```C#:RocketAgent.cs
    public void Rocket_AddReward(float reward)
    {
        AddReward(reward);
        if (reward < 0)
        {
            Done();
        }
    }
```

```C#:BulletController.cs
	GameObject.Find("Rocket").GetComponent<RocketAgent>().Rocket_AddReward(3f);
```

### スピード設定

Academyオブジェクトで学習時のスピードを指定できますが、なにも対応していないと、隕石の発生頻度はあがりつつも、落下スピードはそのままで、超大量の隕石が発生。。。とか挙動が面白くなります。

なので、速度設定している箇所に```Time.timeScale``` を追加して対応しました。

```C#:RockController.cs
	void Start () {
		this.fallSpeed = (0.01f + 0.01f * Random.value) * Time.timeScale;
		this.rotSpeed = (5f + 3f * Random.value) * Time.timeScale;
		this.endurance = 1f + Random.value * 5f;
	}
```


というわけで、強化学習させるための2Dシューティングゲームを作ることができました。
ただ、学習をさせてみると、まだ報酬設定やハイパーパラメータの設定がいまいちなようで、まだまだ詰めが必要な状況です。
興味がある方はぜひここからチューニングしてみてくださいね^^