Callbacks
簡介
Quantum 中的碰撞(Collision)和觸發器(Trigger)回調函式是通過 系統信號(system signals) 來處理的。要讓任何特定實體執行回調函式,需要完成兩個步驟:
- 為實體啟用特定類型的回調函式。
- 實現對應的信號。
在學習如何啟用回調函式和編寫回調程式碼之前,首先了解不同的回調類型以及觸發它們執行的原因是很重要的。
回調類型
碰撞和觸發器最初表現為物理引擎的碰撞檢測步驟所生成的「兩個實體對」或「實體與靜態物體對」。根據附加到實體的物理元件組合以及觸發器屬性(true/false)的值,下表說明了將會執行的回調類型。
實體 vs 實體
根據其元件構成,啟用物理功能的實體可分類為:
- 非觸發器碰撞體(Non-Trigger Collider): 具有非觸發器物理碰撞體,並可選擇性附加運動學物理主體(kinematic Physics Body)的實體。
- 觸發器碰撞體(Trigger Collider): 具有觸發器物理碰撞體,並可選擇性附加運動學物理主體的實體。
- 動態主體(Dynamic Body): 具有非觸發器物理碰撞體和動態(非運動學)物理主體的實體。
當碰撞對由兩個實體 A 和 B 組成時,以下是可能執行的回調函式(取決於每個實體所屬的組):
| 實體 A x B | 非觸發器碰撞體 | 觸發器碰撞體 | 動態主體 | 
|---|---|---|---|
| 非觸發器碰撞體 | 無 | OnTrigger | OnCollision | 
| 觸發器碰撞體 | OnTrigger | 無 | OnTrigger | 
| 動態主體 | OnCollision | OnTrigger | OnCollision | 
實體 vs 靜態碰撞體
另一方面,靜態碰撞體根據其IsTrigger屬性,可以是觸發器或非觸發器。
當碰撞對由一個實體和一個靜態碰撞體組成時,可能的組合如下:
| 元件(實體和靜態物體) | 非觸發器靜態碰撞體 | 觸發器靜態碰撞體 | 
|---|---|---|
| 非觸發器碰撞體 | 無 | OnTrigger | 
| 觸發器碰撞體 | OnTrigger | 無 | 
| 動態主體 | OnCollision | OnTrigger | 
在實體上啟用回調函式
可以控制每個實體啟用哪些回調類型(以及針對哪些類型的其他碰撞體)。這可以通過 Unity 中的 Quantum 實體原型 (Entity Prototype) 或程式碼中物理引擎 API 的 SetCallbacks 函式來完成,該函式接受實體和碰撞回調標誌。
通過實體原型
物理回調可以設置在任何具有 PhysicsCollider(2D/3D)的實體原型上。
 
    每個實體可以啟用多個回調。
注意: 在 Quantum 實體原型 上啟用回調僅為該特定實體 設置回調。仍然需要 在程式碼中實現對應的 信號。有關更多資訊,請參閱下面的 「回調信號」 部分。
此外,啟用碰撞時,請確保圖層在圖層矩陣中正確匹配。預設情況下,碰撞體的圖層對應於遊戲物件圖層,但可以更改為在Layer Source欄位中顯式定義。
通過程式碼
回調標誌是一個位元遮罩,可以通過位元運算指定多個回調類型,如下例所示。
以下程式碼片段啟用針對另一個動態實體的全套 OnTrigger 回調(OnDynamicTrigger、OnDynamicTriggerEnter 和 OnDynamicTriggerExit)。
C#
CallbackFlags flags = CallbackFlags.OnDynamicTrigger;
flags |= CallbackFlags.OnDynamicTriggerEnter;
flags |= CallbackFlags.OnDynamicTriggerExit;
// for 2D
f.Physics2D.SetCallbacks(entity, flags);
// for 3D
f.Physics3D.SetCallbacks(entity, flags);  
以下是基本回調標誌(只要物理引擎不再檢測到碰撞,對應的信號就會每幀調用):
- CallbackFlags.OnDynamicCollision
- CallbackFlags.OnDynamicTrigger
- CallbackFlags.OnStaticTrigger
以下是對應的進入 / 離開回調(可以獨立於上述標誌啟用):
- CallbackFlags.OnDynamicCollisionEnter 
- CallbackFlags.OnDynamicCollisionExit 
- CallbackFlags.OnDynamicTriggerEnter 
- CallbackFlags.OnDynamicTriggerExit 
- CallbackFlags.OnStaticCollisionEnter 
- CallbackFlags.OnStaticCollisionExit 
- CallbackFlags.OnStaticTriggerEnter 
- CallbackFlags.OnStaticTriggerExit 
必須按實體啟用回調是有意識的設計選擇,目的是使預設模擬盡可能快速。另请注意,與基本回調相比,進入 / 離開回調會消耗更多記憶體和 CPU 資源,因此為了使模擬盡可能精簡,請儘量避免使用這些回調。
回調信號
注意: 「實體 vs 實體」 和 「實體 vs 靜態物體」 對的碰撞和觸發器回調被組合到一個統一的信號 API 中。
以下是 2D 物理信號:
C#
namespace Quantum {
  public interface ISignalOnCollision2D : ISignal {
    void OnCollision2D(Frame frame, CollisionInfo2D info);
  }
  public interface ISignalOnCollisionEnter2D : ISignal {
    void OnCollisionEnter2D(Frame frame, CollisionInfo2D info);
  }
  public interface ISignalOnCollisionExit2D : ISignal {
    void OnCollisionExit2D(Frame frame, ExitInfo2D info);
  }
  public interface ISignalOnTrigger2D : ISignal {
    void OnTrigger2D(Frame frame, TriggerInfo2D info);
  }
  public interface ISignalOnTriggerEnter2D : ISignal {
    void OnTriggerEnter2D(Frame frame, TriggerInfo2D info);
  }
  public interface ISignalOnTriggerExit2D : ISignal {
    void OnTriggerExit2D(Frame frame, ExitInfo2D info);
  }
}
以下是 3D 物理信號:
C#
namespace Quantum {
  public interface ISignalOnCollision3D : ISignal {
    void OnCollision3D(Frame frame, CollisionInfo3D info);
  }
  public interface ISignalOnCollisionEnter3D : ISignal {
    void OnCollisionEnter3D(Frame frame, CollisionInfo3D info);
  }
  public interface ISignalOnCollisionExit3D : ISignal {
    void OnCollisionExit3D(Frame frame, ExitInfo3D info);
  }
  public interface ISignalOnTrigger3D : ISignal {
    void OnTrigger3D(Frame frame, TriggerInfo3D info);
  }
  public interface ISignalOnTriggerEnter3D : ISignal {
    void OnTriggerEnter3D(Frame frame, TriggerInfo3D info);
  }
  public interface ISignalOnTriggerExit3D : ISignal {
    void OnTriggerExit3D(Frame frame, ExitInfo3D info);
  }
}
要響應回調,請在至少一個活動系統中實現對應的信號介面(禁用的系統不會在其中執行任何信號):
C#
public class PickUpSystem : SystemSignalsOnly, ISignalOnTriggerEnter3D
{
    public void OnTriggerEnter3D(Frame frame, TriggerInfo3D info)
    {
        if (!frame.Has<PickUpSlot>(info.Entity)) return;
        if (!frame.Has<PlayerID>(info.Other)) return;
        var item = frame.Get<PickUpSlot>(info.Entity).Item;
        var itemAsset = frame.FindAsset<ItemBase>(item.Id);
        itemAsset.OnPickUp(frame, info.Other, itemAsset);
        frame.Destroy(info.Entity);
    }
}
上面的程式碼展示了實現信號時可以使用的另一種最佳化方式。繼承自 SystemSignalsOnly 可讓系統處理信號,而無需調度空的更新函式(這會不必要地增加任務系統的開銷)。
CollisionInfo
OnCollisionEnter和OnCollision的信號通過CollisionInfo結構提供有關碰撞實體的額外資訊。
接觸點
可以通過ContactPoints訪問接觸點的資訊。
- Average: 所有接觸點的平均值
- Count: 接觸點的數量
- Length: 包含所有接觸點的緩衝區
- First: 返回第一個三角形的第一個接觸點
如果只需要一個 / 任意一個接觸點,且不需要平均值,First有助於節省計算量。
ContactPoints也是一個迭代器。與網格碰撞時,可用於迭代所有三角形碰撞的接觸點。
C#
while(info.ContactPoints.Next(out var cp)) {
  Draw.Sphere(cp, radius);
}
球體 - 三角形 碰撞只有一個接觸點;因此,Average和ContactPoints[0]將返回相同的接觸點。其他類型的碰撞可能有更多接觸點。
網格碰撞
當一個實體與網格碰撞時,Count 和 Average 會考慮屬於該特定網格的所有三角形碰撞。這些碰撞的三角形會被組合成一個 CollisionInfo 結構,而不是為實體碰撞的每個三角形接收一個回調。
在網格碰撞的情況下,info.ContactNormal和info.Penetration將返回該網格三角形碰撞的平均值;這些數據也可以通過info.MeshTriangleCollisions.AverageNormal和 info.MeshTriangleCollisions.AveragePenetration獲取。
除了平均法線和穿透深度外,還可以迭代每個三角形碰撞並訪問特定資訊,例如三角形本身的數據。MeshTriangleCollisions也是一個迭代器;可用於迭代每個三角形的碰撞數據並檢索網格特定的碰撞數據。
C#
if (info.IsMeshCollision) {
  while(info.MeshTriangleCollisions.Next(out var triCollision)) {
    Draw.Ray(triCollision.Triangle->Center, triCollision.ContactNormal * triCollision.Penetration);
  }
}
其他信息和常見問題
使用碰撞回調時的實用資訊:
- 如果兩個實體設置了相同的回調,回調將被調用兩次 —— 每個關聯的實體各一次。兩次調用之間的唯一區別是 Entity 和 Other 的值會交換。
- 與觸發器和靜態碰撞體的碰撞不會計算法線 / 點 / 穿透深度數據。
- Unity 上的靜態碰撞體可以在其 Asset欄位中附加 Quantum DB 資產。將其轉換為預期的自定義資產類型是向靜態碰撞回調添加任意數據的好方法。