This document is about: QUANTUM 3
SWITCH TO

10 - Collision Detection

碰撞系統

Quantum是基於ECS的,因此碰撞事件不是基於每個實體的,而是系統可以聽取的全域訊號。效能和便利性的一種常見模式是讓一個單一系統接收所有碰撞,然後按類型篩選它們,並相應地叫用其他訊號。

在實作碰撞系統之前,創建一個新的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);
}

程式碼的第一部分忽略了拋射物撞擊本船時的碰撞。而第二部分會破壞拋射物。可以新增額外的邏輯來摧毀被擊中的敵方船。

拋射物與小行星碰撞

當拋射體與小行星碰撞時,小行星應該被分成多個較小的小行星。

AsteroidsWaveSpawnerSystem中已經有一個生成小行星的函數,可以透過將其轉化為訊號來重新利用,也可以生成下層小行星。

將以下訊號新增到AsteroidsAsteroid.qtn中。也要向元件中新增一個ChildAsteroid檔案。

C#

component AsteroidsAsteroid
{
    asset_ref<EntityPrototype> ChildAsteroid;
}

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

ChildAsteroid欄位在運行時不會被修改,因此從科技上講,它更適合設置檔。然而,創建設置檔並將其連結到僅包含單一欄位的元件是多餘的,只會導致效能下降和增加程式碼複雜性。

打開AsteroidsWaveSpawnerSystem並將其繼承自ISignalSpawnAsteroid

EntityRef parent參數新增到SpawnAsteroid函數中,以便正確實作訊號。在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中的兩個訊號正常工作,它必須實作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並將AsteroidsCollisionSystem新增到AsteroidsSystemConfig中。

下層小行星預製件

AsteroidLarge預製件拖動到場景中。在EntityPrototype中將Circle Radius調整為0.5。同時,將下層模型縮小到(0.6, 0.6, 0.6)。將其重命名為AsteroidSmall。將AsteroidSmall物件拖回 Resources資料夾,並在快顯視窗中選擇Prefab Variant,為小行星創建一個較小的預製件變體。然後將其從場景中刪除。

AsteroidSmall VariantEntityPrototype拖動到AsteroidLarge預製件的EntityPrototypeChild Asteroid欄位中。

啟用碰撞回調

程式碼碰撞現在已完全設定。但是,預設下,出於效能原因,Quantum不會叫用實體之間的碰撞回調。可以透過修改EntityPrototypePhysicsCollider2D部分的Callback Flags來啟用它們。

AsteroidLargeAsteroidsProjectileAsteroidsShip預製件上啟用OnDynamicCollisionEnter回調標誌。(無需調整AsteroidSmall,因為它是AsteroidLarge的預製件變體,因此會自動調整)。

進入遊玩模式,測試多種碰撞場景。應發生以下情況:

  • 當拋射物撞擊小行星時,小行星被摧毀並分裂成兩顆小行星。拋射物也被摧毀了。
  • 當船與小行星相撞時,船就被摧毀了。
  • 當射擊另一艘船時,拋射物被摧毀。

修復波生成

現時,只有第一波小行星正在被生成。要解決此問題,請在AsteroidWaveSpawnerSystem中新增以下函數:

C#

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

並讓它實作ISignalOnComponentRemoved<AsteroidsAsteroid>介面。每當從實體中移除元件時(當實體被銷毀時,所有元件也會從中移除),都會調用此訊號。

有了這個,小行星的核心遊戲機制就實作了。

Gameplay
Back to top