>   >  Unityでつくるインタラクティブコンテンツ:第14回:図鑑コンテンツをつくる
第14回:図鑑コンテンツをつくる

第14回:図鑑コンテンツをつくる

<4>MasterMemoryを使う

MasterMemoryはCysharpが公開している、読み取り専用のインメモリデータベースです。ゲームでの利用を目的に設計されているため、検索が非常に高速になっているようです。

●GitHub - Cysharp/MasterMemory: Embedded Typed Readonly In-Memory Document Database for .NET Core and Unity.
https://github.com/Cysharp/MasterMemory


https://github.com/Cysharp/MasterMemory より

これを使って前出のテーブルを検索できるようにしていきます。


まず、Project Settingsを開き、「Api Compatibility Level*」を「.NET 4.x」に変更します。


これをしないと、今後の作業で以下のようなエラーが発生します。

FormatterNotRegisteredException:TagContents[] is not registered in this resolver.
resolver:StandardResolver

次に先ほどのGitHubページの導入手順に従って、MasterMemoryとMessagePackをダウンロードしてUnityに入れます。GeneratedフォルダとTablesフォルダを作成し、その中に以下のテーブル定義のスクリプトAnimal.csを作成します。

●DictSample/Assets/Tables/Animal.cs

1.	using MasterMemory;  
2.	using MessagePack;  
3.	  
4.	namespace CGWORLD  
5.	{  
6.	    [MemoryTable("animal"), MessagePackObject(true)]  
7.	    public class Animal  
8.	    {  
9.	        [PrimaryKey]  
10.	        public int Id { get; set; }  
11.	        [SecondaryKey(0), NonUnique]  
12.	        public string Classification { get; set; }  
13.	        public string Name { get; set; }  
14.	        public float Size { get; set; }  
15.	  
16.	        public Animal(int id, string classification, string name, float size) 
17.	        {  
18.	            Id = id;  
19.	            Name = name;  
20.	            Size = size;  
21.	            Classification = classification;  
22.	        }  
23.	    }  
24.	}  

ここまで用意できたら、MasterMemoryのコードを生成するスクリプトを実行します。MasterMemoryのサイトには生成スクリプトをUnityのエディタに登録する方法が書いてありますが、ここでは以下のようなバッチで処理を行なっています。

1.	@echo on  
2.	  
3.	set MMGEN="MasterMemory.Generator\win-x64\MasterMemory.Generator.exe"  
4.	set MPC="MessagePackUniversalCodeGenerator\win-x64\mpc.exe"  
5.	set NAME_SPACE=CGWORLD  
6.	set SCRIPT_PATH="..\Assets"  
7.	  
8.	del /S /Q %SCRIPT_PATH%\Generated  
9.	  
10.	%MMGEN% -i %SCRIPT_PATH%\Tables -o %SCRIPT_PATH%\Generated -n %NAME_SPACE%  
11.	%MPC% -i ..\Assembly-CSharp.csproj -o %SCRIPT_PATH%\Generated\MessagePack.Generated.cs  

これでGeneratedフォルダの中に以下のようなコードが生成されます。


ここで生成されたコードは自分で触ることはありません。このコードも前述のGitHubのサンプルコード置き場に上げてあります。

以下はデータ登録部分です。11種類の動物の類、名前、サイズ(mm)を登録してあります。Sample2.unityは、爬虫類、哺乳類といった分類を選択することができ、大きさと名前でソートして表示することができるサンプルです。


スクリプト部分はこれだけです。

●DictSample/Assets/Sample2/Sample2.cs

1.	using System.Collections.Generic;  
2.	using UnityEngine;  
3.	using CGWORLD;  
4.	using System.Linq;  
5.	  
6.	public class Sample2 : MonoBehaviour  
7.	{  
8.	    [SerializeField] UnityEngine.UI.Dropdown _classSelect;  
9.	    [SerializeField] UnityEngine.UI.Dropdown _orderSelect;  
10.	    [SerializeField] UnityEngine.UI.Button _search;  
11.	    [SerializeField] UnityEngine.UI.Text _list;  
12.	    void Start()  
13.	    {  
14.	        // 動物データを登録します  
15.	        var databaseBuilder = new DatabaseBuilder();  
16.	        databaseBuilder.Append(new List {  
17.	                    new Animal(0, "哺乳類", "フクロギツネ", 500),  
18.	                    new Animal(1, "哺乳類", "マレーグマ", 1200),  
19.	                    new Animal(2, "哺乳類", "アイアイ", 400),  
20.	                    new Animal(3, "鳥類", "アオサギ", 930),  
21.	                    new Animal(4, "鳥類", "コノハズク", 200),  
22.	                    new Animal(7, "爬虫類", "カミツキガメ", 300),  
23.	                    new Animal(6, "爬虫類", "ムカシトカゲ", 600),  
24.	                    new Animal(5, "鳥類", "セキセイインコ", 180),  
25.	                    new Animal(8, "爬虫類", "キングコブラ", 2300),  
26.	                    new Animal(9, "両生類", "ツチガエル", 40),  
27.	                    new Animal(10, "両生類", "カメガエル", 50),  
28.	                    new Animal(11, "両生類", "クロサンショウウオ", 150)});  
29.	        var db = new MemoryDatabase(databaseBuilder.Build());  
30.	  
31.	        // 検索ボタンが押されたらDropdownメニューで選択されている条件の動物を表示する  
32.	        _search.onClick.AddListener(() =>  
33.	        {  
34.	            var cls = db.AnimalTable.FindByClassification(_classSelect.options[_classSelect.value].text);  
35.	            _list.text = string.Join("\n", (_orderSelect.value == 0) ?  
36.	                cls.OrderBy(x => x.Name).Select(x => $"{x.Name} {x.Size}mm") :  
37.	                cls.OrderBy(x => x.Size).Select(x => $"{x.Size}mm {x.Name}"));  
38.	        });  
39.	    }  
40.	}  

DatabaseBuilderは先ほどの自動生成の際に作られたクラスです。これに対してAnimalオブジェクトを追加し、データベースを構築します。データの問い合わせは34行目で行なっていて、以下と同じことです。

1.	var cls = db.AnimalTable.FindByClassification("哺乳類");  

FindByClassificationも自動的に生成されたメソッドです。テーブル定義のとき以下のように指定しました。

1.	        [PrimaryKey]  
2.	        public int Id { get; set; }  
3.	        [SecondaryKey(0), NonUnique]  
4.	        public string Classification { get; set; }  

[PrimaryKey]と[SecondaryKey]属性を指定するとその変数が問い合わせ対象となり、FindBy変数名メソッドが生成されます。ひとつの分類(Classification変数)には動物が複数登録されるため、NonUniqueも指定します。

[PrimaryKey]はそのデータを特定できる情報を指定します、今回の例ではなくても良いのですが一般的にあった方が便利なので追加しています。[SecondaryKey]は検索対象となる情報を指定します。[PrimaryKey]と[SecondaryKey]の組み合わせで、生成される問い合わせメソッドが変化します。

くり返しになりますが、MasterMemoryを使う利点は、データベースプログラムを用意する必要がなく、Unityだけで完結できることだと思っています。もちろんMasterMemoryはたくさんのゲームで使われており、高速であるという利点がありますが、今回のような検索コンテンツの場合はリアルタイム性をシビアに求められません。

MySQLなどのデータベースは高機能なのですが、展示の検索コンテンツは多数の同時アクセスやデータの更新が起こらないため、ほとんどが不要な機能です。別のプログラム(データベース)の連携はプログラム的にも複雑になり、PCの環境設定も面倒になります。

MasterMemoryを使ってUnityで完結できれば、実行ファイル1つ渡せばどこでも動くため クライアントもチェックしやすく、トラブルを減らすこともできると思っています。

<5>OR検索を使う

SQLiteを紹介したときに、以下の条件で検索を行いました。

1.	-- 爬虫類もしくはサイズが40mmの生き物を問い合わせる  
2.	SELECT name FROM animals WHERE category='爬虫類' or size=40;  

この「もしくは」が「OR検索」と呼ばれるものです。MasterMemoryで生成される問い合わせメソッドにはORで検索できるものはありません。

そもそも複雑な問い合わせに対応するように設計されてはいないので、アプリケーション側で対応します。いくつか方法はあると思いますが、最もシンプルと思われる方法で処理してみました。Sample3.csがその実装です。まずデータベースに分類だけを登録した以下のようなテーブルを作成します。

登録コードはこうなります。

1.	databaseBuilder.Append((new List {  
2.	    "哺乳類", "爬虫類", "鳥類", "両生類"  
3.	}).Select((name, index) => new Classification(index, name)));  

シーン中では4つのToggleコンポーネントがそれぞれ検索項目に対応するように並べてあります。


下のコードでToggleがONになっているものだけ抜き出し、FindByClassificationメソッドで検索をかけています。

1.	var res = Enumerable.Range(0, 4)
2.	    .Where(x => _condition.transform.GetChild(x).GetComponent<Toggle>().isOn)  
3.	    .SelectMany(x => db.Animal2Table.FindByClassification(x)); 


コードがシンプルなのはLINQの恩恵も大きいのですが、データの登録、検索をとてもわかりやすく書くことができます。特別に他のデータベースを用意することなく実装できるので閲覧だけのコンテンツ制作には非常に向いていると思います。

また、データベースの内容は以下のようにMessagePackとして保存することもできます。

1.	using (var fs = new FileStream("test.mpac", FileMode.OpenOrCreate, FileAccess.Write))
2.	{
3.	    databaseBuilder.WriteToStream(fs);
4.	}

MessagePackはJSONと同じ感覚で使え、ファイル容量を大幅に小さくすることができます。コンテンツ実行時にセンサで収集したデータの保存用途としても有用だと思います。

今回紹介した図鑑コンテンツの実装例はファイル構造を反映するもの、MasterMemoryを利用するもの、それとOR検索を行うものでした。これらの例は簡単なものですが、ここから、検索の方法を工夫したり、見やすくUIをつくり込んだりしていけば十分使えるものになると思います。

またこのMessagePackとMasterMemoryを使った方法は図鑑コンテンツだけではなくデータビジュアライゼーションなどにも応用できると思いますので、ぜひご自分でいろいろ調べてみてください。

Profile.

高田稔則/Toshinori Takata(Codelight)
Codelight株式会社 代表取締役・インタラクションエンジニア
フリーランス、株式会社TBSテレビ等で映画CG制作、株式会社ソニー・コンピュータエンタテインメント(現 ソニー・インタラクティブエンタテインメント)でPS4のOSD開発などを経て2006年にCodelight株式会社を設立。インタラクティブコンテンツの制作を中核として、製造業向けのプロトタイプ開発なども行う
www.codelight.co.jp




その他の連載