はじめに
この記事では MasterMemory の基本的な使い方を紹介していきます
目次
- はじめに
- 目次
- 検証環境
- 必要なファイルの入手
- .unitypackage のインポート
- マスタを管理するクラスの準備
- MasterMemory のジェネレータを実行
- MessagePack のジェネレータを実行
- ゲーム実行中にマスタをビルドして使用
- 事前にマスタをビルドして使用
- 基本的な使い方まとめ
- MasterMemory のジェネレータの補足
- マスタを管理するクラスの追加
- マスタのクラスのプロパティに適用できる属性
検証環境
- Windows 10
- Unity 2019.3.10f1
- MasterMemory 2.2.2
- MessagePack for C# 2.1.115
必要なファイルの入手
https://github.com/Cysharp/MasterMemory/releases
上記のページにアクセスして、下記の2つのファイルをダウンロードします
- MasterMemory.Generator.zip
- MasterMemory.Unity.unitypackage
https://github.com/neuecc/MessagePack-CSharp/releases
次に上記のページにアクセスして、下記の2つのファイルをダウンロードします
- MessagePack.Unity.2.1.115.unitypackage
- mpc.zip
.unitypackage のインポート
ダウンロードした下記の2つの .unitypackage を Unity プロジェクトにインポートします
- MasterMemory.Unity.unitypackage
- MessagePack.Unity.2.1.115.unitypackage
マスタを管理するクラスの準備
Unity プロジェクトに「Assets/Scripts/Master」フォルダを作成して
このフォルダ内に「Master.cs」を作成します
using MasterMemory; using MessagePack; [MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } public string Name { get; set; } }
作成した「Master.cs」を開いて上記のコードを記述します
ここでは ID と名前を持つキャラクターのマスタクラスを定義しました
これでマスタを管理するクラスの準備が完了しました
MasterMemory のジェネレータを実行
次は準備したマスタクラスを MasterMemory で使えるようにするために
MasterMemory のジェネレータを実行します
"【MasterMemory.Generator.zip 展開先のフォルダ】\win-x64\MasterMemory.Generator.exe" ^ -i "【Unity プロジェクトのパス】\Assets\Scripts\Master" ^ -o "【Unity プロジェクトのパス】\Assets\Scripts\Generated" ^ -n "Master"
上記のように MasterMemory のジェネレータを実行するバッチファイルを書いて
実行することで
マスタクラスを MasterMemory で使えるようにするためのクラスが自動生成されます
MessagePack のジェネレータを実行
準備したマスタクラスを MasterMemory で使えるようにするためには
MessagePack のジェネレータも実行する必要があります
"【mpc.zip 展開先のフォルダ】\win\mpc.exe" ^ -i "【Unity プロジェクトのパス】\Assembly-CSharp.csproj" ^ -o "【Unity プロジェクトのパス】\Assets\Scripts\Generated"
上記のように MessagePack のジェネレータを実行するバッチファイルを書いて
実行することで
MessagePack で必要なクラスが自動生成されます
以上で準備したマスタクラスを MasterMemory で使用できるようになりました
ゲーム実行中にマスタをビルドして使用
マスタクラスが MasterMemory で使えるようになったため、
ゲーム実行中にマスタデータをビルドして使ってみます
Unity プロジェクトに「Test.cs」を作成して
using Master; using MessagePack; using MessagePack.Resolvers; using UnityEngine; public sealed class Test : MonoBehaviour { // MessagePack の初期化処理 [RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad )] private static void Initialize() { StaticCompositeResolver.Instance.Register ( MasterMemoryResolver.Instance, GeneratedResolver.Instance, StandardResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver( StaticCompositeResolver.Instance ); MessagePackSerializer.DefaultOptions = options; } private void Start() { // キャラクターの配列の用意 var characters = new[] { new Character { Id = 1, Name = "フシギダネ" }, new Character { Id = 2, Name = "フシギソウ" }, new Character { Id = 3, Name = "フシギバナ" }, new Character { Id = 4, Name = "ヒトカゲ" }, new Character { Id = 5, Name = "リザード" }, new Character { Id = 6, Name = "リザードン" }, }; // データベースのビルド var builder = new DatabaseBuilder(); builder.Append( characters ); var databaseBinary = builder.Build(); // データベースにアクセスするためのインスタンスの作成 var database = new MemoryDatabase( databaseBinary ); // キャラクターのデータベースを参照 var characterTable = database.CharacterTable; // Id が 3 のキャラクターを取得 Debug.Log( characterTable.FindById( 3 ).Name ); // Id が 7 に一番近いキャラクターを取得 Debug.Log( characterTable.FindClosestById( 7 ).Name ); // Id が 1 から 3 のキャラクターをすべて取得 foreach ( var n in characterTable.FindRangeById( 1, 3 ) ) { Debug.Log( n.Name ); } // Id が 3 のキャラクターが存在する場合は取得 if ( characterTable.TryFindById( 3, out var result ) ) { Debug.Log( result.Name ); } // Id が 3 のキャラクターが存在するかどうか Debug.Log( characterTable.TryFindById( 3, out _ ) ); // すべてのキャラクターを取得 foreach ( var n in characterTable.All ) { Debug.Log( n.Name ); } // すべてのキャラクターを逆順で取得 foreach ( var n in characterTable.AllReverse ) { Debug.Log( n.Name ); } // キャラクターの数を取得 Debug.Log( characterTable.Count ); } }
上記のコードを記述し、このコンポーネントをシーンのゲームオブジェクトにアタッチして
ゲームを実行してみると、正常にマスタデータを読み込んで使用できることが確認できます
ここではスクリプト上でマスタデータを作成する処理を直に記述していますが
例えば CSV や JSON からデータを読み込む処理に変更することで
外部アセットからマスタデータを読み込んで使用できるようになります
事前にマスタをビルドして使用
前項ではゲーム実行中にマスタをビルドして使用する方法を紹介しましたが、
MasterMemory にはマスタデータを事前に .bytes 形式にビルドする機能が備わっています
事前にマスタをビルドすることでより高速にマスタデータを読み込めるようになります
エディタ拡張で事前にマスタをビルドしてみます
Unity プロジェクトに「Assets/Editor」フォルダを作成して
このフォルダ内に「Builder.cs」を作成します
using Master; using MessagePack; using MessagePack.Resolvers; using System.IO; using UnityEditor; public static class Builder { [MenuItem( "Tools/Build" )] private static void Build() { // MessagePack の初期化 // 初期化を複数回実行すると下記の例外が発生するが // データベースのビルドには影響がないため例外は無視する // InvalidOperationException: Register must call on startup(before use GetFormatter<T>). try { StaticCompositeResolver.Instance.Register ( MasterMemoryResolver.Instance, GeneratedResolver.Instance, StandardResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver( StaticCompositeResolver.Instance ); MessagePackSerializer.DefaultOptions = options; } catch { } // キャラクターの配列の用意 var characters = new[] { new Character { Id = 1, Name = "フシギダネ" }, new Character { Id = 2, Name = "フシギソウ" }, new Character { Id = 3, Name = "フシギバナ" }, new Character { Id = 4, Name = "ヒトカゲ" }, new Character { Id = 5, Name = "リザード" }, new Character { Id = 6, Name = "リザードン" }, }; // データベースのビルド var builder = new DatabaseBuilder(); builder.Append( characters ); var databaseBinary = builder.Build(); // ビルドしたデータベースをファイルに保存 var path = "Assets/Resources/master.bytes"; var dir = Path.GetDirectoryName( path ); Directory.CreateDirectory( dir ); using ( var stream = new FileStream( path, FileMode.Create ) ) { stream.Write( databaseBinary, 0, databaseBinary.Length ); } // Unity の Project ビューに反映 AssetDatabase.Refresh(); } }
作成した「Builder.cs」を開いて上記のコードを記述します
これで事前にマスタデータをビルドできるようになりました
Unity メニューの「Tools > Build」を選択すると
Resources フォルダに「master.bytes」という名前のファイルが生成されます
これがマスタデータがビルドされたファイルになるため、
「Test.cs」でこのファイルからマスタデータを読み込むようにしていきます
using Master; using MessagePack; using MessagePack.Resolvers; using UnityEngine; public sealed class Test : MonoBehaviour { // MessagePack の初期化処理 [RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad )] private static void Initialize() { StaticCompositeResolver.Instance.Register ( MasterMemoryResolver.Instance, GeneratedResolver.Instance, StandardResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver( StaticCompositeResolver.Instance ); MessagePackSerializer.DefaultOptions = options; } private void Start() { // 事前にビルドしたマスタを読み込み var textAsset = Resources.Load<TextAsset>( "master" ); var bytes = textAsset.bytes; // データベースにアクセスするためのインスタンスの作成 var database = new MemoryDatabase( bytes ); // キャラクターのデータベースを参照 var characterTable = database.CharacterTable; // すべてのキャラクターを取得 foreach ( var n in characterTable.All ) { Debug.Log( n.Name ); } } }
スクリプトを変更して Unity でゲームを再生してみると
正常にマスタデータを読み込んで使用できていることが確認できます
このように、事前にマスタをビルドすることで
より高速にマスタデータを読み込めるようになります
基本的な使い方まとめ
- マスタを管理するクラスを定義する
- MasterMemory のジェネレータを実行する
- MessagePack のジェネレータを実行する
- マスタデータを準備する
- スクリプト直書き
- CSV・JSON などの外部アセットで準備
- マスタデータをビルドして使用する
- ゲーム実行中にマスタデータをビルドして使用する
- エディタ拡張などで事前にマスタデータをビルドして使用する
以上が MasterMemory の基本的な使い方になります
MasterMemory のジェネレータの補足
名前空間の指定
"【MasterMemory.Generator.zip 展開先のフォルダ】\win-x64\MasterMemory.Generator.exe" ^ -i "【Unity プロジェクトのパス】\Assets\Scripts\Master" ^ -o "【Unity プロジェクトのパス】\Assets\Scripts\Generated" ^ -n "Master"
-n
オプションで MasterMemory が出力するクラスが所属する名前空間を指定できます
キーが見つからなかった場合は null を返す
// KeyNotFoundException: DataType: Character, Key: -1 var character = characterTable.FindById( -1 );
デフォルトではキーが見つからなかった場合に KeyNotFoundException
が投げられますが
"【MasterMemory.Generator.zip 展開先のフォルダ】\win-x64\MasterMemory.Generator.exe" ^ -i "【Unity プロジェクトのパス】\Assets\Scripts\Master" ^ -o "【Unity プロジェクトのパス】\Assets\Scripts\Generated" ^ -n "Master" ^ -t
-t
オプションを指定すると null が返ってくるようになります
不変型
using MasterMemory; using MessagePack; [MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; } public string Name { get; } }
上記のようにマスタのクラスを不変型にしたい場合は
"【MasterMemory.Generator.zip 展開先のフォルダ】\win-x64\MasterMemory.Generator.exe" ^ -i "【Unity プロジェクトのパス】\Assets\Scripts\Master" ^ -o "【Unity プロジェクトのパス】\Assets\Scripts\Generated" ^ -n "Master" ^ -c
-c
オプションを指定します
using MasterMemory; using MessagePack; [MemoryTable("character"), MessagePackObject(true)] public class Character { [PrimaryKey] public int Id { get; } public string Name { get; } public Character(int Id, string Name) { this.Id = Id; this.Name = Name; } }
すると、MasterMemory.Generator.exe 実行後にコンストラクタが自動で記述され、
不変型として扱われるようになります
(すでにコンストラクタを定義している場合は無視されます)
テーブルクラスの拡張
namespace Master.Tables { // CharacterTable の部分クラス public sealed partial class CharacterTable { public int MaxId { get; private set; } // テーブル作成後に呼び出される関数 partial void OnAfterConstruct() { MaxId = All.Max( x => x.Id ); } } }
テーブルクラスは部分クラスとして定義されているため
自作のプロパティや関数を別ファイルで定義することができます
テーブル作成後に OnAfterConstruct 関数が呼び出されるため
テーブル作成後に行いたい処理はこの関数に記述することができます
マスタを管理するクラスの追加
ゲーム開発中にマスタを増やす時は
using MasterMemory; using MessagePack; [MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } public string Name { get; set; } } // ★ [MemoryTable( "item" ), MessagePackObject( true )] public class Item { [PrimaryKey] public int Id { get; set; } public string Name { get; set; } }
「Assets/Scripts/Master」フォルダ内のスクリプトに新しいマスタのクラスを定義して
MasterMemory と MessagePack のジェネレータを実行して
using Master; using MessagePack; using MessagePack.Resolvers; using System.IO; using UnityEditor; public static class Builder { [MenuItem( "Tools/Build" )] private static void Build() { // MessagePack の初期化 // 初期化を複数回実行すると下記の例外が発生するが // データベースのビルドには影響がないため例外は無視する // InvalidOperationException: Register must call on startup(before use GetFormatter<T>). try { StaticCompositeResolver.Instance.Register ( MasterMemoryResolver.Instance, GeneratedResolver.Instance, StandardResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver( StaticCompositeResolver.Instance ); MessagePackSerializer.DefaultOptions = options; } catch { } // キャラクターの配列の用意 var characters = new[] { new Character { Id = 1, Name = "フシギダネ" }, new Character { Id = 2, Name = "フシギソウ" }, new Character { Id = 3, Name = "フシギバナ" }, new Character { Id = 4, Name = "ヒトカゲ" }, new Character { Id = 5, Name = "リザード" }, new Character { Id = 6, Name = "リザードン" }, }; // ★アイテムの配列を用意 var items = new[] { new Item { Id = 1, Name = "キズぐすり" }, new Item { Id = 2, Name = "いいキズぐすり" }, new Item { Id = 3, Name = "すごいキズぐすり" }, }; // データベースのビルド var builder = new DatabaseBuilder(); builder.Append( characters ); builder.Append( items ); // ★アイテムの配列を登録 var databaseBinary = builder.Build(); // ビルドしたデータベースをファイルに保存 var path = "Assets/Resources/master.bytes"; var dir = Path.GetDirectoryName( path ); Directory.CreateDirectory( dir ); using ( var stream = new FileStream( path, FileMode.Create ) ) { stream.Write( databaseBinary, 0, databaseBinary.Length ); } // Unity の Project ビューに反映 AssetDatabase.Refresh(); } }
マスタをビルドする処理を記述します
事前にマスタをビルドする場合は Unity メニューからビルドしておきます
using Master; using MessagePack; using MessagePack.Resolvers; using UnityEngine; public sealed class Test : MonoBehaviour { // MessagePack の初期化処理 [RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad )] private static void Initialize() { StaticCompositeResolver.Instance.Register ( MasterMemoryResolver.Instance, GeneratedResolver.Instance, StandardResolver.Instance ); var options = MessagePackSerializerOptions.Standard.WithResolver( StaticCompositeResolver.Instance ); MessagePackSerializer.DefaultOptions = options; } private void Start() { // 事前にビルドしたマスタを読み込み var textAsset = Resources.Load<TextAsset>( "master" ); var bytes = textAsset.bytes; // データベースにアクセスするためのインスタンスの作成 var database = new MemoryDatabase( bytes ); // すべてのキャラクターを取得 foreach ( var n in database.CharacterTable.All ) { Debug.Log( n.Name ); } // すべてのアイテムを取得 foreach ( var n in database.ItemTable.All ) { Debug.Log( n.Name ); } } }
これで、新しいマスタを使用できるようになります
マスタのクラスのプロパティに適用できる属性
PrimaryKey
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } [PrimaryKey] public string Name { get; set; } }
PrimaryKey 属性は必ず1つ付与する必要があります
var character = characterTable.FindByIdAndName( ( 1, "フシギダネ" ) );
PrimaryKey 属性が付いていると Find 関数で使用できるようになります
PrimaryKey 属性が複数付いていると Find 関数で一緒に使用できるようになります
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey( keyOrder: 1 )] public int Id { get; set; } [PrimaryKey( keyOrder: 0 )] public string Name { get; set; } }
var character = characterTable.FindByNameAndId( ( "フシギダネ", 1 ) );
keyOrder で順番を指定することができます
NonUnique
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey, NonUnique] public int Id { get; set; } public string Name { get; set; } }
var characters = characterTable.FindById( 1 ); foreach ( var character in characters ) { }
NonUnique 属性が付いていると Find 関数で複数の結果を受け取れるようになります
SecondaryKey
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } [SecondaryKey( 0 )] public string Type1 { get; set; } [SecondaryKey( 0 )] public string Type2 { get; set; } }
var characters = characterTable.FindByType1AndType2( ( "くさ", "どく" ) );
PrimaryKey が適用されたプロパティ以外にも Find 関数で使用したい場合は
SecondaryKey 属性を使用します
SecondaryKey 属性の引数の数値を同じにしておくと Find 関数で一緒に使用できます
StringComparisonOption
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } [StringComparisonOption( StringComparison.InvariantCultureIgnoreCase )] public string Name { get; set; } }
StringComparisonOption 属性で文字列の比較方法を指定できます
IgnoreMember
[MemoryTable( "character" ), MessagePackObject( true )] public class Character { [PrimaryKey] public int Id { get; set; } [IgnoreMember] public string Name { get; set; } }
IgnoreMember 属性が指定されたプロパティは MessagePack で無視されます