Unityに関連する記事です

趣旨


 PlayFab などのサーバーに保存したデータの多くは Json 形式のファイルになっています。
そのため、プレイヤーデータやゲーム内の状態を効率的かつ、正しい方法を使って保存および読み込む必要があります。

 本記事では、Json.NETとUniRxのReactivePropertyを組み合わせて、シリアライズとデシリアライズを行う手法を解説します。


やりたいこと


 ・Unityゲーム内のプレイヤーデータをJson形式で保存
 ・保存されたJsonデータを読み込んでUnityオブジェクトにデシリアライズ
 ・リアクティブプログラミングライブラリUniRxのReactivePropertyを使って効率的なデータ管理


できないこと


 通常のJson.NETではReactivePropertyのデシリアライズが出来ません。

 例えば、以下のようなReactivePropertyを持つクラスがあるとします。

[System.Serializable]
public class PlayerData {

    public ReactiveProperty<int> Score { get; set; }

    // コンストラクタ
    public PlayerData() {
        // 初期化など必要に応じて処理を追加
        Score = new ReactiveProperty<int>();
    }
}

 このクラスを通常のJson.NETでデシリアライズしようとすると、うまくいかずエラーが発生します。

ArgumentException: Could not cast or convert from System.Int64 to UniRx.ReactiveProperty`1[System.Int32].
Newtonsoft.Json.Utilities.ConvertUtils.EnsureTypeAssignable (System.Object value, System.Type initialType, System.Type targetType)

JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Int32' 
because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.
To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change 
the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.



解決のアプローチ方法  Json の内容を ReactiveProperty に対応可能な形式にして対応するー


 Json.NETにおいて、ReactiveProperty を正しくデシリアライズするためには、JsonConveter クラスを継承した
ReactivePropertyConverter を作成し、ReadJson メソッドと WriteJson メソッドをオーバーライドして実装します。

 以下はそのサンプルコードです。

using Newtonsoft.Json;
using UniRx;
using System;
using Newtonsoft.Json.Linq;

public class ReactivePropertyConverter<T> : JsonConverter<ReactiveProperty<T>> {

    public override void WriteJson(JsonWriter writer, ReactiveProperty<T> value, JsonSerializer serializer) {
        if (value != null && value.HasValue) {
            // HasValueがtrueの場合、Valueを出力
            serializer.Serialize(writer, new { Value = value.Value, HasValue = true });
        } else {
            // HasValueがfalseの場合、nullを出力
            serializer.Serialize(writer, null);
        }
    }

    public override ReactiveProperty<T> ReadJson(JsonReader reader, Type objectType, ReactiveProperty<T> existingValue, bool hasExistingValue, JsonSerializer serializer) {
        var token = JToken.Load(reader);

        if (token.Type == JTokenType.Null) {
            // JSONがnullの場合、新しいReactivePropertyを作成して返す
            return new ReactiveProperty<T>();
        } else {
            // JSONが値を持っている場合、その値を元に新しいReactivePropertyを作成して返す
            var valueToken = token["Value"];

            if (valueToken != null) {
                var value = valueToken.ToObject<T>(serializer);
                return new ReactiveProperty<T>(value);
            } else {
                // "Value"プロパティが存在しない場合、例外処理またはデフォルトの処理を行う
                // 例: throw new JsonSerializationException("Expected 'Value' property not found.");
                return new ReactiveProperty<T>();
            }
        }
    }
}


サンプルコード


 ReactivePropertyConverter クラスは、適用したい ReactiveProperty に属性情報を付与することで利用できます。

 例えば、先ほどの PlayerData クラス内の ReactiveProperty に属性情報を付与します。

[System.Serializable]
public class PlayerData {
 
    [JsonConverter(typeof(ReactivePropertyConverter<int>))]  //  ← 属性情報を付与
    public ReactiveProperty<int> Score { get; set; }
}

 これでデシリアライズとシリアライズが可能な ReactiveProperty になります。

 この機能を活用すると Json が ReactiveProperty に適した形式として保存・読み込みされます。

{
  "Score": {
    "Value": 0,
    "HasValue": true
  }
}



 利用する場合には、下記のように利用します。

public class GameManager : MonoBehaviour {

    void Start() {
        // プレイヤーデータの作成と初期化
        PlayerData player = new PlayerData {
            Score = new (1000)
        };

        // プレイヤーデータをJsonにシリアライズ
        string json = JsonConvert.SerializeObject(player);

        // Jsonデータをデシリアライズして新しいプレイヤーデータを作成
        PlayerData loadedPlayer = JsonConvert.DeserializeObject<PlayerData>(json);

        // デシリアライズ後のプレイヤーデータのスコアを表示
        Debug.Log("Loaded Player Score: " + loadedPlayer.Score.Value);
    }
}

 このサンプルコードでは、ReactivePropertyConverterを使用してJson.NETがReactivePropertyを正しく処理できるようにしています。

 これにより、UniRxのReactivePropertyを含むオブジェクトを簡単にJson形式で保存・読み込みできるようになります。


解決のアプローチ方法◆ Json の内容はそのままで対応するー


 アプローチ方法,両豺隋△垢任吠歛犬気譴討い Json データが存在していると、
ファイルの形式が合わないためエラーとなります。よって、途中からの運用変更は出来ません

 最初からアプローチ方法,農作している場合には問題ありませんが、
すでに Json ファイルがある場合には、そのファイルの形式はそのままにしておき、
保存するタイミングや、読み込むタイミングで ReactiveProperty に対応させる方法がよいでしょう。

 以下はそのサンプルコードです。


サンプルコード


 PlayerData クラスに、Jsonのデシリアライズおよびシリアライズのためのメソッドを追加しています。
ReactivePropertyは非Serializableとして保持し、Jsonの処理前後でscore変数とscoreReactiveの同期をとるようにしています。
これにより、Jsonのシリアライズ・デシリアライズ時にReactivePropertyの値も適切に処理できます。


using UnityEngine;

[System.Serializable]
public class PlayerData
{
    [SerializeField]
    private int score;

    // ReactivePropertyはJsonUtilityでシリアライズできないので、非Serializableとして保持
    private ReactiveProperty<int> scoreReactive;

    // ReactivePropertyを外部に公開するためのプロパティ
    public IReadOnlyReactiveProperty<int> ScoreReactive => scoreReactive;


    // コンストラクタ
    public PlayerData(int initialScore)
    {
        // 初期化など必要に応じて処理を追加
        score = initialScore;
        scoreReactive = new ReactiveProperty<int>(score);
    }

    // サーバーから取得したJsonデータをPlayerDataに変換するメソッド
    public static PlayerData FromJson(string json)
    {
        PlayerData playerData = JsonUtility.FromJson<PlayerData>(json);

        // JsonUtilityでデシリアライズした後、scoreをReactivePropertyに設定
        playerData.scoreReactive.Value = playerData.score;

        return playerData;
    }

    // サーバーに送るためのJsonデータを生成するメソッド
    public string ToJson()
    {
        // サーバーに送る際には、ReactivePropertyの値をscore変数に設定
        score = scoreReactive.Value;

        return JsonUtility.ToJson(this);
    }
}



 この場合の Json 形式は一般的な形式になります。

{
  "score": 0
}



 利用する場合には、下記のように利用します。

using UnityEngine;
using Newtonsoft.Json;

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // TODO サーバーから受け取ったJsonデータを仮定する
        string receivedJsonFromServer = "{ \"score\": 1500 }";

        // PlayerDataのFromJsonメソッドを使用してJsonデータをデシリアライズ
        PlayerData loadedPlayer = PlayerData.FromJson(receivedJsonFromServer);

        // デシリアライズ後のプレイヤーデータのスコアを表示
        Debug.Log("Loaded Player Score: " + loadedPlayer.ScoreReactive.Value);

        // 新しいプレイヤーデータを作成
        PlayerData newPlayer = new PlayerData(2000);

        // PlayerDataのToJsonメソッドを使用してプレイヤーデータをJsonにシリアライズ
        string json = newPlayer.ToJson();

        // シリアライズしたJsonデータを表示
        Debug.Log("Serialized Player Data: " + json);

        // TODO サーバーへ送信

    }
}

 サーバーからのJsonデータを仮定してデシリアライズし、新しいプレイヤーデータを作成してシリアライズしています。

 ゲーム内では PlayerData 内の ReactiveProperty を活用して購読処理を行い、
サーバーに送信する前に Json に置き換えてから送信するようにします。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます