本文へスキップ
バージョン: 3.3

ランタイムに配置されたコンテンツを永続化のためにシリアライズする方法

このハウツーは以下を含みます:

  • 実行時にスポーンするプレハブの登録
  • 将来のセッションでコンテンツを復元するために、トランスフォームとステートフル・データを保存する。
  • 以前にシリアライズされたデータの更新
備考

このハウツーでは、手動で作成した冗長な文字列を使って、人間が読める形式でのシリアライゼーションを実演する。 適切なシリアライゼーションを行うには、 [Serializable] 構造体を使用し、具象構造体にはJSONシリアライザを、コンパクトなストレージにはバイナリー・シリアライザを使用することをお勧めします。

前提条件

Lightship ARを有効にしたUnityのプロジェクトが必要です。 詳細については、 ARDK 3.0 のインストールを参照してください。

ランタイム・コンテンツのシリアライゼーションの基本概念

リモートコンテンツオーサリングとは対照的に、Unityのシーンには、静的に配置されたオブジェクトが ARLocationごとに含まれています。ランタイムコンテンツシリアライゼーションでは、プレイヤーが任意にコンテンツを配置し、配置されたコンテンツを既知のフォーマットにシリアライズして、将来的に再スポーンできるようにします。

プレーヤーが構築したシーンをシリアライズするには、この情報が必要です:

  1. コンテンツを固定するルート変換(ARLocation、Image Targetなど);
    • これにより、将来デシリアライズされるコンテンツが同じ場所に置かれることが保証される。
  2. プレイヤーが配置したすべての GameObjects
  3. これらの GameObjectsのそれぞれのタイプと状態(この例では、トランスフォームと色)。

このデータは、次にプレーヤーが同じ ARLocationを追跡したときに検索できるように、文字列またはバイナリブロブにコンパクト化されます。

データをデシリアライズし、シーンを再構築する:

  1. シリアライズされたデータから GameObject を取得し、インスタンス化する;
    • GameObject は、同じルート変換 (ARLocation) の下に置かれ、同じ場所に表示されます。
  2. GameObjectの状態データ(トランスフォーム、色など)を適用する。

サンプルシーン "VpsSceneSerialization "は、このHow-Toのスクリプトと一緒にご覧いただけます。

シリアライズのためにプレハブを登録する

解決すべき最初の問題は、プレイヤーが配置可能なすべてのプレハブのリストを集めることです。 これは 将来、現場を再現するために必要なことである。

スクリプト PersistentObject.cs では、 GetComponent でスポーン可能な Prefab を見つけ、 PrefabIdで一意の Prefab を特定することができます。

をクリックして、PersistentObject クラスを展開します。
using UnityEngine;

public class PersistentObject : MonoBehaviour
{
[SerializeField]
private long _prefabId;

public long PrefabId
{
get { return _prefabId; }
internal set
{
_prefabId = value;
}.
}

...// シリアライズコード
}.

VpsSceneSerialization」サンプルでは、 PersistentObject Prefabsとして立方体と球体の2つがあり、 PrefabIds 0と1がそれぞれ割り当てられています。 これらの PersistentObjects は、 PersistentObjectManager に登録され、スポーン可能なオブジェクトの完全なリストを作成する。

永続オブジェクト・マネージャ・コンポーネント
クリックして、PersistentObjectManager クラスを展開します。
using System.Collections.Generic;
using System.Collections.ObjectModel;


public class PersistentObjectManager : MonoBehaviour
{
[SerializeField]
private List<PersistentObject> _persistentObjects = new List<PersistentObject>();

public ReadOnlyCollection<PersistentObject> PersistentObjects => _persistentObjects.AsReadOnly();

...// シリアライズおよび検証コード
}.

ランタイム・コンテンツの配置

PersistentContentDemoManager.csPersistentObjectPlacementBehaviour.csです。 これらのスクリプトは、プレイヤーがプレーンや GameObjectをタップするたびに、 CubesSpheres を生成/破壊する。 オブジェクトのスポーンや破壊については、ゲームごとに独自のルールがあるので、このHow-Toではスクリプトの詳細については触れません。

注意

以下のコード行は重要な詳細を実装している - インスタンス化後、すべてのスポーンされたオブジェクトは ARLocation の子でなければならない! あなた自身のアプリケーションを構築する際には、必ずこのようにしてください!

// _rootは、現在追跡されているARLocationへの参照
spawned.transform.SetParent(_root.transform, true)

生成オブジェクトのシリアライズと保存

プレイヤーがシーンを構築し、それを保存するためにシリアライズする準備ができたら、 GameObjectsをスポーンしたすべてのリストを作成する必要があります。 GameObject をそれぞれ Listに保持する方法や、このサンプルのように、ルート GameObjectGetComponent<PersistentObject> を使用する方法などがあります。

更新されたPersistentObjectManagerスクリプトを展開するにはクリックしてください。
public class PersistentObjectManager : MonoBehaviour
{
[SerializeField]
private List<PersistentObject> _persistentObjects = new List<PersistentObject>();
private const char Delimiter = ';';

private readonly Dictionary<long, PersistentObject> _validObjects =
new Dictionary<long, PersistentObject>();

public string SerializeObjectsToString(GameObject rootGo = null)
{
var root = rootGo ? rootGo.transform : this.transform;
var serializedObjects = new List<string>();
ValidateAndRefreshPersistentObjectList();

foreach (var child in root)
{
var persistentObject = ((Transform)child).GetComponent<PersistentObject>();
if (persistentObject)
{
if (!_validObjects.Keys.ToList().Contains(persistentObject.PrefabId))
{
continue;
}

serializedObjects.Add(persistentObject.SerializeObjectToString());
}.
}

return string.Join(Delimiter, serializedObjects.ToArray());
}.

...// デシリアライズと検証コード
} ...

簡単のため、このサンプルではすべてのプレハブが同じルートの子であり、ネストしたプレハブはないと仮定しています。 実際のアプリケーションでは、ヒエラルキー内のプレハブのトラッキングとハンドリングを改善する必要があるかもしれない。

PersistentObjectsリスト を収集した後、各 PersistentObject は、それ自身のシリアライズ を文字列に処理します。 これらの文字列は、保存のために1つの文字列表現に結合される。

更新されたPersistentObjectスクリプトを展開するにはクリックしてください。
public class PersistentObject : MonoBehaviour
{
[SerializeField]
private long _prefabId;

public long PrefabId
{
get { return _prefabId; }
internal set
{
_prefabId = value;
}.
}

public virtual string SerializeObjectToString()
{
return SimpleObjectToString();
}

protected string SimpleObjectToString()
{
var stringFormat = $"PrefabId: {_prefabId}位置:{SerializeVector3(transform.localPosition)}, " +
$"Rotation:{SerializeQuaternion(transform.localRotation)}、Scale:{SerializeVector3(transform.localScale)}";

return stringFormat;
}

private string SerializeVector3(Vector3 vector)
{
return string.Format("{0},{1},{2}", vector.x, vector.y, vector.z);
}

private string SerializeQuaternion(Quaternion quat)
{
return string.Format("{0},{1},{2},{3}", quat.x, quat.y, quat.z, quat.w);
}.

...// デシリアライズコード
}.

シリアライズは、将来のセッションでシーンを再構築するための関連情報を含む文字列を返す。 この例では、文字列は次のようになる:

PrefabId:0, Position:0,0,0, 回転:0,-0.2323422,0,0.9726341, Scale:1,1,1; PrefabId:1, Position:0,0,-1.689, Rotation:0,0,0,1, Scale:1,1,1

シリアライズされた文字列は、将来の検索のためにバックエンドやサービスに保存することができる。 このサンプルでは、文字列は PlayerPrefsにローカルに保存され、 ARLocation Anchorの名前によってインデックスが付けられる。

// コンテンツをシリアライズして格納する
var serializedString = PersistentObjectManager.SerializeObjectsToString(_root);

// _locationStringはpersistentAnchor.nameだけ
PlayerPrefs.SetString(_locationString, serializedString)

より多くの状態を格納するためのPersistentObjectの拡張

VpsSceneSerialization」サンプルでは、CubeとSphereは、 PersistentObjectsベースではなく、 ColorfulPersistentObjects です。 ここでは基本クラスを拡張して、基本的な変換の上にさらにカスタムのステートフルなデータをシリアライズすることを示す。

ColorfulPersistentObjectクラスを展開するにはクリックしてください。
using UnityEngine;

public class ColorfulPersistentObject : PersistentObject
{
public override string SerializeObjectToString()
{
Color color;

if (!Application.isPlaying)
{
// マテリアルを漏らさないためには、編集モードでsharedMaterialを使用する必要があります。
// これは、マテリアル(デフォルト)を共有するすべてのゲームオブジェクトが同じ色を持つことを意味します
color = GetComponent<Renderer>().sharedMaterial.color;
}
else
{
color = GetComponent<Renderer>().material.color;
}

var colorStr = SerializeColor(color);
return $"{base.SerializeObjectToString()}, Color: {colorStr}";
}

private string SerializeColor(Color color)
{
return $"{color.r},{color.g},{color.b},{color.a}";
}.

...// デシリアライズコード
}.

このエクステンションは、将来のリストアのために、 Color フィールドをシリアライズされた GameObject に追加します。 シリアライズされたデータ文字列のサンプルは次のようになる:

PrefabId:0, Position:0,0,0, 回転:0,-0.2323422,0,0.9726341, Scale:1,1,1, Color: 1,1,1,1;PrefabId:1, Position:0,0,-1.689, 回転:0,0,0,1, Scale:1,1,1, 色: 1,1,1,1

シリアライズされたコンテンツの復元(デシリアライズ)

シリアライズされたデータ文字列は、 PersistentObjectManager.csによって、将来のセッションで復元されます。

更新されたPersistentObjectManagerスクリプトを展開するにはクリックしてください。
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;

using UnityEngine;

public class PersistentObjectManager : MonoBehaviour
{
[SerializeField]
private List<PersistentObject> _persistentObjects = new List<PersistentObject>();

public ReadOnlyCollection<PersistentObject> PersistentObjects => _persistentObjects.AsReadOnly();

private const char Delimiter = ';';
private const string PrefabIdReg = "PrefabId:(?<pid>˶*)";
private Regex _prefabIdRegex = new Regex(PrefabIdReg);

private readonly Dictionary<long, PersistentObject> _validObjects =
new Dictionary<long, PersistentObject>();

...// シリアライズとバリデーションコード

public List<GameObject> DeserializeObjectsFromString(string str, GameObject rootGo = null)
{
var spawnedObjects = new List<GameObject>();
var root = rootGo ? rootGo.transform : this.transform;
ValidateAndRefreshPersistentObjectList();

var objs = str.Split(Delimiter);
foreach (var serializedObj in objs)
{
var match = _prefabIdRegex.Match(serializedObj);
if (!match.Success)
{
Debug.LogError("Could not parse out prefab id");
continue;
}

var prefabId = long.Parse(match.Groups["pid"].Value);
if (!_validObjects.ContainsKey(prefabId))
{
Debug.LogError($"Could not find prefab id {prefabId}");
continue;
}

var persistentObject = _validObjects[prefabId];
var newObject = Instantiate(persistentObject.gameObject, root);
newObject.GetComponent<PersistentObject>().ApplyStringToObject(serializedObj);
spawnedObjects.Add(newObject);
}

return spawnedObjects;
}.
}

DeserializeObjectsFromString メソッドは、シリアライズされた文字列を受け取り、それを個々の GameObjectsに分割し、記録された PrefabIdsに従って、登録された PersistentObjects をスポーンしようとします。 GameObject の各文字列は、 PersistentObject (この例では、 ColorfulPersistentObject)に渡され、 GameObject が個別に処理する。

注意

_persistentObjects リストのリストと内容が、シリアライズとデシリアライズの間で一貫していることを確認してください! そうでなければ、無意味なオブジェクトが代わりに生まれるかもしれない。 完全なゲームを作る場合、リストが操作間で一貫性を保つように、バージョン管理スキームを使用することをお勧めします。

クリックすると更新されたColorfulPersistentObjectデシリアライズスクリプトが表示されます。
using System.Text.RegularExpressions;

using UnityEngine;

public class ColorfulPersistentObject : PersistentObject
{
private const string ColourReg = "Color: (?<color>.*,.*,.*,.*,.*)";
private static Regex _colourRegex = new Regex(ColourReg);

...// シリアル化コード

public override void ApplyStringToObject(string str)
{
base.ApplyStringToObject(str);

var match = _colourRegex.Match(str);
if (!match.Success)
{
Debug.Log("No match");
return;
}

var colorString = match.Groups["color"].Value;
var color = DeserializeColor(colorString);
ApplyColorToObject(color);
}

public void ApplyColorToObject(Color color)
{
Material mat;
if (!Application.isPlaying)
{
// マテリアルを漏らさないためには、編集モードで sharedMaterial を使用しなければならない
mat = GetComponent<Renderer>().sharedMaterial;
}
else
{
mat = GetComponent<Renderer>().material;
}

mat.color = color;
}

private Color DeserializeColor(string str)
{
// 文字列から色をデシリアライズ
var split = str.Split(',');
return new Color(float.Parse(split[0]), float.Parse(split[1]), float.Parse(split[2]),
float.Parse(split[3]));
}.
}

拡張 ColorfulPersistentObject は、シリアル化された文字列の Color 部分を処理し、残りをベース PersistentObject クラスに渡して変換データを処理します。 これは非効率的だから、きれいにしよう:

クリックすると、最適化された PersistentObject スクリプトが表示されます。
using System.Text.RegularExpressions;

using UnityEngine;

public class PersistentObject : MonoBehaviour
{
[SerializeField]
private long _prefabId;

public long PrefabId
{
get { return _prefabId; }
internal set
{
_prefabId = value;
}.
}

private static string regStr =
"PrefabId:(?<pid>\d*), Position: (?<pos>.*,.*,.*), Rotation: (?<rot>.*,.*,.*,.*), Scale: (?<scale>.*,.*,.*)";
private static Regex regex = new Regex(regStr);

public virtual void ApplyStringToObject(string str)
{
// 文字列を解析してオブジェクトに適用
SimpleApplyStringToObject(str);
}

protected void SimpleApplyStringToObject(string str)
{
var match = regex.Match(str);
if (!match.Success)
{
Debug.Log("No match");
return;
}

var prefabId = match.Groups["pid"].Value;
var position = match.Groups["pos"].Value;
var rotation = match.Groups["rot"].Value;
var scale = match.Groups["scale"].Value;

_prefabId = long.Parse(prefabId);
transform.localPosition = DeserializeVector3(position);
transform.localRotation = DeserializeQuaternion(rotation);
transform.localScale = DeserializeVector3(scale);
}

private Vector3 DeserializeVector3(string str)
{
var split = str.Split(',');
return new Vector3(float.Parse(split[0]), float.Parse(split[1]), float.Parse(split[2]));
}

private Quaternion DeserializeQuaternion(string str)
{
var split = str.Split(',');
return new Quaternion(float.Parse(split[0]), float.Parse(split[1]), float.Parse(split[2]), float.Parse(split[3]));
}.
}

シリアライズされたデータの更新

シリアライズされたデータを更新するには、データをリストアし、プレイヤーにシーンの変更( GameObjectsの削除、移動など)を許可してから、再シリアライズし、以前に保存されたデータを上書きします。 (非常に大きなシーンの場合、常にシーン全体を読み込んで書き換えるのは非効率的なので、インクリメンタルな更新システムを推奨する)

詳細情報

For more information on using Location AR, see Creating Location AR Experiences with VPS.