AirtestとPocoを拡張して、Componentのメソッドを直接実行する。

この記事は「[サムザップ #1 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/sumzap1)」の12月18日の記事です。
昨日の記事は@sato_tatsukiさんの「[新規プロジェクトでのScriptableObject活用事例](https://qiita.com/sato_tatsuki/items/7fc15d1f67c9a4d9b689)」でした。


[CEDEC](https://speakerdeck.com/sgeengineer/airtesttopocotoopenstfniyoruunityzhi-sumatohuonxiang-kegemufalseshi-ji-zi-dong-tesutohuan-jing-gou-zhu-tosofalseli-yong-fang-fa )でお話などもしましたが、実機デバッグの自動化にAirtest、および、Pocoというライブラリを使用しています。これはTCPでPythonコードと対話し、Unity上のゲームオブジェクトの情報を取得、その位置にクリック情報をおくる。などして自動的にゲームを進める。というものです。

※参考URL
* https://airtest.netease.com/
* https://github.com/AirtestProject/Poco-SDK/tree/master/Unity3D

とはいえ、クリックだけでは出来ないこともあったり、時にはメソッドを直接実行したい！と思うこともあるかもしれません。今回は実験的にPocoを少しだけ拡張し、Componentの命令を実行する実験をしてみましょう。



Unity側のPocoMangerに以下のコードを追加します

```c#
    rpc.addRpcMethod("ExtLeft", ExtLeft);
    rpc.addRpcMethod("ExtRight", ExtRight);

    [RPC]
    private object ExtRight(List<object> param)
    {
        var cube = GameObject.Find("Cube");
        var component = cube.GetComponent<MoveScript>();
        component.right();
        return false;
    }

    [RPC]
    private object ExtLeft(List<object> param)
    {
        var cube = GameObject.Find("Cube");
        var component = cube.GetComponent<MoveScript>();
        component.left();
        return false;
    } 
```

GameObject Cubeにはこのような命令を追加しておきます

```c#
    public void right()
    {
        this.gameObject.transform.Translate (0.05f, 0, 0);
    }
    
    public void left()
    {
        this.gameObject.transform.Translate (-0.05f, 0, 0);
    }
```


Airtest側からは以下のような命令を追加してあげます。

```python
poco.agent.rpc.call("ExtLeft").wait()
```

これで、PocoからAndroid上で動いているUnityの命令を直接叩く準備ができました。せっかくなので、Web上から叩けるサーバーを簡易的に用意してみましょう

```python3
import uvicorn as uvicorn
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
from poco.drivers.unity3d import UnityPoco

app = FastAPI()
poco = UnityPoco()
html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Ext</title>
    </head>
    <body>
        <h1>input method name</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:8000/ws");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                //input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


@app.get("/")
async def get():
    return HTMLResponse(html)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        obj = poco.agent.rpc.call(data).wait()
        await websocket.send_text(f"Message text was: {data}")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

```


これを実行すると以下の様なことが出来るようになります

![6jjwq-mldog.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222241/4e242902-17c3-789e-86f6-46e1e5ae3ea4.gif)



というわけで、Pocoを拡張する実験は成功しました。ということで、これから以下のことに取り組んでいく予定です。

* 各種命令をリフレクションで呼び出すようにし、デバッグに汎用性をもたせる。
* パラメータの動的注入を行えるようにし、ゲームのパラメータ調整などに役立ててもらう
* 自動化する際に手が届かなかったような部分に手をいれていく。

特にパラメータ注入などはゲームの細やかな調整時に威力を発揮してくれるのではないかと個人的に注力したいポイントです。ある程度成果がでたらまたQiitaなどで報告できればと思います。