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
中的兩個訊號正常工作,它必須實作ISignalOnCollisionProjectileHitShip
和ISignalOnCollisionProjectileHitAsteroid
,因此需要向其新增兩個介面。
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
預製件的EntityPrototype
的Child Asteroid
欄位中。
啟用碰撞回調
程式碼碰撞現在已完全設定。但是,預設下,出於效能原因,Quantum不會叫用實體之間的碰撞回調。可以透過修改EntityPrototype
的PhysicsCollider2D
部分的Callback Flags
來啟用它們。
在AsteroidLarge
、AsteroidsProjectile
和AsteroidsShip
預製件上啟用OnDynamicCollisionEnter
回調標誌。(無需調整AsteroidSmall
,因為它是AsteroidLarge
的預製件變體,因此會自動調整)。
進入遊玩模式,測試多種碰撞場景。應發生以下情況:
- 當拋射物撞擊小行星時,小行星被摧毀並分裂成兩顆小行星。拋射物也被摧毀了。
- 當船與小行星相撞時,船就被摧毀了。
- 當射擊另一艘船時,拋射物被摧毀。
修復波生成
現時,只有第一波小行星正在被生成。要解決此問題,請在AsteroidWaveSpawnerSystem
中新增以下函數:
C#
public void OnRemoved(Frame f, EntityRef entity, AsteroidsAsteroid* component)
{
if (f.ComponentCount<AsteroidsAsteroid>() <= 1)
{
SpawnAsteroidWave(f);
}
}
並讓它實作ISignalOnComponentRemoved<AsteroidsAsteroid>
介面。每當從實體中移除元件時(當實體被銷毀時,所有元件也會從中移除),都會調用此訊號。
有了這個,小行星的核心遊戲機制就實作了。
Back to top