This document is about: QUANTUM 3
SWITCH TO

10 - 衝突判定

Collision System

QuantumはECSベースのため、衝突イベントはエンティティ毎ではなく、システムが購読できるグローバルなシグナル毎になります。パフォーマンスと利便性を考慮すると、あらゆる衝突を検知するシステムを1つ用意して、型によって衝突をフィルタリングし、対応するシグナルを発生させます。

衝突システムを実装する前に、新しいAsteroidsAsteroid.qtnを作成し、以下のコードを追加してください。

C#

component AsteroidsAsteroid
{
}

この空タグコンポーネントは、AsteroidsLargeプレハブに追加します。

次に、新しいC#スクリプトを作成し、AsteroidsCollisionSystemと名付けます。

そして、以下のコードを追加してください。

C#

using UnityEngine.Scripting;

namespace Quantum.Asteroids
{
  [Preserve]
  public unsafe class AsteroidsCollisionsSystem : SystemSignalsOnly, ISignalOnCollisionEnter2D
  {
    public void OnCollisionEnter2D(Frame f, CollisionInfo2D info)
    {
      // Projectile is colliding with something
      if (f.Unsafe.TryGetPointer<AsteroidsProjectile>(info.Entity, out var projectile))
      {
        if (f.Unsafe.TryGetPointer<AsteroidsShip>(info.Other, out var ship))
        {
            // Projectile Hit Ship
        }
        else if (f.Unsafe.TryGetPointer<AsteroidsAsteroid>(info.Other, out var asteroid))
        {
          // projectile Hit Asteroid
        }
      }
      
      // Ship is colliding with something
      else if (f.Unsafe.TryGetPointer<AsteroidsShip>(info.Entity, out var ship))
      {
        if (f.Unsafe.TryGetPointer<AsteroidsAsteroid>(info.Other, out var asteroid))
        {
          // Asteroid Hit Ship
        }
      }
    }
  }
}

このコードは、グローバルな衝突シグナルを購読して、エンティティの型によってフィルタリングします。

次に、AsteroidsCollisionSignals.qtnを作成して、以下のシグナルを追加してください。

C#

signal OnCollisionProjectileHitShip(CollisionInfo2D info, AsteroidsProjectile* projectile, AsteroidsShip* ship);

signal OnCollisionProjectileHitAsteroid(CollisionInfo2D info, AsteroidsProjectile* projectile, AsteroidsAsteroid* asteroid);

signal OnCollisionAsteroidHitShip(CollisionInfo2D info, AsteroidsShip* ship, AsteroidsAsteroid* asteroid);

弾と船との衝突

弾に関するインタラクションはAsteroidsProjectileSystemに実装します。システムを開き、以下のコードを追加してください。

C#

public void OnCollisionProjectileHitShip(Frame f, CollisionInfo2D info, AsteroidsProjectile* projectile, AsteroidsShip* ship)
{
    if (projectile->Owner == info.Other)
    {
        info.IgnoreCollision = true;
        return;
    }
    
    f.Destroy(info.Entity);
}

コードの最初の部分で、自分自身の船が発射した弾との衝突を無視しています。そして2つ目の部分で、それ以外の弾を削除します。ここから、弾に当たった敵の船を破壊するロジックを追加することもできます。

弾とアステロイドとの衝突

弾がアステロイドに当たった場合は、アステロイドを複数の小さなアステロイドに分裂させます。

AsteroidsWaveSpawnerSystemには既にアステロイドをスポーンする関数があるため、シグナルによって調整すれば、子アステロイドのスポーンにも再利用できます。

AsteroidsAsteroid.qtnに以下のシグナルを追加して、コンポーネントにはChildAsteroidを追加します。

C#

component AsteroidsAsteroid
{
    asset_ref<EntityPrototype> ChildAsteroid;
}

signal SpawnAsteroid(AssetRef<EntityPrototype> childPrototype, EntityRef parent);

ChildAsteroidフィールドは実行時に変更しないため、技術的には設定ファイルを使用するのに適しています。しかし、フィールドを1つだけ持つコンポーネントのために設定ファイルを作成して紐づけるのはやりすぎで、パフォーマンスが落ちたりコードが複雑になってしまうだけです。

AsteroidsWaveSpawnerSystemを開き、ISignalSpawnAsteroidインターフェースを実装してください。

SpawnAsteroid関数にEntityRef parentパラメーターを追加して、正しくシグナルを実装できるようにします。SpawnAsteroidWave関数のSpawnAsteroid()を呼び出している部分では、親エンティティとしてEntityRef.Noneを渡します。

最後にこのコードを、

C#

asteroidTransform->Position = GetRandomEdgePointOnCircle(f, config.AsteroidSpawnDistanceToCenter);

以下のコードに置き換えてください。

C#

if (parent == EntityRef.None)
{
    asteroidTransform->Position = GetRandomEdgePointOnCircle(f, config.AsteroidSpawnDistanceToCenter);
}
else
{
    asteroidTransform->Position = f.Get<Transform2D>(parent).Position;
}

これで親エンティティが与えられた場合は、ランダムな位置ではなく親の位置からアステロイドがスポーンされるようになりました。

AsteroidsProjectileSystemに戻り、以下のコードを追加してください。

C#

public void OnCollisionProjectileHitAsteroid(Frame f, CollisionInfo2D info, AsteroidsProjectile* projectile, AsteroidsAsteroid* asteroid)
{
    if (asteroid->ChildAsteroid != null)
    {
        f.Signals.SpawnAsteroid(asteroid->ChildAsteroid, info.Other);
        f.Signals.SpawnAsteroid(asteroid->ChildAsteroid, info.Other);
    }

    f.Destroy(info.Entity);
    f.Destroy(info.Other);
}

そして、AsteroidsProjectileSystemで2つのシグナルを受け取れるように、ISignalOnCollisionProjectileHitShipISignalOnCollisionProjectileHitAsteroidインターフェースを実装してください。

C#

public unsafe class AsteroidsProjectileSystem : SystemMainThreadFilter<AsteroidsProjectileSystem.Filter>, ISignalAsteroidsShipShoot, ISignalOnCollisionProjectileHitShip, ISignalOnCollisionProjectileHitAsteroid

船とアステロイドとの衝突

AsteroidsShipSystemを開き、ISignalOnCollisionAsteroidHitShipインターフェースを実装します。

また、以下のコードを該当する関数に追加します。

C#

public void OnCollisionAsteroidHitShip(Frame f, CollisionInfo2D info, AsteroidsShip* ship, AsteroidsAsteroid* asteroid)
{
    f.Destroy(info.Entity);
}

これで船がアステロイドに衝突すると、単に船が破壊されるようになります。完全なゲームループでは、船をリスポーンするロジックを追加したり、すべての船が破壊されたらスコアを出してゲームを終了するような調整も入るでしょう。

AsteroidsCollisionsSystemに戻り、以下のようにシグナルを発生させましょう。

C#

// Projectile Hit Ship

C#

f.Signals.OnCollisionProjectileHitShip(info, projectile, ship);

C#

// Projectile Hit Asteroid

C#

f.Signals.OnCollisionProjectileHitAsteroid(info, projectile, asteroid);

C#

// Asteroid Hit Ship

C#

f.Signals.OnCollisionAsteroidHitShip(info, ship, asteroid);

Unityに戻り、AsteroidsSystemConfigAsteroidsCollisionSystemを追加します。

子アステロイドのプレハブ

シーンにAsteroidLargeプレハブをドラッグして、EntityPrototypeCircle Radius0.5に調整します。子要素のスケールは(0.6, 0.6, 0.6)にします。プレハブの名前をAsteroidSmallに変更して、Resourcesフォルダーにドラッグして戻し、ポップアップではPrefab Variantを選択して、小さなアステロイドのプレハブを作成しましょう。その後、シーンからプレハブを削除します。

そして、AsteroidLargeプレハブのEntityPrototypeChild Asteroidフィールドに、AsteroidSmall VariantEntityPrototypeをドラッグします。

衝突コールバックの有効化

コード側の衝突判定の設定は完了しました。しかしQuantumのデフォルトではパフォーマンス上の理由から、エンティティ間の衝突コールバックは呼び出されません。EntityPrototypePhysicsCollider2DCallback Flagsを変更して、これらを有効にすることができます。

AsteroidLargeAsteroidsProjectileAsteroidsShipプレハブのOnDynamicCollisionEnterを有効にしてください(AsteroidSmallAsteroidLargeのPrefab Variantなので、手動で調整する必要はありません)。

ゲームを再生して、以下のような衝突が発生するか確認してみましょう。

  • 弾がアステロイドに当たると、アステロイドは破壊され、2つのアステロイドに分裂する。弾は削除される。
  • 船がアステロイドに当たると、船は破壊される。
  • 他の船を撃つと、その船は破壊される。

ウェーブの修正

現在は、最初のウェーブでのみアステロイドがスポーンします。AsteroidWaveSpawnerSystemに以下の関数を追加して、これを修正します。

C#

public void OnRemoved(Frame f, EntityRef entity, AsteroidsAsteroid* component)
{
    if (f.ComponentCount<AsteroidsAsteroid>() <= 1)
    {
        SpawnAsteroidWave(f);
    }
}

さらにISignalOnComponentRemoved<AsteroidsAsteroid>インターフェースを実装します。このシグナルは、コンポーネントがエンティティから削除された時に呼び出されます(エンティティが削除されると、すべてのコンポーネントも同様に削除されます)。

これでアステロイドゲームのゲームプレイメカニクスの核が完成しました。

Gameplay
Back to top