[unity2019]Firebase RealtimeData でハイスコアの管理を行う

以前、サーバー上の MySQL にデータを保存する方法を記事にしました。

このやり方の場合「自前でサーバーを用意する」か、「レンタルサーバーを借りる」必要があります。
自前で用意するためには膨大な知識が、レンタルサーバーを借りるには少額といえど月額がかかる…。

どちらも敷居が高い、なんとかならないか、という人にオススメなのが、今回紹介する Firebase です。

慣れれば簡単ですが、インポートの順序を間違えたり、バージョンによって障害が発生したりと、私はいろいろと地雷を踏みました💦
失敗談も踏まえて解説していければと思います。

なお、今回は Firebase Realtime Database に特化した記事です。Firebase にはそれ以外にも色々と便利な機能があります。Analystics Crashlytics は企業でも使える内容です。

Firebase フェーズ

Firebase の登録

最初のセットアップはどの機能でも共通です。詳しくはこちら。

データベース作成

構築 をクリック。Realtime Database を選択

データベースを作成

ロケーションは米国のまま、次へ

とりあえずテストモードを選択。有効にする
(この情報はすぐ後で、手動で書き換えます)

データベースが作成完了。ここで ルール を選択

以下のように書き換えます。
(これで、FirebaseAuth で認証されたユーザーしかアクセス出来なくなるのでだいぶ安全なはず)

テストモードだと、認証なしで誰でも好き勝手にフルアクセスできるため、アプリ外からも自由に書き換えられる大変危険な状態です。
認証は匿名であれば本当に簡単な「ひと手間」ですむので、最初からちゃんと設定しておいた方がいいと私は思っています。

Authentification

匿名ユーザーのみ、認証が通るように設定します。

始める

匿名を有効にする

設定ファイルをダウンロード

「設定ファイル」をダウンロードします。
2つのファイルは、この後作成する unity プロジェクトに含めるので、取っておいてください。

左上の歯車 – プロジェクトを設定

2つのファイルをダウンロード

unity フェーズ

テストプロジェクトを作成

Assets/ の下に先ほどダウンロードした2つのファイルをドラッグ

Assets/ の下であれば、ファイルはどこでも構わないようです。

Player Settings

バンドルIDを、firebase と同じ内容にしてください。

SDK をインストール

Firebase Unity スタートガイドFirebase Unity SDK をクリックし、zip ファイルをダウンロード&解凍。
(2021/3/29 現在では7.1.0 でした)

unity2019 の場合、dotnet4/FirebaseDatabase.unitypackage をインストール

firebase 6.16.0 では色々あったダイアログが消えてしまっています。7.1.0 に問題がありそうな予感はしますが…

Build Settings で Android を選択肢、Switch Platform します。

[失敗] うっかり バンドルID が間違っていた場合

firebase unity のバンドルIDが一致していた場合は表示されませんが、タイプミスなどで一致していない場合、次のウィンドウが表示されます。
Apply で名前を一致させるようにしてください。

Auto resolution は Enable に

Android Resolver が自動的に実行される

無事完了すると、以下のようにプロジェクトの中に大量のファイルが作成されます。

[エラー] Android Resolver が正しく終了しない

unity 初心者(unity2019 から始めました)なんて人は高確率でハマるポイント(私の事)ですが、Android Resolver がエラーで終了してしまうことがありました。

この場合、一件エディタでは動くのに、Android 実機ではエラーになって firebase にアクセスできない、といった問題が発生します。
この辺りについては別記事にまとめましたので、参考にしてください。

実行スクリプトを作成する

以下のスクリプトを、Main Camera などにアタッチし、実行してください。

using System.Collections.Generic;
using System.Threading.Tasks;
using Firebase.Database;
using UnityEngine;

public class TestCreate : MonoBehaviour
{
    void Start()
    {
        dummyCreate();
//        dummyClear();
    }

    public static async void dummyCreate()
    {
        await SignIn();

        DatabaseReference root = FirebaseDatabase.DefaultInstance.RootReference;

        // 3つほどテストで作成してみる
        for (int i = 0; i < 3; i++)
        {
            // stage??/highScore, stage??/playCount を作成する
            DatabaseReference reference = root.Child("stage" + i.ToString("00"));

            Dictionary<string, object> itemMap = new Dictionary<string, object>();
            itemMap.Add("highScore", i * 100);
            itemMap.Add("playCount", i);

            await reference.UpdateChildrenAsync(itemMap);
        }


        await SignOut();
    }

    public static async void dummyClear()
    {
        await SignIn();

        DatabaseReference root = FirebaseDatabase.DefaultInstance.RootReference;

        // dummyCreate で作ったテーブルを削除
        for (int i = 0; i < 3; i++)
        {
            // stage??/highScore, stage??/playCount を作成する
            DatabaseReference reference = root.Child("stage" + i.ToString("00"));
            
            await reference.RemoveValueAsync();
        }


        await SignOut();
    }

    /// <summary>
    /// sign in
    /// </summary>
    public static async Task SignIn()
    {
        Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;

        // 匿名認証
        await auth.SignInAnonymouslyAsync().ContinueWith(
            task =>
            {
                if (task.IsCanceled)
                {
                    Debug.LogError("SignInAnonymouslyAsync was canceled.");
                    return;
                }
                if (task.IsFaulted)
                {
                    Debug.LogError("SignInAnonymouslyAsync encountered an error: " + task.Exception);
                    return;
                }
            }
        );

        Debug.Log($"Firebase signed in successfully: {auth.CurrentUser.UserId}");
    }

    /// <summary>
    /// sign out
    /// </summary>
    public static async Task SignOut()
    {
        Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;

        string userId = null;

        // 匿名認証していたら、開放
        if (auth.CurrentUser != null)
        {
            userId = auth.CurrentUser.UserId;

            // 匿名ユーザーでいっぱいにならないよう、消す
            await auth.CurrentUser.DeleteAsync().ContinueWith(
                task =>
                {
                    if (task.IsFaulted)
                    {
                        Debug.LogError("DeleteAsync encountered an error: " + task.Exception);
                        return;
                    }
                }
            );

            auth.SignOut();
        }

        if (userId != null)
        {
            Debug.Log($"Firebase signed out successfully: {userId}");
        }
    }
}

dummyCreate() が実行されると、firebase 上のテーブルにテストデータを書き出します。
(Start() メソッドで呼び出されます)
Realtime Database を確認すると、プログラムで書き込んだ値が即時反映されているはずです。

このように、厳格なテーブルの型を一切持たず気軽に書けるところが firebase(Realtime Database) の利点です。

お手軽すぎるので、大規模なデータを統合管理するようなアプリケーションだと、ちょっとしたミスでデータが壊れる…なんてこともありますが、個人で運営するちょっとしたものであれば特に問題はないと思います。

dummyClear() は dummyCreate() で作成されたテーブルを削除します。
Start() を書き換えて、dummyClear() を実行すると、このようにデータを消すことができます。

SignIn()SignOut()dummyCreate()dummyClear() のアクセス前、アクセス後に呼ばれます。
これは匿名認証と呼ばれ、ユーザーが手動でログインする必要なく、データベースへのアクセス権を得るための方法です。
今回のようにハイスコアを集計するだけ、名前もいらないという場合、認証はこれだけでよさそうです。

匿名認証を、通常のユーザー認証に変更する手段も firebase には用意されているので、色々と試してみてください。

Firebase は無料って言うけど、具体的には?

Firebase Spark は無料ですが、使用できるリソースに上限が定められており、それを超えると有料にアップグレードする必要があるようです。

容量は 1GB まで、ダウンロードアクセス月 10GB が上限のようです。
(アップロードは制限なし?)

この辺りを意識したアクセス頻度にしましょう!

おわりに

ちょっと長めの記事になりましたが、ほとんどがスクリーンショットなので、実際に手を動かす部分がほとんどなく、クラウドデータを Unity でアクセスする事が出来たのではないでしょうか。
(MySQLの時は長すぎて5記事くらいに分けましたしね…)

エラーには色々と悩まされましたが、それを差し引いても今は様々な無料サービスがあり、想像以上に楽することができますね。(知識さえあれば…)

ノーコード時代はどこまでいっても難しいと思いますが、ローコード時代です。ただ、色々なサービスの知識を持ち、それを上手に構築する…ノーコード時代の方が必要とされる知識量(や、アンテナ)は多くなってるんじゃ、と感じました。

よろしければ Twitter をフォローしてもらえると嬉しいです!

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA