16
7

More than 3 years have passed since last update.

UnityとGo言語でAPI通信する

Posted at

はじめに

ゲーム開発エンジン「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には便利なパッケージがたくさんあり、かつネットには参考になる情報が大量にあるため、それらをめっちゃ活用しました。

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

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をやってみた①

今回は、簡単にRESTfulなAPIサーバを構築することができる以下のパッケージを使っています。
Go-Json-Rest

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

    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

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

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

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に接続する

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

        yield return request.SendWebRequest();

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

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

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

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

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

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

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

client_link.png

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

client_btn.png

これで準備OKです!

動作確認

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

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

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

$ 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を叩くと、結果が全件返ってきてくれました!

$ 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

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

result_1.png

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

execute_2.png

IDが2のデータ(ソルジャー)が取得できました!

result_2.png

終わりに

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

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

参考

golangでREST APIをやってみた①
UnityでHTTPに接続する
UnityWebRequest
UnityWebRequestの使い方【Unity】
Go 言語の値レシーバとポインタレシーバ

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7