Shape Config
はじめに
Shape Configは、シェイプの情報を保持します。これによって、エディター上で設定オプションを簡単に表示したり、コードでのシェイプの初期化を効率化できます。
ShapeConfigは2D/3Dの両方が存在し、それぞれShape2DConfig/Shape3DConfigになります。
現在、ShapeConfigは動的エンティティでの使用を想定しています。対応するシェイプの種類は次の通りです。
// 2Dの場合
- Circle
- Box
- Polygon
- Edge
- Compound (複数のシェイプの組み合わせ)
// 3Dの場合
- Sphere
- Box
- Capsule
- Compound (複数のシェイプの組み合わせ)
上記すべてのシェイプは、ShapeOverlapやPhysicsColliderと互換性があります。
ShapeConfigをエディターに表示する
独自アセットにShape2DConfig/Shape3DConfigを含めると、自動的にエディター上に表示されいます。
C#
namespace Quantum
{
    public unsafe partial class WeaponSpec
    {
        public Shape3DConfig AttackShape;
        public LayerMask AttackLayers;
        public FP Damage;
        public FP KnockbackForce;
    }
}
上記スニペットから生成されたアセットは、インスペクター上ですべてのShapeConfigオプションが表示されます。
 
    ShapeConfigからシェイプを作成/使用する
ShapeConfig使用する際は、CreateShapeメソッドを呼び出します。これによって、ShapeConfigアセットに保持されている情報が自動的に処理され、設定内のデータから適切なシェイプとパラメーターが作成されます。
C#
private static void Attack(in Frame frame, in EntityRef entity)
{
    // 攻撃範囲にOverlapShapeを使用して、近接攻撃を実行する
    var transform = frame.Unsafe.GetPointer<Transform3D>(entity);
    var weapon = frame.Unsafe.GetPointer<Weapon>(entity);
    var weaponSpec = frame.FindAsset<WeaponSpec>(weapon->WeaponSpec.Id);
    var hits = frame.Physics3D.OverlapShape(
        transform->Position,
        transform->Rotation,
        weaponSpec.AttackShape.CreateShape(frame),
        weaponSpec.AttackLayers);
        
    // ヒット対象を反復するゲームロジック
}
PhysicsColliderのシェイプの初期化時にも、同様の処理を実行できます。
Compoundシェイプ
Compoundシェイプは、複数のシェイプで構成されるシェイプです。Compoundシェイプ作成に使用できるシェイプは、最初に紹介したリストに含まれるものになります。
PhysicsColliderとShapeOverlapは、Compoundシェイプと完全に互換性があります。
現在、Quantumは永続的Compoundシェイプを提供しています。つまり、ヒープ内の他のシェイプのバッファを指すシェイプです。このバッファは、手動で解放するまでフレーム間で永続します。
新しいCompoundシェイプの作成
Compoundシェイプは、CreateShapeメソッドを呼び出すだけでShapeConfigから作成できます。または、Shape.CreatePersistentCompoundから手動で作成し、後でFreePersistentを呼び出して解放します。以下はライフサイクルを管理する方法の例で、3Dシェイプにも同様に適用できます。
C#
    // 永続的Compoundシェイプを作成する。実際にシェイプを追加するまでは、メモリ割り当ては発生しない。
    var compoundShape = Shape2D.CreatePersistentCompound();
    // Compoundシェイプにシェイプを追加する(shape1とshape2は任意のシェイプ)
    compoundShape.Compound.AddShape(f, shape1);
    compoundShape.Compound.AddShape(f, shape2);
    (...) // ゲームロジック
    // 手動で解放するまでこのCompoundシェイプは永続する
    compoundShape.Compound.FreePersistent(f);
APIには、RemoveShapes・GetShapes・FreePresistentなどのメソッドも提供されています。これらメソッドの詳細情報は、SDKのdocsフォルダー内のAPIドキュメントをご覧ください。
既存Compoundシェイプからのコピー
既存のCompoundシェイプをコピーして、新しいCompoundシェイプを作成することもできます。
C#
// 上記の例の既存Coumpoundシェイプを使用する
var newCompoundShape = Shape2D.CreatePersistentCompound();
newCompoundShape.Compound.CopyFrom(f, ref oldCompoundShape);
新しいCompoundシェイプを作成すると、新しいバッファも作成されます。このバッファは、他の永続的Compoundシェイプのように、開発者が手動で解放する必要があります。
個別のシェイプへのアクセス
シェイプを反復処理する例として、GetShapesメソッドを使用してバッファのポインタを取得し、単純なforループからCompoundシェイプに含まれるすべてのShape*にアクセスする方法があります。
メソッドが返すcoundはメモリ内にシェイプのポインタが含まれる境界を表すため、それを超えないようにしてください。
C#
if (shape->Compound.GetShapes(frame, out Shape3D* shapesBuffer, out int count))
{
    for (var i = 0; i < count; i++)
    {
        Shape3D* currentShape = shapesBuffer + i;
        // シェイプで何かする
    }
}
Compoundコライダー
Compoundコライダーは、Compoundシェイプを持つ通常のコライダーです。
エディター上で作成
エディター上では、Entity Component Physics Collider 2D/3DやEntity Prototypeスクリプトから、Compoundコライダーを作成するオプションが表示されています。
 
    Compoundシェイプを持つコライダーのプロトタイプを作成する場合は、自動的にメモリ管理が行われます。つまり、コライダーは自身が持つCompoundシェイプを管理するため、手動でメモリを解放する必要はありません。
コードで作成
コードからコライダーを作成する場合は、単純にCompoundシェイプをCreateファクトリーメソッドに渡します。前セクションのコードスニペットのようにCompoundシェイプを作成した後は、(...)を次のように置き換えてコライダーを作成できます。
C#
    var collider = PhysicsCollider2D.Create(f, compoundShape);
    f.Set(entity, collider);
上記のコードスニペットでは、collider.ShapeとcompoundShapeはヒープ内の異なるバッファを指しています。コライダーを作成するためだけにCompoundシェイプを使用していた場合は、その直後に解放することが可能です。コライダーが破棄/削除されると、メモリ内のコピーは解放されます。
メモリに関する重要な備考
ファクトリーメソッドCreate()の一部として使用されたコライダーは、Compoundシェイプのバッファのコピーのみを作成します。
C#
    var compoundShape = Shape2D.CreatePersistentCompound();
    compoundShape.Compound.AddShape(f, shape1);
    compoundShape.Compound.AddShape(f, shape2);
    // collider1とcollider2は、それぞれcoumpoundShepeバッファのコピーを作成する
    // collider1とcollider2は、それぞれ破棄/削除時に自身のコピーを解放する
    var collider1 = PhysicsCollider2D.Create(f, compoundShape);
    f.Set(entity1, collider1);
    
    var collider2 = PhysicsCollider2D.Create(f, compoundShape);
    f.Set(entity2, collider2);
    
    // 不要になったcompoundShapeのバッファは、ここで解放する
    compoundShape.Compound.FreePersistent(f);
対照的に、通常のシェイプのコライダーを作成した後にcollider.Shape = someCompoundを設定する場合は、バッファのコピーは作成されません。つまり、collider.ShapeとsomeCompoundは同じバッファを指すことになります。複数のCompoundコライダーやCompoundシェイプが同じバッファを指している場合、一方が解放されると、他方のバッファの参照が事実上壊れてしまうため、非常に危険です。
C#
    var compoundShape = Shape2D.CreatePersistentCompound();
    compoundShape.Compound.AddShape(f, shape1);
    compoundShape.Compound.AddShape(f, shape2);
    var collider1 = PhysicsCollider2D.Create(f, default(Shape2D));
    collider1.Shape = compoundShape;
    f.Set(entity1, collider1);
    var collider2 = PhysicsCollider2D.Create(f,  Shape2D.CreateCircle(1));
    collider2.Shape = compoundShape;
    f.Set(entity2, collider2);
    // collider1.Shapeとcollider2.ShapeとcompoundShapeは、すべて同じバッファを指す
    // ここでcompoundShapeを解放すると、collider1とcollider2が壊れてしまう
    compoundShape.Compound.FreePersistent(f);
ただし、これを適切に実行できれば、シェイプのメモリ管理を簡素化することができます。シェイプをコライダーに割り当て、メモリ管理の責任をコライダーに移譲することで、コライダーが破棄/削除された際に自動的にメモリを解放することができます。
C#
    var compoundShape = Shape2D.CreatePersistentCompound();
    compoundShape.Compound.AddShape(f, shape1);
    compoundShape.Compound.AddShape(f, shape2);
    var collider1 = PhysicsCollider2D.Create(f, Shape2D.CreateCircle(1));
    collider1.Shape = compoundShape;
    f.Set(entity1, collider1);
    // compoundShepeのバッファはcollider1によって参照されていて、
    // 自動的に破棄/削除されるため、手動で解放する必要はない
Compoundシェイプクエリ
Compoundシェイプクエリは、ブロードフェーズクエリと通常のクエリの両方を完全にサポートしています。動作やパフォーマンスは、複数のクエリを実行する場合と同様ですが、結果は同じHitCollectionで返されます。
Compoundシェイプの入れ子
Compoundシェイプの入れ子は、物理エンジンでサポートされていますが、2つの制限があります。
- シェイプは階層内において、ヒープ内の同じバッファの参照を1つだけ保持できます。既に参照されているバッファを追加すると、デバッグモードではエラーが発生します。これは解放時に、循環参照や無効なポインタが発生する問題を避けるためです。
- Compoundシェイプの入れ子は、エディター上ではサポートされていません(警告メッセージが表示されます)。これはUnityのシリアライザーの制限によるもので、ShapeConfigでより複雑な構造とドロワーが必要になるためです。