Map Baking
概述
在 Quantum 中,地圖由兩部分組成:Unity 場景(Unity Scene)和 QuantumMapData。
Unity 場景在加載地圖時被加載,包含地圖的可見元素(如 EntityViews)。這些 Unity 遊戲物件負責渲染遊戲中的實體。
另一方面,QuantumMapData 包含地圖的相關信息,用於驅動確定性模擬以實現遊戲玩法。
地圖烘焙
QuantumSampleGame 場景已包含一個 QuantumMapData 組件,該組件引用了一個名為SampleMap
的地圖資產,該資產位於場景的 Resources 文件夾中。
創建新遊戲場景時,有幾種方法可以創建和填充新的 QuantumMapData 組件:
- 從頂部工具欄創建新的 Quantum 場景:
Quantum/Setup/Create New Quantum Scene
; - 或通過
Quantum/Setup/Add Quantum To Current Scene
將已創建的場景轉換為 Quantum 場景; - 或手動進行地圖設置:創建帶有
QuantumMapData
組件的遊戲物件,然後在偏好位置通過右鍵菜單Create/Quantum/Assets/Map
創建新的Map
資產。
在項目中的QuantumEditorSettings
資產的Editor Features
部分,可以啟用或禁用場景保存、播放模式更改和應用程式構建時的自動地圖烘焙功能。
雖然在某些場景下禁用自動地圖烘焙可能有用,但需要注意手動地圖烘焙可能耗時且可能在流程中引入人為錯誤。因此,對於大多數項目,通常建議保持自動地圖烘焙處於啟用狀態。

要在 Quantum 中烘焙場景,場景中必須有一個帶有QuantumMapData
組件的遊戲物件。此外,如果存在導航網格(navmesh),還需要QuantumMapNavMeshUnity
組件。Quantum SDK 提供的QuantumSampleGame
場景已包含必要的QuantumMapData
設置。
QuantumMapData
腳本組件(MonoBehaviour)還包含一些按鈕,可用於在需要時手動觸發烘焙過程。Bake All Mode
可以調整以跳過烘焙過程中的某些步驟。

QuantumMapData
當 Quantum 烘焙地圖時,它會生成一個Map
資產,該資產可在Resources/DB/Configs
下找到。不過,如有需要,也可以將這些資產移動到其他位置。
需要注意的是,Map
欄位的值通常不應手動更改,因為重新烘焙地圖會覆蓋任何手動更改。
地圖資產中的User Asset
欄位可用於向地圖中注入任何資產,然後可以在模擬端檢索該資產。這可以通過在檢查器中手動鏈接資產,或通過自定義地圖烘焙回調分配資產來實現。以下的示例展示了這一點。
Quantum編輯器設置BakeMapData
Quantum 中的地圖烘焙回調允許用戶在 QuantumMapData 烘焙過程中注入自定義步驟。要實現地圖烘焙回調,可以創建一個派生自MapDataBakerCallback
的類。這還需要用自定義屬性標記程序集,這可以在單個文件中通過添加[assembly: Quantum.QuantumMapBakeAssemblyAttribute]
來完成。這會標記整個程序集,因此無需在多個文件中添加相同的屬性。以下是一個示例實現:
C#
[assembly: Quantum.QuantumMapBakeAssemblyAttribute]
namespace Quantum
{
public class ExampleMapDataBaker : MapDataBakerCallback
{
public override void OnBeforeBake(QuantumMapData data)
{
}
public override void OnBake(QuantumMapData data)
{
}
}
}
Quantum 中的MapDataBakerCallback
屬性可用於指定多個自定義地圖烘焙回調的執行順序。要使用此屬性,將其添加到自定義地圖烘焙回調類並指定invokeOrder
值。值越小,回調在烘焙過程中執行得越早。
示例如下:
C#
[MapDataBakerCallback(invokeOrder:5)]
public class ExampleMapDataBaker : MapDataBakerCallback
OnBeforeBake
在任何其他 MapData 烘焙操作執行之前調用。它允許調整 Unity 場景,例如添加或移除組件。
OnBake
在所有內置的地圖烘焙步驟執行完畢後,但在Map
資產保存之前調用。這允許在訪問所有烘焙地圖數據的同時調整Map
。
如果需要,還可以重寫其他虛擬回調:OnBeforeBakeNavmesh
, OnCollectNavMeshBakeData
, OnCollectNavMeshes
, OnBakeNavMesh
以及提供烘焙標誌和觸發烘焙原因的OnBeforeBake
重載。
向地圖添加自定義數據
除了碰撞體和導航網格外,遊戲地圖可能還包含其他對遊戲玩法很重要的元素。當需要包含不可變數據時,不一定要在地圖的實體中添加這些數據。在這種情況下,可以使用 MapData Unity 組件中的 User Asset 欄位將任何形式的數據傳遞到模擬中。這使得可以在地圖中包含諸如物件位置、遊戲規則等信息。分配給 User Asset 欄位的數據可以在模擬中訪問和使用。
示例:生成點(Spawn Points)
烘焙地圖時使用自定義數據的一個示例是在地圖中包含生成點。設計師可以在 Unity 場景中放置這些生成點並自由移動它們。
創建一個新的 Quantum 資產類來存儲生成點數據:
C#
namespace Quantum
{
using System;
using Photon.Deterministic;
public unsafe class MapCustomData : AssetObject
{
[Serializable]
public struct SpawnPointData
{
public FPVector3 Position;
public FPQuaternion Rotation;
}
public SpawnPointData DefaultSpawnPoint;
public SpawnPointData[] SpawnPoints;
public void SetEntityToSpawnPoint(Frame frame, EntityRef entity, Int32? index)
{
var transform = frame.Unsafe.GetPointer<Transform3D>(entity);
var spawnPoint = index.HasValue && index.Value < SpawnPoints.Length ? SpawnPoints[index.Value] : DefaultSpawnPoint;
transform->Position = spawnPoint.Position;
transform->Rotation = spawnPoint.Rotation;
}
}
}
然後,創建一個新類來處理生成點的烘焙:
C#
namespace Quantum
{
using UnityEditor;
using UnityEngine;
public class SpawnPointBaker : MapDataBakerCallback
{
public override void OnBeforeBake(QuantumMapData data)
{
}
public override void OnBake(QuantumMapData data)
{
var customData = QuantumUnityDB.GetGlobalAssetEditorInstance<Map>(data.Asset.UserAsset.Id);
var spawnPoints = GameObject.FindGameObjectsWithTag("SpawnPoint");
if (customData == null || spawnPoints.Length == 0)
{
return;
}
var defaultSpawnPoint = spawnPoints[0];
if (customData.DefaultSpawnPoint.Equals(default(MapCustomData.SpawnPointData)))
{
customData.DefaultSpawnPoint.Position = defaultSpawnPoint.transform.position.ToFPVector3();
customData.DefaultSpawnPoint.Rotation = defaultSpawnPoint.transform.rotation.ToFPQuaternion();
}
customData.SpawnPoints = new MapCustomData.SpawnPointData[spawnPoints.Length];
for (var i = 0; i < spawnPoints.Length; i++)
{
customData.SpawnPoints[i].Position = spawnPoints[i].transform.position.ToFPVector3();
customData.SpawnPoints[i].Rotation = spawnPoints[i].transform.rotation.ToFPQuaternion();
}
#if UNITY_EDITOR
EditorUtility.SetDirty(customData);
#endif
}
}
}
這個烘焙器相對簡單。它收集場景中所有帶有SpawnPoint
標籤的遊戲物件,提取它們的位置和旋轉,然後將這些信息保存到自定義資產中。最後,將資產標記為「髒污」(dirty),以便將更改保存到磁盤。
要使用自定義數據,請向場景中添加帶有 SpawnPoint 標籤的遊戲物件。然後創建一個MapCustomDataAsset
並將其分配給地圖的UserAsset
欄位。要在模擬中使用生成點,請使用以下代碼:
C#
var data = frame.FindAsset<MapCustomData>(frame.Map.UserAsset);
data.SetEntityToSpawnPoint(f, entity, spawnPointIndex);
編輯時烘焙
有關編輯時烘焙的信息,請參閱地圖烘焙頁面。
運行時烘焙
可以在運行時烘焙地圖。這對於程序生成的地圖或編輯時未知的地圖非常有用。
Quantum 啟動前
一種方法是在啟動 Quantum 會話之前烘焙新地圖。使用這種方法,有兩個選項:
- 讓所有客戶端在啟動會話之前以確定性方式烘焙地圖。
- 此方法通常用於地圖生成是確定性的情況。
- 此方法較複雜,因為需要確保所有客戶端以相同的方式烘焙地圖。這可以通過共享用於地圖生成的種子(seed)並確保代碼是確定性的來實現。
- 這節省了帶寬,因為只需向客戶端發送種子,它們就可以自己生成地圖。
如何在運行時向QuantumUnityDB
添加靜態資產:
C#
var generatedMap = new Map();
// generate...
// add the map to the QuantumUnityDB
QuantumUnityDB.Global.AddAsset(generatedMap);
- 讓一個客戶端烘焙地圖並與其他客戶端共享。
- 這種方式更簡單,因為可以重用在編輯器中使用的相同地圖烘焙代碼。
- 但是,由於編輯器地圖烘焙不是確定性的,需要將資產設為動態資產,並在啟動 Quantum 之前將其添加到初始動態資產中。這將使地圖對所有客戶端可用。
Quantum 會話期間
此方法更複雜且高度取決於遊戲本身。
一些方法:
讓一個客戶端烘焙地圖並通過命令與其他客戶端共享。
- 這允許重用在編輯器中使用的相同地圖烘焙代碼。
- 地圖大小受限於命令大小(或者需要手動拆分和管理)。
讓所有客戶端在會話期間以確定性方式烘焙地圖。
- 如果地圖生成是確定性的,可以讓所有客戶端在會話期間烘焙地圖。
- 這實際上與在會話之前烘焙地圖相同,但資產仍然需要是動態的,因為後加入的客戶端可能錯過烘焙事件。
注意事項
在運行時烘焙地圖有一些注意事項:
- Unity 編輯器中使用的地圖烘焙過程默認不是確定性的(由於浮點數到 FP 的轉換)。
- 在地圖資產添加到 AssetDB 後,不能添加或移除地圖實體條目。