Unityで使うランキング表示用サーバーを作った話

どうも洋梨🍐です。
ゲームによくある「スコアランキング」ですが、この機能を実装していくまでの話を簡単に書いていきます。
[こちら](https://younaship.com/2019/04/28/node-jsmysql%E3%81%A7%E3%83%A9%E3%83%B3%E3%82%AD%E3%83%B3%E3%82%B0%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%BE%E3%81%A71/)も是非ご覧ください。

#環境
テスト環境として**XAMPP**
データーベースは**MySQL** (MariaDB)
サーバーは**Node.js**
フロント側は**Unity**で行っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/fcb9c039-4429-05fd-a56c-4b7773d38573.png)

#データーベースを構築する
今回構築するデーターベースは

id(プレイヤー のID) ,name (プレイヤー の名前) ,score (プレイヤーの点数) ,date(記録した日時)

を保存するものとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/e278dbc9-0eea-746e-8687-c71960a788e6.png)
まずデーターベースを構築します。

今回はテストとしてデーターをいくつか追加しておきます。なお、テーブル名は**”RANK“**としてます。

##Node.jsでサーバーを構築
今回フレームワークとして **Express** を使用します。

なお、サーバーはポート番号:3000で待ち受け、 localhost:3000/Getにアクセスされ、データーの一覧をjson形式で返すものとします。

```js:main.js
const exp = require("express");
const app = exp();

app.listen(3000,function(){
    console.log("Start Server.");
})

const mysql = require('mysql');

app.get("/Get",function(req,res,nex){

    let con = mysql.createConnection({
        host : 'localhost',
        user : 'root',
        port : 3306,
        database : 'test'
    });
    con.query("SELECT * FROM RANK",function(e,r){
        console.log(r);
        response(res,"OK",r);
    })
})

function response(res,result,data){
    let resJson = {status:result,data:data}
    res.json(resJson);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/78c380d4-0d46-45eb-4d08-495548a791fc.png)
実行した後、ブラウザなどでアクセスすると動いているのを確認できると思います。

#Unityでサーバから情報を受け取る
まずはUnity上でサーバーから受信できるかをテストします。

画面のGETボタンをクリックしたら受信をするようにします。

```C#:Test.cs
    public void OnPushGET()
    {
        var req = WebRequest.Create("http://localhost:3000/Get");
        var res = req.GetResponse();

        using (Stream stm = res.GetResponseStream())
        using(StreamReader str = new StreamReader(stm))
        {
            string json = str.ReadToEnd();
            Debug.Log(json);
        }
    }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/fe023396-53ed-924a-c72c-dfed9928268c.png)

取得できている場合、ログに出力されると思います。

##デシリアライズし、使える形式にする

```C#:JsonData.cs
[Serializable]
public class Json
{
    public string status;
    public Data[] data;
}

[Serializable]
public class Data
{
    public string id;
    public string name;
    public int score;
    public string date;
}
```
シリアライズに使うクラス(構造)はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/332ca227-54ab-d3a0-dfbf-a66fd35a6e44.png)
サーバーからのリスポンス(JSON)はこのようなデーターです

```C#:Test.cs
    public void OnPushGET()
    {
        var req = WebRequest.Create("http://localhost:3000/Get");
        var res = req.GetResponse();

        using (Stream stm = res.GetResponseStream())
        using(StreamReader str = new StreamReader(stm))
        {
            string json = str.ReadToEnd();
            Debug.Log(json);

            Json j = JsonUtility.FromJson&lt;Json>(json);

            string s = "結果\n";
            foreach (Data data in j.data) s += "[" + data.name + "] Score:" + data.score + "\n";
            text.text = s;
        }
    }
```
先ほど書いたソースコードを受信したJSONデーターを表示するように書き換えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/48030eb8-3c89-061e-ccfe-1bd107dfa1ea.png)

成功するとこのように表示されます。

これでUnity上でサーバーから取得した情報が使えるようになりました。

#データーベースに情報を登録する
次はUnityからサーバーにスコア(情報)を登録できるようにしていきたいと思います。

##リクエスト構造(JSON)
今回サーバーに送信するリクエスト形式は次のようにしました。

JSON { id , action, name , score }
 id : ユーザー識別用ID
 action : 操作内容(例：追加 = ADD)
 name : ユーザー名
 score : スコア

##リクエストを処理する(サーバー側)
まずはNode.jsでリクエストを処理するためのプログラムを作成します。

今回は追加(ADD)リクエストが来たらとりあえず何も考えずデーターベースに登録するとします。
なお、リクエスト先は **localhost:3000/post** とします。

```js:main.js
app.post("/Post",function(req,res,nex){
    let b = req.body;
    console.log("POST Receive. ["+b+"]");
    database_action(res,b.id,b.action,b.name,b.score);
})

function database_action(res,id,action,name,score){
    switch(action){
        case "ADD":
        console.log("Run Query : INSERT INTO RANK VALUES("+id+","+name+","+score+",now())");
        con.query("INSERT INTO RANK VALUES('"+id+"','"+name+"',"+score+",now())",function(e,r){
            if(e) response(res,"ERROR",null);
            else response(res,"OK",null);
        })
        break;
    }
}
```

##リクエストを送信する(Unity)
テストデーターとして

{ id:”a003” , action:”ADD” , name:”MyUser” , score:300 }

を送信するとします。
なお、送信は**POST**で行います。

```C#:Test.cs
    public void OnPushPost()
    {
        var send = "{ \"id\":\"a003\" , \"action\" : \"ADD\" ,\"name\":\"MyUser\" ,\"score\": 300 }";
        var bytes = System.Text.Encoding.UTF8.GetBytes(send);

        var req = WebRequest.Create("http://localhost:3000/Post");
        req.Method = "POST";
        req.ContentType = "application/json; charset=utf-8";
        req.ContentLength = bytes.Length;
        req.Timeout = 3000;

        var reqStm = req.GetRequestStream();
        reqStm.Write(bytes, 0, bytes.Length);

        var res = req.GetResponse();
        using (Stream stm = res.GetResponseStream())
        using (StreamReader str = new StreamReader(stm))
        {
            string json = str.ReadToEnd();
            Debug.Log(json);
        }

        Debug.Log("Send POST:" + bytes.Length);

        reqStm.Close();
        res.Close();
    }
```
実行してみるとデーターベースに登録されているのがわかると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/142e4038-94fe-a403-34f8-337cf4a49bf6.png)
`↑ リクエスト送信前と送信後でデーターが追加されているのがわかる`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371710/73876d33-d094-c573-6d3f-dd2734cea4b4.png)
送信後 /Get でリストを取得すると確かに登録されていることが確認できます。これでUnityからサーバーに登録もできるようになり、ランキング表示機能として使えるようになりました👼

インジェクション対策(例：SQL文に?を利用していない等)などせず簡単に書いてしまってあるのでこのまま運用すると間違いなく問題が起きますのであしからず。