> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 游戏存档快速入门

> PlayFab 游戏存档快速入门介绍初始设置、SDK 调用以及读取和写入云存档数据，让您能够快速集成存档功能。

# 游戏存档快速入门

PlayFab 游戏存档通过将存档数据同步到云端，让玩家能够在设备之间无缝地继续进度。本快速入门指南将引导您为 XBOX 和 Windows 平台实现完整的游戏存档解决方案。

## 前提条件

在开始之前，请确保您已经：

* 完成了游戏存档的[接入](/services/playfab/player-progression/game-saves/onboarding)
* 查阅了[概述](/services/playfab/player-progression/game-saves/overview)部分中的实现要求
* 完成了下面列出的要求
* （可选）克隆或查看了 GitHub 上适用于 Windows 的端到端**游戏存档示例**：[PlayFabGameSaveSample-Windows](https://github.com/PlayFab/PlayFab-Samples/tree/master/Samples/All/PlayFabGameSaveSample-Windows)。该示例演示了本快速入门中引用的初始化、同步、冲突处理和上传流程。

## 您将学到的内容

在本指南中，您将学习如何：

* 初始化游戏存档系统
* 从云端下载现有存档数据
* 将本地存档数据上传到云端
* 处理冲突和 UI 回调
* 管理活动设备场景

## 开发要求

### 软件要求

* 一个 [PlayFab 开发者帐户](https://developer.playfab.com)
* 推荐使用 Visual Studio 2019 或 Visual Studio 2022 进行 Gaming Runtime 开发。详见 [https://learn.microsoft.com/en-us/gaming/gdk/docs/gdk-dev/get-started/overviews/sdk-and-tools#install-visual-studio。](https://learn.microsoft.com/en-us/gaming/gdk/docs/gdk-dev/get-started/overviews/sdk-and-tools#install-visual-studio。)
* 访问最新的 [Microsoft Game Development Kit (GDK)](https://learn.microsoft.com/gaming/gdk/)

## 游戏存档流程概述

游戏存档系统遵循一种在设备间无缝工作的简单模式：

### 初始设置（每个游戏会话一次）

1. **初始化服务**：设置 PlayFab Core 和游戏存档模块
2. **验证用户身份**：使用 XBOX 身份验证登录玩家
3. **下载现有存档**：将其他设备上的存档数据同步到本地设备
4. **获取存档位置**：获取游戏应在其中写入存档文件的本地存档根文件夹

### 游戏过程中

5. **写入存档文件**：游戏照常将存档数据写入本地存档根文件夹
6. **上传更改**：定期将修改过的存档文件上传到云端
7. **继续游玩**：在游戏会话中根据需要重复步骤 5-6

### 会话结束

8. **最终上传**：在玩家退出之前上传所有最终更改
9. **后台同步**：在 XBOX/Windows 上，系统会在游戏关闭时自动处理最终上传

### 主要优势

* **离线支持**：即使没有互联网连接，玩家也可以开始游戏
* **自动冲突解决**：内置 UI 处理设备之间的存档冲突
* **增量上传**：仅上传已更改的文件，从而提高性能
* **跨设备连续性**：在设备之间切换时提供无缝体验

## 实现详情

以下部分为每个步骤提供了详细的代码示例：

## 步骤 1：初始化游戏存档

游戏存档设计为在线和离线都能工作，这使其与其他 PlayFab API 不同。它维护一个持久的本地用户身份，即使设备离线启动也能工作。

### 关键概念

* **PFLocalUserHandle**：可离线工作的持久用户标识符
* **PFServiceConfigHandle**：您的 PlayFab 作品的配置
* **离线优先设计**：即使没有互联网连接，系统也能立即工作

### 前提条件

在初始化游戏存档之前，请确保您已经：

* 调用了 `XGameRuntimeInitialize()` 以初始化 XBOX 运行时
* 调用了 `XUserAddAsync()` 以登录用户并获取 `XUserHandle`
* 从 Game Manager 获取了您的 PlayFab Title ID

### 实现

```cpp theme={null}
// Step 1: Initialize PlayFab Core
HRESULT hr = PFInitialize(nullptr);
if (FAILED(hr))
{
    // Handle initialization failure - log error and exit gracefully
    return hr;
}

// Step 2: Create service config handle with your title information
PFServiceConfigHandle serviceConfigHandle{ nullptr };
hr = PFServiceConfigCreateHandle(
    "https://<titleId>.playfabapi.com",    // Replace <titleId> with your actual PlayFab Title ID
    "<titleId>",                           // Replace <titleId> with your actual PlayFab Title ID
    &serviceConfigHandle);
if (FAILED(hr))
{
    // Handle service config creation failure
    return hr;
}

// Step 3: Initialize the Game Saves module
PFGameSaveInitArgs args = {};
// Set args.saveFolder here if you are targetting platforms such as Steam
// where you need to provide root of where the game saves are
hr = PFGameSaveFilesInitialize(&args);
if (FAILED(hr))
{
    // Handle Game Saves initialization failure
    return hr;
}

// Step 4: Create a local user handle
// NOTE: Assumes you have already obtained 'xuserHandle' from XUserAddAsync
PFLocalUserHandle localUserHandle;
hr = PFLocalUserCreateHandleWithXboxUser(serviceConfigHandle, xuserHandle, nullptr, &localUserHandle);
if (FAILED(hr))
{
    // Handle local user creation failure
    return hr;
}

// Success! The Game Saves system is now initialized and ready to use
```

<Info>
  请将 `<titleId>` 替换为您在 Game Manager 中的实际 PlayFab Title ID。`xuserHandle` 必须通过成功调用 `XUserAddAsync` 获得。
</Info>

### 其他平台

对于没有 XBOX 身份验证且不支持离线的平台，请改用 `PFLocalUserCreateHandle` 或 `PFLocalUserCreateHandleWithPersistedLocalId` 的其他版本。有关实现细节，请参阅特定于平台的文档。

例如：

```cpp theme={null}
PFLocalUserHandle localUserHandle;
hr = PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, nullptr, &localUserHandle);
if (FAILED(hr))
{
    // Handle local user creation failure
    return hr;
}
```

## 步骤 2：从云端同步存档数据

初始化后，将用户添加到游戏存档系统，以同步来自其他设备的现有存档数据。此步骤还会设置本地存档根文件夹，游戏将在其中读取和写入存档文件。

### 何时调用

* 每个游戏会话一次，在用户身份验证之后
* 当用户返回游戏主菜单时
* 从挂起/后台恢复后

### 此步骤执行的操作

1. **从其他设备下载现有存档**（仅新增或更改的文件）
2. 尽可能**保留文件时间戳**，以便正确进行版本控制
3. 通过内置 UI 自动**处理冲突**
4. 将该设备**设为该用户的活动设备**
5. **提供存档文件夹路径**，供游戏写入文件

### 重要限制

* 每个游戏存档会话只能**成功调用一次**
* 需要重新初始化游戏存档系统才能再次调用
* 会触发关于冲突、存储问题和设备争用的 UI 提示

### 实现

```cpp theme={null}
// Add user to Game Saves system and sync from cloud
HRESULT hr;
XAsyncBlock async{};
hr = PFGameSaveFilesAddUserWithUiAsync(localUserHandle, PFGameSaveFilesAddUserOptions::None, &async);
if (FAILED(hr))
{
    // Handle API call failure
    return hr;
}

// Wait for the operation to complete
// For production code, consider using a callback instead of blocking
hr = XAsyncGetStatus(&async, true); 
if (FAILED(hr))
{
    // Handle async operation failure (network issues, user cancellation, etc.)
    return hr;
}

hr = PFGameSaveFilesAddUserWithUiResult(&async);
if (FAILED(hr))
{
    // Handle specific operation failures (conflicts, storage issues, etc.)
    return hr;
}

// Get the local save root folder path for your game
char saveFolder[1024] = { 0 };
hr = PFGameSaveFilesGetFolder(localUserHandle, 1024, saveFolder, nullptr);
if (FAILED(hr))
{
    // Handle folder retrieval failure
    return hr;
}

// Check remaining cloud storage quota
int64_t remainingQuota{ 0 };
hr = PFGameSaveFilesGetRemainingQuota(localUserHandle, &remainingQuota);
if (FAILED(hr))
{
    // Handle quota retrieval failure
    return hr;
}

// Success! You can now read/write save files in the saveFolder directory
printf("Save folder: %s\n", saveFolder);
printf("Remaining quota: %lld bytes\n", remainingQuota);
```

### 后续步骤

此调用成功完成后：

* 您的游戏可以从 `saveFolder` 目录读取现有存档文件
* 根据需要写入新存档文件并创建子目录
* 该设备现在被视为该用户的"活动"设备
* 如果用户尝试在其他设备上同步，其他设备将显示警告

## 步骤 3：将存档数据上传到云端

一旦您的游戏已将存档文件和子文件夹写入本地存档根文件夹，请使用此步骤将更改上传到云端。系统会自动检测并仅上传自上次上传以来更改的文件和子文件夹。文件和文件夹的删除也会自动同步到云端。

### 建议的上传时机

* **在取得重大进度之后**：当玩家到达检查点或完成关卡时
* **在菜单转换之前**：当返回主菜单或切换游戏模式时
* **在游戏退出时**：在玩家退出游戏之前
* **定期存档**：在长时间游戏会话中每隔几分钟

### 上传选项

* **`KeepDeviceActive`**：设备保持活动状态，允许稍后进行额外的上传
* **`ReleaseDeviceAsActive`**：将设备从活动状态释放，允许其他设备无缝同步

### 平台行为

* **XBOX/Windows**：游戏关闭后上传将在后台继续
* **其他平台**（Steam Deck 等）：上传必须在游戏退出前完成，否则存档数据将无法到达云端

### 实现

```cpp theme={null}
// Upload save files to cloud
XAsyncBlock async{};
HRESULT hr = PFGameSaveFilesUploadWithUiAsync(
    localUserHandle, 
    PFGameSaveFilesUploadOption::KeepDeviceActive,  // Use ReleaseDeviceAsActive when quitting
    &async);
if (FAILED(hr))
{
    // Handle API call failure
    return hr;
}

// Wait for upload to complete
// Consider using callbacks for better user experience
hr = XAsyncGetStatus(&async, true); 
if (FAILED(hr))
{
    // Handle async operation failure (network issues, storage full, etc.)
    return hr;
}

hr = PFGameSaveFilesUploadWithUiResult(&async);
if (FAILED(hr))
{
    // Handle upload failure
    return hr;
}

// Success! Save data is now safely stored in the cloud
```

### 我什么时候可以再次写入存档文件夹？

在上传过程中，系统会先读取并压缩您的本地存档文件，然后再上传。一旦同步状态转换为 `Uploading`（通过 `PFGameSaveFilesUiProgressCallback` 报告），系统就完成了对文件的读取，可以安全地再次写入存档文件夹。您无需等待完整上传完成即可恢复存档。

如果您未使用进度回调，请等待 `XAsyncBlock` 完成后再写入新的存档数据。

### 最佳实践

1. **优雅地处理失败**：网络问题不应导致游戏崩溃
2. **使用合适的选项**：
   * 在游戏过程中使用 `KeepDeviceActive` 以便进行额外上传
   * 当玩家退出或返回菜单时使用 `ReleaseDeviceAsActive`
3. **在非 XBOX 平台上警告用户**：告知玩家在上传期间不要退出

### 频率注意事项

* 支持每次会话进行多次上传，且效率高
* 仅上传已更改的文件，最小化带宽使用
* 有关具体配额和限制，请参阅[限制文档](/services/playfab/player-progression/game-saves/limits)

## 步骤 4：处理 UI 回调（可选）

游戏存档为 XBOX 和 Windows 平台提供了内置 UI。在其他平台（例如 Steam Deck）上，您的游戏必须通过处理回调来提供自己的 UI。

UI 回调在 `PFGameSaveFilesAddUserWithUiAsync` 和 `PFGameSaveFilesUploadWithUiAsync` 期间触发。每个回调都会暂停异步操作，直到您的游戏做出响应——在所有 UI 回调被解决之前，`XAsyncBlock` 回调不会触发。

```cpp theme={null}
// Set up custom UI callbacks (call this before AddUser or Upload operations)
// See sample for detailed examples of these callbacks.
PFGameSaveUICallbacks callbacks{};
callbacks.progressCallback = MyProgressCallback;
callbacks.syncFailedCallback = MySyncFailedCallback;
callbacks.activeDeviceContentionCallback = MyActiveDeviceContentionCallback;
callbacks.conflictCallback = MyConflictCallback;
callbacks.outOfStorageCallback = MyOutOfStorageCallback;

HRESULT hr = PFGameSaveFilesSetUiCallbacks(&callbacks);
```

有关回调类型、响应 API、用户操作的完整列表以及状态机工作方式的详细信息，请参阅[游戏存档 UI 回调](/services/playfab/player-progression/game-saves/ui-callbacks)。

## 了解存档冲突

当在多台设备上修改了相同的游戏数据时，会发生存档冲突。游戏存档将每个根级子文件夹视为冲突解决的原子单元，玩家可以在发生冲突时选择保留本地或云端数据。

有关详细的冲突处理场景和最佳实践，请参阅[游戏存档冲突](/services/playfab/player-progression/game-saves/conflicts)。

## 了解游戏存档离线模式

游戏存档在在线和离线时都能工作。连接到云端时，所有 API 都能正常运行。离线或断开连接时，本地存档继续工作，但云端操作会返回 `E_PF_GAMESAVE_DISCONNECTED_FROM_CLOUD`。

使用 `PFGameSaveFilesIsConnectedToCloud()` 检查连接状态，并实现同步失败回调以优雅地处理网络问题。

有关详细的离线行为和最佳实践，请参阅[游戏存档离线模式](/services/playfab/player-progression/game-saves/offline)。

## 了解游戏存档活动设备变更

当玩家在会话中途切换设备时，防止他们意外地在多台设备上同时游玩而丢失进度非常重要。

如果您的游戏仅使用 XBOX 的**单一在场点 (SPOP)** 功能进行登录，则此场景会自动被阻止。SPOP 确保用户一次只能在一台 XBOX 设备上登录。否则，您还应实现活动设备变更回调，以处理玩家在会话中途切换设备的场景。

有关详细的行为和最佳实践，请参阅[游戏存档活动设备变更](/services/playfab/player-progression/game-saves/activedevicechanges)。

## 调试

查看结果和调试 SDK 中任何调用的最简单方法是启用[调试跟踪](/services/playfab/sdks/c/tracing)。启用调试跟踪后，您既可以在调试器输出窗口中看到结果，又可以将结果挂接到您游戏自己的日志中。


## Related topics

- [游戏存档 UI 回调](/zh-CN/services/playfab/player-progression/game-saves/ui-callbacks.md)
- [PlayFab 游戏存档 Steam Deck 实现指南](/zh-CN/services/playfab/player-progression/game-saves/steam-deck-implementation.md)
- [使用 PlayFab 进行玩家进程管理](/zh-CN/services/playfab/player-progression/player-progression-overview.md)
- [快速入门](/zh-CN/services/playfab/economy-monetization/economy-v2/quickstart.md)
- [Matchmaking 快速入门](/zh-CN/services/playfab/multiplayer/matchmaking/quickstart.md)
