This document is about: QUANTUM 3
SWITCH TO

Map Baking

概述

在 Quantum 中,地圖由兩部分組成:Unity 場景(Unity Scene)和 QuantumMapData。

Unity 場景在加載地圖時被加載,包含地圖的可見元素(如 EntityViews)。這些 Unity 遊戲物件負責渲染遊戲中的實體。

另一方面,QuantumMapData 包含地圖的相關信息,用於驅動確定性模擬以實現遊戲玩法。

地圖烘焙

QuantumSampleGame 場景已包含一個 QuantumMapData 組件,該組件引用了一個名為SampleMap的地圖資產,該資產位於場景的 Resources 文件夾中。

創建新遊戲場景時,有幾種方法可以創建和填充新的 QuantumMapData 組件:

  1. 從頂部工具欄創建新的 Quantum 場景:Quantum/Setup/Create New Quantum Scene;
  2. 或通過 Quantum/Setup/Add Quantum To Current Scene將已創建的場景轉換為 Quantum 場景;
  3. 或手動進行地圖設置:創建帶有QuantumMapData組件的遊戲物件,然後在偏好位置通過右鍵菜單Create/Quantum/Assets/Map創建新的Map資產。

在項目中的QuantumEditorSettings資產的Editor Features部分,可以啟用或禁用場景保存、播放模式更改和應用程式構建時的自動地圖烘焙功能。

雖然在某些場景下禁用自動地圖烘焙可能有用,但需要注意手動地圖烘焙可能耗時且可能在流程中引入人為錯誤。因此,對於大多數項目,通常建議保持自動地圖烘焙處於啟用狀態。

QuantumEditorSettings MapBaking
QuantumEditorSettings 地圖烘焙

要在 Quantum 中烘焙場景,場景中必須有一個帶有QuantumMapData組件的遊戲物件。此外,如果存在導航網格(navmesh),還需要QuantumMapNavMeshUnity組件。Quantum SDK 提供的QuantumSampleGame場景已包含必要的QuantumMapData設置。

QuantumMapData腳本組件(MonoBehaviour)還包含一些按鈕,可用於在需要時手動觸發烘焙過程。Bake All Mode可以調整以跳過烘焙過程中的某些步驟。

MapData Component
QuantumMapData組件

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 會話之前烘焙新地圖。使用這種方法,有兩個選項:

  1. 讓所有客戶端在啟動會話之前以確定性方式烘焙地圖。
    • 此方法通常用於地圖生成是確定性的情況。
    • 此方法較複雜,因為需要確保所有客戶端以相同的方式烘焙地圖。這可以通過共享用於地圖生成的種子(seed)並確保代碼是確定性的來實現。
    • 這節省了帶寬,因為只需向客戶端發送種子,它們就可以自己生成地圖。

如何在運行時向QuantumUnityDB添加靜態資產:

C#

var generatedMap = new Map();

// generate...

// add the map to the QuantumUnityDB

QuantumUnityDB.Global.AddAsset(generatedMap);
  1. 讓一個客戶端烘焙地圖並與其他客戶端共享。
    • 這種方式更簡單,因為可以重用在編輯器中使用的相同地圖烘焙代碼。
    • 但是,由於編輯器地圖烘焙不是確定性的,需要將資產設為動態資產,並在啟動 Quantum 之前將其添加到初始動態資產中。這將使地圖對所有客戶端可用。

Quantum 會話期間

此方法更複雜且高度取決於遊戲本身。

一些方法:

  1. 讓一個客戶端烘焙地圖並通過命令與其他客戶端共享。

    • 這允許重用在編輯器中使用的相同地圖烘焙代碼。
    • 地圖大小受限於命令大小(或者需要手動拆分和管理)。
  2. 讓所有客戶端在會話期間以確定性方式烘焙地圖。

    • 如果地圖生成是確定性的,可以讓所有客戶端在會話期間烘焙地圖。
    • 這實際上與在會話之前烘焙地圖相同,但資產仍然需要是動態的,因為後加入的客戶端可能錯過烘焙事件。

注意事項

在運行時烘焙地圖有一些注意事項:

  • Unity 編輯器中使用的地圖烘焙過程默認不是確定性的(由於浮點數到 FP 的轉換)。
  • 在地圖資產添加到 AssetDB 後,不能添加或移除地圖實體條目。
Back to top