土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

Utf8JsonをUnityで使ってみる(1)

(以下、スクショのスケールやトリミングができてないんですが、面倒なのでいつかやるかも、くらい。あとごめん、コードは微妙に実際に使ったものと違うのでそのままでは動かないかも。これは後で確認します)
 司エンジン(土屋が開発しているコード注入式ステートマシンを特徴とするゲームスクリプトエンジン)をUnityに移植したいのですが、去年試した方法(ruby版と同じようにC#の内部スクリプトとして動作するようにする)は、正直上手く行く気がしませんでした(土屋のC#への理解不足もあるけど、そもそもアプローチが間違ってる気がする)。

 そんな中、あおたくさんと話していて「司エンジンの一番重要な部分はステートマシンを駆動させるASTであって、スクリプト言語自体ではない。ASTをUnityに移植して、コード注入式ステートマシンを動作させられるなら、それだけでも価値があるのではないか」という発想を得まして、今年はこのアプローチでやってみたいと思います(あと3か月で今年終わるけど)。

 ひとまずASTは外部からJSONで渡すことにして、とはいえJSONの操作とかしたことないので、しばらくUnity上でJSONを弄る演習をしてみたいと思います。ライブラリは neueccさんのUtf8Jsonです。C#で実装されたシリアライザとしては最速とのこと(今回は速度を重視しているわけではありませんが)。

Utf8Json - C#最速のJSONリアライザ(for .NET Standard 2.0, Unity)
http://neue.cc/2017/09/29_559.html

 ちなみにUnityにはJsonUtitlityという高性能なJSONライブラリがあるのですが、これDictionaryが使えませんで、それだと今すぐに使う分には取り回しが面倒なので。最終的にDictionaryがなくても良くなるかもしれないし、JSONデシリアライザをカスタム実装する気もしているんですが、それまではこのスタイルで行きます。

 以下、Unityでの導入の手順をまとめました。

Utf8Jsonの導入

 今回のテストではUnity2018.1.6f1を使っています。実際の開発環境は2018.2なんですが、スクショを取ろうとした端末に入っていたUnityが古く、アップデートが面倒なのでしませんでした。まあ大きくは変わりますまい……。

 Unity用のパッケージが用意されているので(ありがたい!)、GitHubのreleasesからUtf8Json.Unity.1.3.7.1.unitypackageをダウンロードします。

Utf8Json
https://github.com/neuecc/Utf8Json/releases

 Unityで新規プロジェクトを作成し、unitypackageファイルをprojectにドラッグ&ドロップし、Importを選択すればライブラリがインポートされます。


Unsafe対応

 この状態ですでにコンパイルエラーが出ており、Playが実行できません。

 「unsafeなコードを書くならオプション指定が必要だよー」と言っています。C#ではメモリを直接制御できるunsafeコンテキストという概念があり、utf8jsonはそれを使っているようですね。これを使うにはコンパイラにオプション指定する必要があり、UnityではEdit>Settings>Player>Other Sttings>Configurationの"Allow 'unsafe' Code"をチェックします(この階層や名称はUnityのver upのたびに微妙に変わるので注意)。

シリアライズ(C#→JSON)

 まずはC#上のオブジェクトをJSONオブジェクトに変換(シリアライズと言います)してみます。コードは以下。utf8jsonのマニュアルのサンプルコードを流用していますが、マニュアルだと内容が異なるPersonクラスとMypersonクラスがあったり、Personクラスのアノテートが余計だったりして最小構成にならないので、若干弄ってます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Utf8Json;

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

public class NewBehaviourScript : MonoBehaviour {
	void Start () {
        var p = new Person { Age = 99, Name = "foobar" };

        // Object -> byte[] (UTF8)
        byte[] result = JsonSerializer.Serialize(p);

        // byte[] -> Object
        var p2 = JsonSerializer.Deserialize<Person>(result);

        // Object -> String
        var json = JsonSerializer.ToJsonString(p2);
    }
}

 "using Utf8Json;"を忘れないようにしましょう。実行すると以下のように、Person型pの中身がシリアライズされ、String型jsonに格納されました。お手軽!><

シリアライズ(JSON→C#)

 お次はJSON文字列を読み込んでC#オブジェクトに変換(デシアライズと言います)します。司AST対応としてはこっちが本番です。
 コードは以下。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Utf8Json;

public class NewBehaviourScript : MonoBehaviour {
	void Start () {
        var json2 = JsonSerializer.Deserialize<dynamic>(@"{""foo"":""json"",""bar"":100,""nest"":{""foobar"":true}}");

        var r1 = json2["foo"]; // "json" - dynamic(string)
        var r2 = json2["bar"]; // 100 - dynamic(double), it can cast to int or other number.
        var r3 = json2["nest"]["foobar"]; // true
    }
}

 が、これはまたコンパイルエラーになります。

 Unityは標準で.NET3.5を使用しているのですが、dynamic型は.NET Framework4.0でないと動作しないためです。こちらはEdit>Settings>Player>Configurationの"Scripting Runtime Version"を".NET 3.5 Equivalent"から".NET 4.x Equivalent"に切り替えればOKです。

 切り替えるとUnityの再起動が要求されます。

 また、VS2015では.NET4.xを上手く扱えないので、UnityがVS2015に紐づいている場合はEdit>PreferencesでUnity Preferencesポップアップを出し(どうでもいいけどなんでこれはインスペクタ表示にならないんだろうか)External Toolsの"External Script Editot"で"Visual Studio 2017(Community)"を選択しておきます(環境によって表示される文言は変わります)。

 これでOK。実行すると見事にJSON形式の文字列がjson2に格納され、それを連想検索で取得&格納できています。

 型がdynamicなのが気になりますが、そもそもdynamicを使うサンプルを選んだのが間違いだったのかも?(そしてdynamicを使わないなら.NET4.x対応必要なかったかも?) マニュアルまだ読んでない(!)のでこんなものでしょう。 本日はここまで。