クエリ
はじめに
クエリは、動的エンティティと静的コライダーを考慮できます。各API(Raycast
・Linecast
・OverlapShape
)は非常に似ていて、ヒットのコレクション(フィールドのデータの種類も同じ)を返します。
すべての物理クエリはオプションフラグでカスタマイズ可能で、非常に柔軟かつ最適化されています。例えば、ほとんどのクエリはデフォルトで衝突点や法線を返さないため、ユーザーによる明示的な定義が必要です(このドキュメントの後半で説明します)。
クエリ
LinecastとRaycast
C#
// For 2D
var hits = f.Physics2D.LinecastAll(FPVector2.Zero, FPVector2.One);
for (int i = 0; i < hits.Count; i++) {
var hit = hits[i];
}
// For 3D
var hits = f.Physics3D.LinecastAll(FPVector3.Zero, FPVector3.One);
for (int i = 0; i < hits.Count; i++){
var hit = hits[i];
}
結果として返されるHitCollection
オブジェクトには、以下のプロパティが含まれます。
HitCollection
の各要素は、EntityRef
か静的コライダー情報を保持します。これらは排他的で、片方が有効なら、もう片方はnull
になります。HitCollection
の反復処理にはCount
を常に使用してください。- 要素はソートされていません。
FPVector2
を渡してSort()
を呼び出すことで、渡された基準点からの距離に基づいて要素をソートできます。
Raycast
はLinecast
のシンタックスシュガーです。動作は同じですが、start
・end
のかわりにstart
・direction
・max-distance
を指定します。また、Linecast
やRaycast
にオプションパラメーターを渡すことができます。
LayerMask
:どの物理レイヤーに対してキャストするかを指定します。QueryOptions
:キャストで考慮するコライダー型を指定します。
シェイプクエリ
Quantumは、2種類のシェイプクエリをサポートしています。
- ShapeOverlap
- ShapeCasts
これらは、Quantumでサポートされているすべての動的シェイプに使用できます。
備考: シェイプクエリを実行するためにCoumpoundShape
を使用できます。詳細は「Shape Config」ページをご覧ください。
シェイプオーバーラップ
OverlapShape()
はHitCollection
を返します。必要なパラメーターは次の通りです。
- 中心位置(
FPVector2
/FPVector3
) - 回転(
FP
、3Dの場合はFPQuaternion
) - シェイプ(
Shape2D
/Shape3D
-PhysicsCollider
のシェイプまたは呼び出し時に作成する)
C#
// For 2D
var hits = f.Physics2D.OverlapShape(FPVector2.Zero, FP._0, Shape2D.CreateCircle(FP._1));
for (int i = 0; i < hits.Count; i++){
var hit = hits[i];
}
// For 3D
var hits = f.Physics3D.OverlapShape(FPVector3.Zero, FPQuaternion.Identity, Shape3D.CreateSphere(1));
for (int i = 0; i < hits.Count; i++){
var hit = hits[i];
}
シェイプキャスト
ShapeCastAll()
はHitCollection
を返します。必要なパラメーターは次の通りです。
- 中心位置(
FPVector2
/FPVector3
) - シェイプの回転(
FP
、3Dの場合はFPQuaternion
) - シェイプのポインタ(
Shape2D*
/Shape3D*
-PhysicsCollider
のシェイプまたは呼び出し時に作成する) - ベクトルで表される距離と方向(
FPVector2
/FPVector3
)
C#
// For 2D
var shape = Shape2D.CreateCircle(FP._1);
var hits = f.Physics2D.ShapeCastAll(FPVector2.Zero, FP._0, &shape, FPVector2.One);
for (int i = 0; i < hits.Count; i++){
var hit = hits[i];
}
// For 3D
var shape = Shape3D.CreateSphere(1);
var hits = f.Physics3D.ShapeCastAll(FPVector3.Zero, FPQuaternion.Identity, &shape, FPVector3.One);
for (int i = 0; i < hits.Count; i++){
var hit = hits[i];
}
シェイプキャストは、独自のGJKアルゴリズムを使用します。初期のシェイプキャスト位置がコライダー内にある場合、QueryOptions
パラメーターのDetectOverlapsAtCastOrigin
フラグを有効にしないと、そのコライダーとの衝突を検知できません。このフラグを有効にすると、開始位置で追加のチェックが行われます。
GJKConfig
設定は、SimulationConfig
アセットのPhysics > GJKConfig
セクションにあります。この設定により、精度とパフォーマンスのバランスを取ることができますが、両者はトレードオフの関係にあります。デフォルトの値は、標準的なサイズのシェイプに合わせて調整されています。
Simplex Min/Max Bit Shift
:生データを徐々にシフトすることで、ボロノイ領域内の点の精度を向上させます。これによって、物理空間における位置の有効範囲を損なうことなく、縮退するケースを回避します。シェイプのスケールやシェイプ間の距離が非常に小さい場合は、値を増やすことを検討してください。Shape Cast Max Iterations
:ハード許容値以下の解を探す際に、アルゴリズムが実行される最大反復回数です。この値を増やすと、精度が向上する可能性がありますが、最悪ケースではパフォーマンスが低下します。Shape Cast Hard Tolerance
:このしきい値以下の反復結果(シェイプ間の最近接距離)を、終了条件として受け入れます。この値を減らすと、精度が向上する可能性がありますが、反復回数が増加します。Shape Cast Soft Tolerance
:最大反復回数以内にハード許容値以下の結果が見つからなかった場合でも、最良の結果がこのしきい値以下であれば、ヒット判定を返します。このしきい値を増やすと偽陽性率が増加し、減らすと偽陰性率が増加します。
ヒットのソート
HitCollection
を返すすべてのクエリはソートできます。
Sort()
:2DならFPVector2
、3DならFPVector3
を渡し、その点までの距離に基づいてコレクションをソートします。SortCastDistance()
:ShapeCast
クエリの結果をソートするために使用します。引数は不要で、キャスト距離に基づいてコレクションをソートします。
オプション
すべてのクエリは、ブロードフェーズ版を含め、QueryOptions
を使用して処理やその結果をカスタマイズできます。
QueryOptions
は、考慮するオブジェクト型や計算する情報をフィルタリングするマスクを作成します。これらは、バイナリ演算子|
を使用して組み合わせることができます。
ヒットの法線
最良のパフォーマンスを提供するため、すべてのデフォルトクエリは、2つのシェイプが重なっているかどうかのみをチェックします。デフォルトでは、大半のクエリはヒットの点や法線などを取得しません。
追加情報を取得するには、より計算が必要となるため、追加のオーバーヘッドが発生します。そのため、QueryOptions
パラメーターにComputeDetailedInfo
を明示的に指定する必要があります。これによって、以下の計算が実行されるようになります。
- point(点)
- normal(法線)
- penetration(めり込み)
レイキャストとトライアングルのチェックにおいて、法線は常にトライアングルの法線になります。この法線はトライアングルのデータにキャッシュされているため、追加の計算は行われません。
ヒットのフィルタリング
以下のQueryOptions
によって、クエリで使用されるマスクを定義できます。パラメーターとして指定されたQueryOptions
に一致しないオブジェクトはスキップされ、QueryOptions
に一致するオブジェクトのみが評価されて結果として返されます。
HitStatics:静的コライダーのみにヒットします。
HitKinematics:以下のいずれかの条件を満たすエンティティにヒットします。
PhysicsCollider
を持ち、PhysicsBody
を持たないエンティティPhysicsCollider
を持ち、無効なPhysicsBody
を持つエンティティPhysicsCollider
を持ち、キネマティックなPhysicsBody
を持つエンティティ
HitDynamics:「有効」かつ「非キネマティック」な
PhysicsBody
を持つエンティティのみヒットします。HitTriggers:トリガーコライダーにヒットするため、他のフラグと組み合わせて使用します。
HitAll:トリガーコライダーを含め、すべての静的コライダーと
PhysicsCollider
を持つエンティティにヒットします。HitSolids :トリガーコライダーを除き、すべての静的コライダーと
PhysicsCollider
を持つエンティティにヒットします。
デフォルトでは、クエリはHitAll
オプションを使用します。他のオプションを選択することで計算を節約できます。
ブロードフェーズクエリ
Quantumではオプションとして、物理システム中に物理クエリ(レイキャストやオーバーラップ)を解決できます。そのためには次の手順が必要です。
- システムを作成する
SystemsConfig
アセットのCore.PhysicsSystem
前にシステムを追加するCore.PhysicsSystem
後に実行する任意のシステムで情報を取得する
このセットアップにより、物理ステップの並行処理の恩恵を受けるため、通常のクエリよりも非常に高速です。

備考: ブロードフェーズクエリは、ソルバーが実行される前に物理エンジンにスケジュール/注入されることから、「注入されたクエリ(Injected Queries)」や「スケジュールクエリ(Scheduled Queries)」とも呼ばれます。
クエリの注入
物理演算前に実行される任意のメインスレッドシステムから、クエリを注入することが可能です。クエリの注入はPhysicsQueryRef
を返し、これを保持して、物理システム実行後に結果を取得できます。ブロードフェーズクエリの結果は、クエリが注入されたフレームと同じフレームで取得されることを想定しているため、PhysicsQueryRef
はロールバック可能なフレームデータ外にも保存できます。
C#
namespace Quantum
{
public unsafe struct ProjectileFilter
{
public EntityRef EntityRef;
public Transform3D* Transform;
public Projectile* Component;
}
public unsafe class ProjectileHitQueryInjectionSystem : SystemMainThread
{
public override void Update(Frame frame)
{
var projectileFilter = frame.Unsafe.FilterStruct<ProjectileFilter>();
var projectile = default(ProjectileFilter);
while (projectileFilter.Next(&projectile))
{
projectile.Component->PathQueryRef = frame.Physics3D.AddRaycastQuery(
projectile.Transform->Position,
projectile.Transform->Forward,
projectile.Component->Speed * frame.DeltaTime);
var spec = frame.FindAsset<WeaponSpec>(projectile.Component->WeaponSpec.Id);
projectile.Component->DamageZoneQueryRef = frame.Physics3D.AddOverlapShapeQuery(
projectile.Transform->Position,
projectile.Transform->Rotation,
spec.AttackShape.CreateShape(frame),
spec.AttackLayers);
}
}
}
}
重要: AddXXXQuery
によって返されるPhysicsQueryRef
は、後でクエリ結果を取得するために絶対に必要です。そのため、後でヒットを処理する必要があるエンティティのコンポーネントに保存することを推奨します。
クエリ結果の取得
クエリ結果は、コアの物理システム実行後の任意のシステムから取得できます。結果(HitCollection*
)を取得するには、以前に保存したインデックスをFrame.Physics.GetQueryHits()
/.TryGetQueryHits()
に渡してください。
無効なPhysicsQueryRef
(例:異なるフレームで注入されたクエリ)で結果を取得しようとすると、GetQueryHits
では例外が投げられ、TryGetQueryHits
ではfalse
が返されます。
C#
using Photon.Deterministic;
namespace Quantum
{
public unsafe class ProjectileHitRetrievalSystem : SystemMainThread
{
public override void Update(Frame frame)
{
var projectileFilter = frame.Unsafe.FilterStruct<ProjectileFilter>();
var projectile = default(ProjectileFilter);
while (projectileFilter.Next(&projectile))
{
if (frame.Physics3D.TryGetQueryHits(projectile.Component->PathQueryRef, out var hitsOnTrajectory) == false || hitsOnTrajectory.Count <= 0)
{
projectile.Transform->Position =
projectile.Transform->Rotation *
projectile.Transform->Forward *
projectile.Component->Speed * frame.DeltaTime;
continue;
}
if (frame.Physics3D.TryGetQueryHits(projectile.Component->DamageZoneQueryRef, out var damageZoneHits))
{
for (int i = 0; i < damageZoneHits.Count; i++)
{
// 被ダメージロジックの適用
}
}
}
}
}
}
これに加えて、Frame.Physics
から利用可能なpublic bool GetAllQueriesHits(out HitCollection* queriesHits, out int queriesCount)
を呼び出すことで、すべてのブロードフェーズクエリ結果を取得可能です。
備考
ブロードフェーズクエリを使用する際には、留意すべき重要な点がいくつか存在します。
- 大量のデータ(例:弾幕)に対しては、パフォーマンスが約20倍向上します。
- クエリは、物理システムが実行される直前のフレームのステートに基づきます。
- ブロードフェーズクエリは、フレーム間では引き継がれません。つまり、物理演算前のフレーム開始時に注入する必要があります。物理演算後に注入されたブロードフェーズクエリは、決して結果を返しません。これはQuantumの物理エンジンがステートレスであるためです。
CCDのエミュレート
Quantumの物理エンジンはステートレスであるため、連続的衝突判定(CCD)には過剰なコストがかかります。ステートレスな物理エンジンでCCD動作をエミュレートするための解決策は、レイキャストやシェイプオーバーラップを使用して、1フレーム分で予想される移動を拡張することです。
このトピックは、弾のような高速で動くエンティティを組み合わせて、通常取り上げられます。高速で動くオブジェクトのサイズに応じて、次のアプローチを取ることを推奨します。
velocity * deltaTime
の長さで、移動方向に短いレイを飛ばす- 単一のシェイプオーバーラップを実行する(複合的なシェイプも可能)
いずれの解決策でも、100%正確なCCDを再現しつつ、全体的なパフォーマンスを大幅に向上させます。さらにパフォーマンスを向上させるために、ブロードフェーズクエリとの併用が可能です。
Back to top