エンティティビュー
はじめに
QuantumEntityView
は、QuantumのView
コンポーネントからエンティティにリンクされます。これはゲームオブジェクトへのAssetRef
を持ち、ゲームオブジェクトは特定のエンティティのビューを表現するためにインスタンス化されます。エンティティプロトタイプのプレハブか、UnityでQuantumEntityView
を持つシーンオブジェクトを設定すると、QuantumのView
コンポーネントが自動的にプロトタイプに追加されます。

QuantumEntityViewUpdater
は、Unity内のすべてのエンティティのビューを処理する役割を担っています。シミュレーションのデータに基づいて、エンティティに関連するビューのゲームオブジェクトの作成・更新・破棄を行います。QuantumEntityViewUpdater
スクリプトは、QuantumMap
を含むすべてのシーンに追加する必要があります。
QuantumEntityViewComponent
を使用して、各EntityView
にビュー関連の機能を追加できます。詳細はエンティティビューコンポーネントをご覧ください。
プーリング
デフォルトでQuantumEntityViewUpdater
は、エンティティが作成されるたびにQuantumEntityView
プレハブから新しいインスタンスを作成し、破棄も同様にビューのゲームオブジェクトを破棄します。
エンティティビューのプーリングを有効にするには、QuantumEntityViewUpdater
ゲームオブジェクトにQuantumEntityViewPool
スクリプトを追加してください。これはQuantumEntityViewUpdater
とシームレスに連携し、IQuantumEntityViewPool
インターフェースを使用した独自実装で置き換えることができます。
HidePooledObjectsInHierarchy | 有効にすると、プールされたオブジェクトにHideFlags.HideInHierarchy が設定されます |
ResetGameObjectScale | 有効にすると、オブジェクトのローカルスケールが1にリセットされます |
Precache Items | オブジェクトはAwake() でインスタンス化され、プール内で利用可能になります |
Quantumのコールバック/イベントを購読しているエンティティビューは、次のいずれかを行う必要があります。
- ゲームオブジェクトがプールに戻される前に購読解除する
- 以下のように
onlyIfActivateAndEnabled
パラメーターを有効にする
C#
QuantumEvent.Subscribe<EventPlayerKilled>(this, OnKilled, onlyIfActiveAndEnabled: true);
Bind Behaviour
UnityのQuantumEntityView
スクリプトにあるBind Behaviour
プロパティは、次のいずれかに設定できます。
Non Verified
:ビューのゲームオブジェクトを予測フレームで作成できるVerified
:ビューのゲームオブジェクトは確定フレームのみで作成できる
Non Verified
は、エンティティビューが高頻度でインスタンス化される場合や、ゲームプレイメカニクスの反応時間の関係から、できるだけ素早くプレイヤーの画面に表示する必要がある場合に通常使用されます。例えば、ハイペースなシューティングゲームの弾の作成には、この選択肢を使用すべきです。
一方Verified
は、エンティティビューを即座に表示する必要がなく、確認フレームを待つ分のわずかな遅延を許容できる場合に役立ちます。これは、予測ミスによるビューオブジェクトの作成/破棄を避けたい場合にも便利です。プレイヤーキャラクターのエンティティの作成時に使用するのが良い例になるでしょう。
Manual Disposal
Manual Disposal
プロパティが有効の場合、QuantumEntityViewUpdater
の破棄メソッドがスキップされます。これによって、QuantumEntityView
のOnEntityDestroyed
コールバックを使用して手動で破棄したり、カスタム破棄イベントから破棄したりすることができます。
ビューカリング(Quantum 3.0.3以降)
QuantumEntityViewUpdater
ゲームオブジェクトに追加したIQuantumEntityViewCulling
インターフェースを使用して、どのQuantumエンティティをUnityビューに同期させるかを制御ように拡張できます。これは、動的エンティティとマップエンティティのイテレーターを上書きします。
以下は球チェックを使用した基本的なカリングアルゴリズムのサンプル実装で、オンラインモード時のNon Verified
ビューの予測カリングも再利用しています。
C#
namespace Quantum {
using System.Collections.Generic;
using Photon.Deterministic;
using UnityEngine;
/// <summary>
/// 球を使用したエンティティビューカリングのサンプル実装
/// このスクリプトは<see cref="QuantumEntityViewUpdater"/>と同じゲームオブジェクトに追加します。
/// </summary>
public class QuantumEntityViewCulling : QuantumMonoBehaviour, IQuantumEntityViewCulling {
/// <summary>
/// カリング球の中心点
/// </summary>
public FPVector3 ViewCullingCenter;
/// <summary>
/// カリング半径
/// </summary>
public FP ViewCullingRadius = 20;
List<(EntityRef, View)> _dynamicEntities = new List<(EntityRef, View)>();
List<(EntityRef, MapEntityLink)> _mapEntities = new List<(EntityRef, MapEntityLink)>();
/// <summary>
/// カリング球内の動的エンティティのみを返す
/// </summary>
public unsafe IEnumerable<(EntityRef, View)> DynamicEntityIterator(QuantumGame game, Frame frame, QuantumEntityViewBindBehaviour createBehaviour) {
_dynamicEntities.Clear();
var radiusSqr = ViewCullingRadius * ViewCullingRadius;
if (createBehaviour == QuantumEntityViewBindBehaviour.NonVerified && frame.IsPredicted) {
// non-verifiedの予測カリングを使用する(予測フレームはオンラインモードのみ)
var filter = frame.Filter<View>();
// フィルターの予測カリングを有効にする
filter.UseCulling = true;
while (filter.NextUnsafe(out var entity, out var view)) {
_dynamicEntities.Add((entity, *view));
}
} else {
// 球からの距離をチェックして、エンティティをカリングする
var filter3D = frame.Filter<Transform3D, View>();
while (filter3D.NextUnsafe(out var entity, out var transform, out var view)) {
var distanceSqr = (transform->Position - ViewCullingCenter).SqrMagnitude;
if (distanceSqr < radiusSqr) {
_dynamicEntities.Add((entity, *view));
}
}
var filter2D = frame.Filter<Transform2D, View>();
while (filter2D.NextUnsafe(out var entity, out var transform, out var view)) {
var distanceSqr = (transform->Position.XOY - ViewCullingCenter).SqrMagnitude;
if (distanceSqr < radiusSqr) {
_dynamicEntities.Add((entity, *view));
}
}
}
return _dynamicEntities;
}
/// <summary>
/// カリング球内のマップエンティティのみを返す
/// </summary>
public unsafe IEnumerable<(EntityRef, MapEntityLink)> MapEntityIterator(QuantumGame game, Frame frame, QuantumEntityViewBindBehaviour createBehaviour) {
_mapEntities.Clear();
var radiusSqr = ViewCullingRadius * ViewCullingRadius;
if (createBehaviour == QuantumEntityViewBindBehaviour.NonVerified && frame.IsPredicted) {
// non-verifiedの予測カリングを使用する(予測フレームはオンラインモードのみ)
var filter = frame.Filter<MapEntityLink>();
// フィルターの予測カリングを有効にする
filter.UseCulling = true;
while (filter.NextUnsafe(out var entity, out var link)) {
_mapEntities.Add((entity, *link));
}
} else {
// 球からの距離をチェックして、エンティティをカリングする
var filter3D = frame.Filter<Transform3D, MapEntityLink>();
while (filter3D.NextUnsafe(out var entity, out var transform, out var link)) {
var distanceSqr = (transform->Position - ViewCullingCenter).SqrMagnitude;
if (distanceSqr < radiusSqr) {
_mapEntities.Add((entity, *link));
}
}
var filter2D = frame.Filter<Transform2D, MapEntityLink>();
while (filter2D.NextUnsafe(out var entity, out var transform, out var link)) {
var distanceSqr = (transform->Position.XOY - ViewCullingCenter).SqrMagnitude;
if (distanceSqr < radiusSqr) {
_mapEntities.Add((entity, *link));
}
}
}
return _mapEntities;
}
/// <summary>
/// カリング球のギズモ描画
/// </summary>
public void OnDrawGizmosSelected() {
Gizmos.DrawWireSphere(ViewCullingCenter.ToUnityVector3(), ViewCullingRadius.AsFloat);
}
}
}
View Flags
View Flags
によって、エンティティビューの詳細を設定し、パフォーマンスを調整できます。
DisableUpdateView | QuantumEntityView.UpdateView() /QuantumEntityView.LateUpdateView() は処理されず、エンティティビューコンポーネントには転送されません。 |
DisableUpdatePosition | エンティティビューの位置の更新を完全に無効にします。 |
UseCachedTransform | Transformのプロパティを呼び出さずに、キャッシュされたTransformを使用することで、パフォーマンスを向上させます。 |
DisableEntityRefNaming | デフォルトでは、エンティティのゲームオブジェクトにはEntityRefの値に合わせた名前が付けられます。このフラグを設定すると、この動作が無効になります。 |
DisableSearchChildrenForEntityViewComponents | エンティティビューコンポーネントから、エンティティビューの子ゲームオブジェクトの検索を無効にします。 |
DisableSearchInactiveForEntityViewComponents | エンティティビューコンポーネントから、非アクティブなエンティティビューの子ゲームオブジェクトの検索を無効にします。 |
EnableSnapshotInterpolation | 確定フレームのみで更新できるようにTransformバッファを初期化することで、スムーズな視覚表現を保証します。使用中は、pingに比例してビジュアル表示が遅延します。バッファとコールバックは、有効時のみ作成されます。補間モードは、各QuantumEntityViewごとの設定で制御されます。 |
Predction Error Correction
エンティティビューの誤差修正の設定を微調整します。各パラメーターの詳細な説明は、Unityのインスペクターから確認できます。
Events
UnityEvent<QuantumGame>
を使用して、エンティティビューの作成(プールからの作成含む)/破棄のUnityイベントを追加します。

エンティティのテレポート
デフォルトでQuantumEntityView
スクリプトは、エンティティのゲームオブジェクトのビジュアルを補間します。これによって、シミュレーションレートと描画レートの違いを調整し、予測ミスにおける誤差修正が行われます。
エンティティを1フレームで遠方の位置に移動させる(つまり「テレポート」させる)と、シミュレーション中のエンティティのデータは即座に対象の位置になりますが、ビューは数フレームに渡って開始位置から終了位置への線形補間が行われます。するとビューのゲームオブジェクトは、非常に速く動いているように目立って見えることがありますが、これは望ましくありません。
この問題を防ぐため、エンティティの位置が大きく変わる場合(通常、エンティティのリスポーンや、テレポート機能があるゲームでの移動)は、transform->Teleport(frame, newPosition);
を使用してください。これによって、エンティティビューコンポーネントは自動的に補間なしの移動を適用します。
ビューの検索
非常に一般的なユースケースに、特定のエンティティのビューの検索があります。シミュレーション側はQuantumEntityView
を認識しないため、イベントからEntityRef
をビューに渡したり、フレームから(読み取り専用で)ポーリングしたりする必要があります。QuantumEntityViewUpdater
はGetView(EntityRef)
関数を持ち、特定のEntityRef
のビューを見つけるために使用できます。ビューはディクショナリーにキャッシュされているため、ルックアップは非常に効率的です。
イベントと更新順序
QuantumEntityViewUpdater
のOnObservedGameUpdated
関数では、EntityView
の作成・破棄・更新が行われ、イベントが処理される前に呼び出されます。そのため、イベントで破棄されたエンティティは、既にビューを破棄している可能性があります。
カスタム破棄イベント
一般的なパターンとして、エンティティを破棄する際に、ビュー破棄についての追加情報を含むイベントを実行したい場合があります。イベントが処理される前にQuantumEntityView
が破棄されることを防ぐには、Manual Disposal
フィールドをtrue
に設定してください。
これによって、ビューが生存し続けるようになり、デフォルトでゲームオブジェクトを破棄するQuantumEntityViewUpdater
のDestroyEntityViewInstance
関数には渡されないようになります。
これで、イベントハンドラーはビューを検索して、存在するビューの破棄イベントを実行できます。QuantumEntityView
のクリーンアップ(破棄とオブジェクトプールへの返却)は手動で行う必要があります。
AutoFindMapData
マップ上でQuantumEntityView
を使用している場合は、AutoFindMapData
を有効にする必要があります。これを有効にすると、ビューは対応するMapData
オブジェクトを検索し、マップエンティティとそのビューを一致させます。マップ上でエンティティを使用していない場合は、これを無効にして、MapData
スクリプトが存在しないシーンにすることができます。
カスタマイズ
QuantumEntityViewUpdater
は、以下のようなカスタマイズが可能です。
Createのオーバーライド
ゲームオブジェクトをインスタンス化せずにプールから取得するには、CreateEntityViewInstance
関数をオーバーライドしてください。関数は、スポーンするビューを示すQuantum.EntityView
パラメーターを持ちます。QuantumEntityView.AssetGuid
は、オブジェクトプールのディクショナリーのキーとして使用できます。
C#
protected override QuantumEntityView CreateEntityViewInstance(Quantum.EntityView asset, Vector3? position = null, Quaternion? rotation = null) {
Debug.Assert(asset.View != null);
// IQuantumEntityViewPoolを使用して、ビューのオブジェクトプールもカスタマイズできます
EntityView view = _myObjectPool.GetInstance(asset);
view.transform.position = position ?? default;
view.transform.rotation = rotation ?? Quaternion.identity;
return view;
}
CreateEntityViewInstance()
の戻り値は、OnEntityViewInstantiated()
でエンティティに割り当てられます。このメソッドも仮想メソッドなのでオーバーライドできますが、ほとんどの場合は必要ありません。オーバーライドする際は、EntityRef
の割り当てを適切に維持することが重要です。
Destroyのオーバーライド
ビューを破棄せずにプールに返却するには、DestroyEntityViewInstance()
をオーバーライドしてください。
C#
protected virtual void DestroyEntityViewInstance(QuantumEntityView instance) {
_myObjectPool.ReturnInstance(instance);
}
マップエンティティ
マップエンティティは、ActivateMapEntityInstance()
でビューをアクティブにする必要があります。これは、必要に応じて独自の動作にオーバーライドできます。
DisableMapEntityInstance()
が呼び出されると、デフォルトではゲームオブジェクトが無効になります。この関数も独自の動作でオーバーライドできます。