PrismatiXEngine
打包與更新

存檔與版本更新

使用存讀檔 API,保留故事 ID,並讓新版遊戲讀取舊存檔。

English

Preview 與 Player

Preview 的 Save/Load 是暫存進度,重建或關閉後會清除。測試關閉遊戲後再讀檔,或測試版本升級時,使用打包後的 Player。

初期可以沿用 save.openload.openbacklog.open 等預設 Action。製作自訂介面後,再接上相同的存檔 API。

使用存檔欄位

以下片段放在具有 persistence 能力的 Extension callback 中:

const saved = ctx.saves.save(3);
if (!saved) {
  ctx.raw.log('save', 'Slot 3 could not be saved');
}

const information = ctx.saves.query(3);
ctx.raw.log('save information', JSON.stringify(information));
const slots = ctx.saves.list();
ctx.raw.log('available slots', JSON.stringify(slots));

load(3) 讀取第 3 個欄位,autosave() 自動存檔,delete(3) 刪除該欄位。刪除或覆寫前,介面應清楚顯示操作對象,並處理 API 回報的失敗結果。

存檔包含劇情位置、變數、UI、Stage,以及引擎管理的非同步執行紀錄。自訂系統應透過 API 參與保存,避免直接修改未知的存檔格式。

版本與 ID

修改既有內容時,保留故事 [id ...]、場景 ID、資源 ID 和 state provider ID。三個版本欄位用途不同:version 是作品發行版本,contentVersion 是內容版本,saveVersion 是存檔格式版本。

重新命名變數、改型別、移除資源或調整劇情結構後,需要檢查舊存檔。保留台詞 ID 能穩定定位,但不能代替其他資料的相容性處理。

範例:把 score 改成 points

先建立測試基準:以 contentVersion: "tutorial-v1"saveVersion: 1 打包遊戲並存檔。新版將 score 改成 points,同時更新 catalog 和程式中的引用。

建立 Content/Migrations/v1-v2.pxsavemigration

{
  "format": "PrismatiXSaveMigration",
  "schemaRevision": 2,
  "id": "tutorial.v1-v2",
  "from": {"contentVersion": "tutorial-v1", "saveVersion": 1},
  "to": {"contentVersion": "tutorial-v2", "saveVersion": 2},
  "anchor": {"policy": "preserve"},
  "operations": [
    {"op": "renameVariable", "from": "score", "to": "points"}
  ]
}

在新版專案合併:

{
  "contentVersion": "tutorial-v2",
  "saveVersion": 2,
  "saveMigrations": [
    {
      "id": "tutorial.v1-v2",
      "from": {"contentVersion": "tutorial-v1", "saveVersion": 1},
      "to": {"contentVersion": "tutorial-v2", "saveVersion": 2},
      "asset": "Content/Migrations/v1-v2.pxsavemigration"
    }
  ]
}

fromto 要與實際版本一致,新 catalog 也要接受 pointsanchor.policy: preserve 適用於仍能沿用故事定位的更新;位置改變時,使用 map 並填入實際的 runtimeDocumentIdsourceIdoperationId 對應。

其他遷移操作有 removeVariablesetVariablerenameRoutesetScript。先用舊存檔副本測試;CLI 的 migrate 是另一個處理作者文件的工具,不是這份存檔遷移設定。

更新 Extension 自有資料

假設同一個 provider 在 v1 保存 {score: number},v2 改為 {points: number}

type PluginState = { points: number };
let pluginState: PluginState = { points: 0 };

ctx.state.registerProvider<PluginState>('tutorial.points', 2, {
  capture: () => ({ ...pluginState }),
  restore: (saved) => {
    pluginState = { ...saved };
  },
  migrate: (saved, fromVersion) => {
    if (fromVersion === 1 && saved !== null && typeof saved === 'object' && !Array.isArray(saved)) {
      const value = (saved as { score?: unknown }).score;
      if (typeof value === 'number' && Number.isFinite(value)) {
        return { points: value };
      }
    }
    throw new Error('Unsupported tutorial.points state migration');
  },
});

舊版也必須使用 tutorial.points 這個 provider ID。這個例子不是把其他 provider 改名,而是更新同一份資料的格式。遇到未支援的舊格式時回報錯誤,避免讀檔後意外清空進度。

capturerestoremigrate 必須同步處理 JSON 資料,期間不可播放媒體或呼叫引擎操作。

更新前的測試

保留每個已發布版本的 Player 和測試存檔,檢查對話、選項等待、動畫、Extension 的 await、語言切換、永久解鎖和 backlog 回溯。

還原進度可能重新執行部分 callback,要確認狀態沒有遺漏,也沒有重複產生不該重複的副作用。全螢幕轉場會取消後回復 UI/Stage 狀態;媒體不保證精確到每個音訊取樣或 GPU 影格。

需要檢查單一存檔時,先看 prismatix inspect-save --help,再提供相符的 package 或所需 secret。問題報告使用測試存檔,避免附上玩家資料和密鑰。

實作參考:遷移格式專案設定Runtime SDK存檔與回放

On this page