迷路ゲームにランキング機能をつけるお話


N高アドカレ16日目の記事ですー
データベース一切わかんない人がオンラインランキングの実装を頑張った話になります。

はじめに

軽く自己紹介

N高の江坂プロクラの高三の受験生です。
大学に無事AOで合格して時間ができたのでアドカレ参加。
UnityとBlenderでゲーム作ったり色々してます。
Webとサーバーサイドはほとんどわかんないです…

ゲームの説明

迷路ゲーム自体は自作キャラがモデリングできたからそれを歩かせてゲームにしたいという思いから作ったもので、シンプルなものです。

敵キャラのゴーストを避けながらゴールを目指すゲームです。
このゲームにオンラインランキング(タイムアタックのハイスコア)をつけるときの話です。


自信作の自作キャラ

どうやってスコアを保存するか

はじめにどうやってクリアタイムを保存させようかと考えました。
ゲームに記憶させるのか、データをサーバーに保存するか…と色々考えていた時に、こちらのUnity + NCMBでスコアランキングを実装するという記事を見つけました。この記事の通りにやってみることにしました。

記事を参考に作業

NCMB側の設定

Googleでログインしました。

そしてクラスをHighScoreという名前で作成。

Unity側の設定

UnityにNCMBのパッケージを導入。

NCMBSettingsという名前でオブジェクトを作ってNCMBSettingsというスクリプトをアタッチしました。

アプリケーションキーとクライアントキーをNCMBSettingsのインスペクタにコピペ。

ここまでは事前準備です、次にコードを書いていきます。

クリアタイムの保存

データを保存するためのオブジェクト(箱)を作ってその中にクリアタイムを保存します。

NCMBObject HighScore = new NCMBObject("HighScore");

これでデータを入れるオブジェクトを作ります。

HighScore["Score"] = script.totalTime;

HighScoreに値を代入。

私は別の時間制御をしているスクリプトのtotalTimeを参照させるようにしています。

HighScore.SaveAsync();

そしてNCMB側に保存させます。

私が実際に書いたコードの一部を参考に置いておきます。

 void OnTriggerEnter(Collider collision)
    {
        if (collision.gameObject.tag == "Player")
        {
            gameclear.SetActive(true);
            TimerUI.SetActive(false);

            NCMBObject HighScore = new NCMBObject("HighScore");
            HighScore["Score"] = script.totalTime;
            HighScore.SaveAsync();
        }
    }

プレイヤーとゴールの判定オブジェクトが衝突判定を起こした時に、gameclearUIの表示、制限時間UIの非表示と同時にクリア時の時間をデータベースに送っています。

これで、データベースにクリア時の時間が記録されました。

クリアタイムの取得

クリアタイムをデータベースから取得して表示させます。

NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("HighScore");

取得したデータはNCMBQueryに格納されるのでこれを先に作ります。

query.OrderByAscending("Score");
query.Limit = 5;

並び替え&5つだけ取得させる設定

取得設定ができました。

そして実際に取得させようと思い、

query.FindAsync ((List<NCMBObject> objList ,NCMBException e) => {
        if (e == null) {
          objList[0]["Score"] = score;
          objList[0].SaveAsync();
        }
      });

このコードを参考にしてコードを書いたところ、エラーでデータの取得ができませんでした。
Scoreデータを関数に代入する方法がわからず、しばらく試行錯誤しました。

query.FindAsync((List<NCMBObject> objList, NCMBException e) =>{
if (e == null)
{
HighScore_1 = System.Convert.ToSingle(objList[0]["Score"]);
HighScore_2 = System.Convert.ToSingle(objList[1]["Score"]);
HighScore_3 = System.Convert.ToSingle(objList[2]["Score"]);
HighScore_4 = System.Convert.ToSingle(objList[3]["Score"]);
HighScore_5 = System.Convert.ToSingle(objList[4]["Score"]);
}

結果このコードでHighScoreにListのScoreを代入することができました…

どうやらSaveAsyncが邪魔してたみたいでした…

ランキング表示

取得して関数に代入した値をランキングのUIとして表示させます。

参考に私が書いたコード置いときます。

using UnityEngine;
using UnityEngine.UI;
public class Ranking_1 : MonoBehaviour
{
    private Text Ranking1;

    public GameObject Ranking;
    ranking script;

    public GameObject Next;
    Next script2;

    int display;

    float totalTime;
    int minute;
    float seconds;

    void Start()
    {
        script = Ranking.GetComponent<ranking>();//HighScoreを代入した関数を参照させる設定
        script2 = Next.GetComponent<Next>();//HighScoreを表示させる関数を参照させる設定
        Ranking1 = GetComponentInChildren<Text>();
    }
    void Update()
    {
        totalTime = script.HighScore_1;//totalTimeにHighScore_1の値を入れる

        display = script2.rankingdisplay;//HighScoreを表示させるための関数

        if (display == 1)
        {
            minute = (int)totalTime / 60;
            seconds = totalTime - minute * 60;

            Ranking1.text = minute.ToString("00") + ":" + ((int)seconds).ToString("00");
        }

    }
}

HighScoreの値とHighScoreを表示させる関数の値を参照させる設定をして、HighScoreを表示させる関数が1の時にHighScoreをUIに表示させるようにしています。
ゲームクリア画面からランキング画面の画面推移時に関数を0から1にして表示させることができます。
(もうちょっといいコードがかけそうな気がしたけど、私のコーディング力ではこれぐらいしかかけなかった…)


ゲームクリア画面
Nextボタンを押すと

ランキング表示させることができた!

追記

ゲームにランキングが追加できたので、しばらくテストプレイしていると、なぜか大量のAPIリクエストを送っていた…

この日と次の日はすごい数送ってました…その数約5万件…
今は改善して減りましたが…
大量のAPIリクエストの原因は、ゴール後にUpdate処理で1フレームに1回、データベースのデータを取得していたからでした…

void Update()
    {

        int GameclearJudgment = script.Gameclearfunction;

        if (GameclearJudgment == 1)
        {
            NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("HighScore");

            query.OrderByAscending("Score");
            query.Limit = 5;

            query.FindAsync((List<NCMBObject> objList, NCMBException e) =>{

                if (e == null)
                {
                    HighScore_1 = System.Convert.ToSingle(objList[0]["Score"]);
                    HighScore_2 = System.Convert.ToSingle(objList[1]["Score"]);
                    HighScore_3 = System.Convert.ToSingle(objList[2]["Score"]);
                    HighScore_4 = System.Convert.ToSingle(objList[3]["Score"]);
                    HighScore_5 = System.Convert.ToSingle(objList[4]["Score"]);
                }


            });

        }
    }

問題のコード
ゲームクリアした時に値が1になる関数を参照して、ゲームクリアしていたらデータベースの値を取得するようにする処理をUpdateに書いてしまった。

    void Update()
    {

        int RankingJudgment = script.Rankingfunction;


        if (RankingJudgment == 1)
        {
            NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("HighScore");

            query.OrderByAscending("Score");
            query.Limit = 5;

            query.FindAsync((List<NCMBObject> objList, NCMBException e) =>{

                if (e == null)
                {
                    HighScore_1 = System.Convert.ToSingle(objList[0]["Score"]);
                    HighScore_2 = System.Convert.ToSingle(objList[1]["Score"]);
                    HighScore_3 = System.Convert.ToSingle(objList[2]["Score"]);
                    HighScore_4 = System.Convert.ToSingle(objList[3]["Score"]);
                    HighScore_5 = System.Convert.ToSingle(objList[4]["Score"]);
                }

                script.RankingJudgmentReset();

            });

        }
    }

データベースのデータを取得した後に参照元の関数を0にリセットするように改善。
無事にリクエスト数が減りました。

まとめ

データベースを触るのは初めてだったのでわからないことも多かった…

NCNBは細かい設定が必要ないのでわかりやすくて触りやすかった


次はランキングのスコアと同時にuser nameを表示できるようにしたい…
WebGL書き出ししてWeb上で遊べるようにしようか検討中。