UnityとGo言語でAPI通信する

# はじめに
ゲーム開発エンジン「Unity」と、Googleにより開発されたプログラミング言語「Go」を用いて、凄くシンプルなREST APIを実装したいと思います。

Unityについて：https://unity.com/ja
Goについて：https://golang.org/

# 開発環境
- MacBook Pro (15-inch, 2018)
- macOS Catalina
- Unity 2019.1.1f1 Personal
- Go 1.13.5 darwin/amd64

# システム構造
Unityをクライアントサイド、APIサーバ（Goで実装）をサーバサイドとして作りました。

# 内容
サーバサイドで保存されているデータに対して、Unity側からデータのID（一意なもの）を指定して対象データを取得します。データに関しては、予めいくつか用意しておきます。

今回はDBを使わないので、オンメモリのデータストア（リストを使います）に格納しておこうと思います。なので、サーバを停止させるとデータは吹き飛びますw

# 実装
## サーバサイド
まずサーバサイドから実装していきます。
といっても、Goには便利なパッケージがたくさんあり、かつネットには参考になる情報が大量にあるため、それらをめっちゃ活用しました。

コードは以下の通りです。

```go
package main

import (
	"log"
	"net/http"
	"strconv"

	"github.com/ant0ine/go-json-rest/rest"
)

type Monster struct {
	ID   int
	Name string
}

// オンメモリのデータストア.
var dataStore = map[int]*Monster{}

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)

	router, err := rest.MakeRouter(
		rest.Get("/getAllData", GetAllData),
		rest.Post("/postData", PostData),
		rest.Get("/getData/:id", GetData),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Printf("Server Started.")

	// APIサーバを起動.
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

// データを新しく作成する.
func PostData(w rest.ResponseWriter, r *rest.Request) {
	monster := Monster{}
	err := r.DecodeJsonPayload(&monster)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	dataStore[monster.ID] = &monster

	w.WriteJson(&monster)
}

// 指定IDのデータを取得する.
func GetData(w rest.ResponseWriter, r *rest.Request) {
	id, _ := strconv.Atoi(r.PathParam("id"))

	var monster *Monster
	if dataStore[id] != nil {
		monster = &Monster{}
		*monster = *dataStore[id]
	}

	if monster == nil {
		rest.NotFound(w, r)
		return
	}

	w.WriteJson(monster)
}

// 全データを取得する.
func GetAllData(w rest.ResponseWriter, r *rest.Request) {
	allData := make([]Monster, len(dataStore))

	i := 0
	for _, data := range dataStore {
		allData[i] = *data
		i++
	}
	w.WriteJson(&allData)
}
```

こちらのコードを作成するために、以下の資料を大変参考にさせていただきました！
[golangでREST APIをやってみた①](https://qiita.com/katekichi/items/d94e078b376151858ca4)

今回は、簡単にRESTfulなAPIサーバを構築することができる以下のパッケージを使っています。
[Go-Json-Rest](https://github.com/ant0ine/go-json-rest)

コードに関して大事なところは、以下の箇所かと思います。

```go
    router, err := rest.MakeRouter(
        rest.Get("/getAllData", GetAllData),
        rest.Post("/postData", PostData),
        rest.Get("/getData/:id", GetData),
    )
```
ここでは、`go-json-rest`のrestパッケージに実装されている`MakeRouter`を使って、ルーティングパスを3つ設定しています。それぞれ以下のハンドラと結びつきます。

- `/getAllData`
    - GetAllData : データストアに格納されている全データを取得するハンドラ.
- `/postData`
    - PostData : データストアに新しくデータを格納するハンドラ.
- `/getData/:id`
    - GetData : 指定idを持つデータを取得するハンドラ.

今後新しくルーティングを作成していきたい場合は、`MakeRouter`にHTTPリクエストメソッドに対して、ルーティングパスとハンドラを結び付けて定義してあげればいいということですね。

## クライアントサイド
サーバサイドに対して処理を要求するクライアントサイドの実装を行います。
クライアントはUnityを使うので、最初はEditorで以下の画面を作成しました。

![unity_editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/dfc08ef6-e071-ad28-13d1-5b4a3e0f8a7e.png)

`Hierarchy`ビューを見ていただくと、`InputArea`と`OutputArea`というオブジェクトがあります。これらはそれぞれ、`Game`ビューにおける下側の要素と上側の要素を指しています。

また、とても重要なのが`Hierarchy`ビューの`Client`オブジェクトです。これはEmpty Objectなのですが、以下のスクリプトがアタッチされています。

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class ApiClient : MonoBehaviour
{
    public InputField inputField;
    public Text outputText;
    public Image monsterImage;

    private string baseURL = "http://127.0.0.1:8080";

    public void GetDataFromAPIServer()
    {
        StartCoroutine(GetData());
    }

    IEnumerator GetData()
    {
        string url = baseURL + "/getData/" + int.Parse(inputField.text);
        UnityWebRequest request = UnityWebRequest.Get(url);
        yield return request.SendWebRequest();

        if (request.isNetworkError)
        {
            Debug.Log(request.error);
        }
        else
        {
            if(request.responseCode == 200)
            {
                string rawData = request.downloadHandler.text;
                MonsterData monsterData = JsonUtility.FromJson<MonsterData>(rawData);

                string imgPath = "monster_" + monsterData.ID;

                outputText.text = monsterData.Name;
                monsterImage.sprite = Resources.Load<Sprite>(imgPath);
            }
        }
    }
}
```

上記コードは以下の資料を参考にさせていただきました！
[UnityでHTTPに接続する](https://qiita.com/ponchan/items/65aeb43e8fea8da0bcac)

このスクリプトでは、`GetDataFromAPIServer()`メソッドが実行されると`GetData()`が動きます。
`GetData()`内で、リクエスト対象のURLを作成後、以下の箇所でサーバ側への送信とレスポンス受付けを行います。

```cs
        yield return request.SendWebRequest();
```

レスポンスのステータスコードが200ならば、返ってきたJSONデータ（string型）をプログラム内の変数に格納します。
その後、JSONデータをパースして対応するクラスインスタンスに情報を入れていきます。以下の箇所です。

```cs
                MonsterData monsterData = JsonUtility.FromJson<MonsterData>(rawData);
```

この`MonsterData`クラスですが、以下のようにしました。データを一意に保つための`ID`と、データの名前`Name`を持ちます。なので返却されるJSONは、この構造を持っている必要があります。

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class MonsterData
{
    public int ID;
    public string Name;
}
```

上のソースコード（`ApiClient`クラスのほう）を`Client`オブジェクトにアタッチしたのが、以下の状態です。

![client_set.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/0ff45e06-9eee-3c96-8fd9-3f6c008e42a3.png)

いくつか`Inspector`ビューから紐づけているコンポーネントがあります。

- `InputField`
    - クライアント側から指定するIDを入力するフィールド.
- `OutputText`
    - サーバ側から返ってきたデータの`Name`変数を格納する.
- `MonsterImage`
    - 取得データのIDを使ってクライアント側に置いてある対応する画像をセットする.

これらは以下のように紐づけています。

![client_link.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/48a3ca6d-a143-f822-a461-94b4819a3732.png)

最後に、今回はGUIのボタンが押されたらサーバ側に処理を依頼するようにしようと思ったので、`Hierarchy`ビュー上に置かれているボタンオブジェクトに対して、ClientオブジェクトにアタッチしたApiClientクラスが持つ`GetDataFromAPIServer()`メソッドを割り当てています。

![client_btn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/26d3a5d0-e20f-7c8e-e3b0-4fe966c594e4.png)

これで準備OKです！

# 動作確認
まずAPIサーバを立てておきます。以下の状態で待機します。

```zsh
$ go run server.go
2019/12/15 23:25:10 Server Started.
```

ターミナルで別タブを開いて、いくつかデータを格納（POST）しておきます。

```zsh
$ curl -i -H "Content-Type: application/json" \
-d '{"ID": 1, "Name": "スライム"}' http://127.0.0.1:8080/postData
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: xxxxx
Content-Length: 39

{
  "ID": 1,
  "Name": "スライム"
}%

$ curl -i -H "Content-Type: application/json" \
-d '{"ID": 2, "Name": "ソルジャー"}' http://127.0.0.1:8080/postData
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: yyyyy
Content-Length: 42

{
  "ID": 2,
  "Name": "ソルジャー"
}%
```

以下のREST APIを叩くと、結果が全件返ってきてくれました！

```zsh
$ curl -i http://127.0.0.1:8080/getAllData
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: xxxxx
Content-Length: 103

[
  {
    "ID": 1,
    "Name": "スライム"
  },
  {
    "ID": 2,
    "Name": "ソルジャー"
  }
]%
```

次にクライアントサイドを起動して動作をみてみます。
まず、IDが`1`のデータを取得してみます。（格納しておいたデータでいうと、スライムが該当します）

以下のように入力して「データを取得」を押すと、、、

![execute_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/ffd831cc-7d3a-9949-7f89-9f7a6f8894d9.png)

無事データを取得して、画面に反映されました！（画像データはあらかじめ自分で作ったものを参照してますw）

![result_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/59e298e9-9b8b-1af6-88e5-abc14f552116.png)

同じように、IDを`2`にしてやってみます。以下のようにして「データを取得」を押すと、、、

![execute_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/c4ab9047-fc6f-c5e8-75d6-93234704297e.png)

IDが2のデータ（ソルジャー）が取得できました！

![result_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386899/2e0fb479-238c-b33c-9d97-b620eed8c1d6.png)

# 終わりに
今回、UnityとGoを用いて簡単なAPI通信を実装してみました。
今後は今回学んだことも踏まえながら、より発展的な内容にも取り組んでいけたらなと思っています！

ありがとうございました！

# 参考
[golangでREST APIをやってみた①](https://qiita.com/katekichi/items/d94e078b376151858ca4)
[UnityでHTTPに接続する](https://qiita.com/ponchan/items/65aeb43e8fea8da0bcac)
[UnityWebRequest](https://docs.unity3d.com/ja/2019.1/Manual/UnityWebRequest.html)
[UnityWebRequestの使い方【Unity】](http://kan-kikuchi.hatenablog.com/entry/UnityWebRequest)
[Go 言語の値レシーバとポインタレシーバ](https://skatsuta.github.io/2015/12/29/value-receiver-pointer-receiver/)