AIテニスプレイヤーとVRで試合しよう(MlAgents)-2

前回記事の続きになります

https://qiita.com/SEKI211/items/11e7bf27855d97b44166

MlAgentを用いたテニスのAI学習において報酬値の設定について解説します(MlAgent Pluginのサンプルシーンの解説になります)

# 報酬値の設定概要
報酬を設定するコンポーネントはballにアタッチします
ボールがラケット、壁に当たることが報酬に直結するからです



# Header
TennisAgent型変数m_AgentA,BにラケットA,BのTennisAgentsコンポーネントを入れます。→ これにより報酬値,スコアをラケットに入れることができます。
列挙型変数FloorHitに最後にballが触れた床(かサービスショット)を記録します。
int型(bool型で良い気がする) lastAgentHit　に最後に触れたラケット格納　0 → A ,1 → B


```Header.cs
    public GameObject areaObject;//Agent A,Bのスクリプト取得のため
    public int lastAgentHit;//0はA　1はB 　なぜboolにしない？
    public bool net;//サービスショットでネットに当たったか

    //
    public enum FloorHit
    {
        Service,
        FloorHitUnset,
        FloorAHit,
        FloorBHit
    }

    public FloorHit lastFloorHit;

    TennisArea m_Area;
    TennisAgent m_AgentA;//エピソードの開始終了を操作する
    TennisAgent m_AgentB;//エピソードの開始終了を操作する
    
    //ラケット内の関数を呼び出せるようにする
    void Start()
    {
        m_Area = areaObject.GetComponent<TennisArea>();
        m_AgentA = m_Area.agentA.GetComponent<TennisAgent>();
        m_AgentB = m_Area.agentB.GetComponent<TennisAgent>();
    }
```

# 報酬設定
Reset() :エピソードを終了する　
AgentAWins()　
ラケットAに報酬1を与えラケットBにペナルティ-1与え、スコア加算しエピソード終了
AgentBWins()　
ラケットBに報酬1を与えラケットAにペナルティ-1与え、スコア加算しエピソード終了

```AgentWin.cs
    //得点が入ったもしくは　目的を達成しなかった場合エピソードの強制終了
    void Reset()
    {
        m_AgentA.EndEpisode();
        m_AgentB.EndEpisode();
        m_Area.MatchReset();
        lastFloorHit = FloorHit.Service;
        net = false;
    }
    //ラケットAに得点が入ったとき　Aに報酬与えBにペナルティ与える　その後エピソード終了
    void AgentAWins()
    {
        m_AgentA.SetReward(1);
        m_AgentB.SetReward(-1);
        m_AgentA.score += 1;
        Reset();

    }
    //ラケットBに得点が入ったとき　Aに報酬与えBにペナルティ与える　その後エピソード終了
    void AgentBWins()
    {
        m_AgentA.SetReward(-1);
        m_AgentB.SetReward(1);
        m_AgentB.score += 1;
        Reset();

    }

```

# 場外に球が出た時
Aの壁に球が当たったとき(場外)の勝敗判定
最後に触ったのがラケットA自身　→　Bの勝ち
最後に触ったのがBでAのエリアでワンバンした　→　Bの勝ち
それ以外（ノーバウンドでBの球が場外に出た時）→　Aの勝ち

```AgentWin.cs
    void OnCollisionEnter(Collision collision)
    {
        //壁,床に当たったとき (iWallは壁,床,ネットのTag)
        if (collision.gameObject.CompareTag("iWall"))
        {
            //Aの壁に当たったとき
            if (collision.gameObject.name == "wallA")
            {
                //Aが打った球がAの壁に当たった　又は　Bが打った球がAのエリアで一回バウンドした　Bの勝ち
                if (lastAgentHit == 0 || lastFloorHit == FloorHit.FloorAHit)
                {
                    //報酬,ペナルティ与えてエピソード終了
                    AgentBWins();
                }
                //ノーバウンドでBがAのエリア外に出したとき　Aの勝ち
                else
                {
                    //報酬,ペナルティ与えてエピソード終了
                    AgentAWins();
                }
            }
        }
    }
```

# 自陣でパウンドしたとき
ラケットAが打った球が自陣でワンバウンドしたとき　
ラケットBからの球が自陣で二回バウンドしたとき
サービスショットが打てず(ラケットに触れず)自陣でバウンドしたとき
→　ラケットBの勝ち

最後に当たった床を更新　
サービスショットが成功したときnetをtrueに更新
```AgentWin.cs
void OnCollisionEnter(Collision collision)
    {
        
        else if (collision.gameObject.name == "floorA")
        {　
             //自身が打った球が自陣でワンバウンドしたとき　相手からの球が自陣で二回バウンドしたとき　サービスショットが自陣でバウンドしたとき
            if (lastAgentHit == 0 || lastFloorHit == FloorHit.FloorAHit || lastFloorHit == FloorHit.Service)
                {
                    AgentBWins();
                }
                //球を打ち返えせた時　サービスショットが成功したとき
                else
                {
                    //最後に当たった床を更新　service, B → A
                    lastFloorHit = FloorHit.FloorAHit;
                    //ネットに引っかからず通過した判定（サービスショット成功したか否か）
                    if (!net)
                    {
                        net = true;
                    }
                }
            }
        }
    

```

# サービスショット失敗
Aがサービスショット時にネットに引っかかった　→　Bの勝ち
Bがサービスショット時にネットに引っかかった　→　Aの勝ち

```net.cs
    else if (collision.gameObject.name == "net" && !net)
    {
        //サービスショット失敗した人判別
        if (lastAgentHit == 0)
        {
            AgentBWins();
        }
        else if (lastAgentHit == 1)
        {
            AgentAWins();
        }
    }
```

# ラケットに触れた時
Aのラケットに連続して当たったとき　→　Ｂの勝ち

```net.cs
    else if (collision.gameObject.name == "AgentA")
            {
                //ラケットに2回当たったとき
                if (lastAgentHit == 0)
                {
                    AgentBWins();
                }
                else
                {
                    //agent can return serve in the air
                    if (lastFloorHit != FloorHit.Service && !net)
                    {
                        net = true;
                    }
                //最後に触れたのがAと記録
                lastAgentHit = 0;
                //サービスショット,floorB → floorHitUnsetに変更(最後にラケットAかBに触れたこと記憶)
                lastFloorHit = FloorHit.FloorHitUnset;
                }
            }
```
# まとめ
報酬を与えるパターンとして
**自身依存**
自身が打った球が相手エリアでバウンドし場外に出た　→　+1
相手の球を1バウンドで返せなかった　→　-1
サービスショットの球に触れず地面と接触した　→　-1
自身の球がネットに引っかかった　→　-1
球がラケットに二回触れた　→　-1
**相手依存**
相手が打った球が相手エリアの場外に出た　→　+1
相手が打った球が相手エリアでバウンドした　→　+1

得点のみが報酬につながっている
ボールの速度、ラケットとボールの距離などによる報酬は考慮されていない
動きはx,y及びラケットのz軸の回転のみ


# 今後
ラケットの動きにZ方向を追加(横方向)
ラケットの回転にY軸を追加
続きは以下の記事

https://qiita.com/SEKI211/items/4430f8b2d04ea9112462