> ## 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.

# Game Saves 冲突

> 解决本地存档与云端存档出现差异时的 PlayFab Game Saves 冲突，通过同步策略保护玩家跨设备的进度。

# Game Save 冲突与原子单元

## 概述

当你在多台设备上修改同一份游戏数据时，就会发生存档冲突。系统需要决定保留哪个版本。要设计存档数据结构，你需要理解系统如何检测和解决冲突。

## 冲突发生的时机

只有在以下条件全部满足时，同步操作（`PFGameSaveFilesAddUserWithUiAsync`）期间才会发生冲突：

1. **存在本地更改**：自上次同步以来，你在本地修改了文件。
2. **存在云端更改**：自上次同步以来，另一台设备上传了更新的数据。
3. **同一原子单元**：两项更改都位于同一个根级文件夹内。

### 冲突检测矩阵

| 本地设备更改 | 云端在同一原子单元中存在更改 | 结果     |
| ------ | -------------- | ------ |
| 修改的文件  | 是              | **冲突** |
| 删除的文件  | 是              | **冲突** |
| 无更改    | 是              | 继续下载   |
| 修改的文件  | 否              | 继续上传   |
| 删除的文件  | 否              | 继续删除   |

## 什么是原子单元？

一些文件同步系统按文件为单位处理冲突。如果同一个文件需要同时上传和下载，就会产生冲突。在 Game Saves 中，每个根级子文件夹被视为一个*原子单元*。

<Note>
  **每个根级子文件夹都是一个原子单元。**
</Note>

如果某个根级子文件夹内的任何文件或子文件夹需要下载，同时该根级子文件夹内的任何文件或文件夹又需要上传，则整个原子单元处于冲突状态。

这种方式让你能够：

* **保持数据完整性**：相互依赖的文件保持一致
* **提供隔离性**：位于不同文件夹的独立数据同步时不会互相冲突
* **最小化冲突**：不同设备上对不同原子单元的更改会自动合并

### 存档结构示例

```
SaveRoot/
├── save.dat              ← Atomic unit: root
├── player.dat            ← Atomic unit: root
├── Save1/
│   ├── stats.json        ← Atomic unit: Save1
│   └── inventory.json    ← Atomic unit: Save1
├── Save2/
│   ├── stats.json        ← Atomic unit: Save2
│   └── inventory.json    ← Atomic unit: Save2
└── WorldState/
    ├── map.dat           ← Atomic unit: WorldState
    └── npcs/
        └── positions.dat ← Atomic unit: WorldState
```

### 冲突场景

| 设备 A 的更改             | 设备 B 的更改                        | 是否冲突？ | 原因                 |
| -------------------- | ------------------------------- | ----- | ------------------ |
| `Save1/stats.json`   | `Save1/inventory.json`          | **是** | 同一原子单元：Save1       |
| `Save1/stats.json`   | `Save2/stats.json`              | 否     | 不同单元：Save1 与 Save2 |
| `WorldState/map.dat` | `WorldState/npcs/positions.dat` | **是** | 同一原子单元：WorldState  |
| `save.dat`           | `Save1/stats.json`              | 否     | 不同单元：root 与 Save1  |
| `player.dat`         | `save.dat`                      | **是** | 同一原子单元：root        |

## 根级文件：特殊情况

**存档根目录下的所有文件共享一个原子单元。**

系统会将你直接放在存档根目录（未放入任何子文件夹）下的所有文件归为一个原子单元。如果你在本地修改了一个根级文件，而另一台设备修改了另一个根级文件，这种差异会触发冲突。

| 设备 A（本地）           | 设备 B（云端）              | 是否同一原子单元？ | 结果      |
| ------------------ | --------------------- | --------- | ------- |
| 修改 `rootfile1.txt` | 修改 `rootfile2.txt`    | ✅ 是       | **冲突**  |
| 修改 `rootfile1.txt` | 修改 `save1/config.ini` | ❌ 否       | 无冲突，均同步 |
| 删除 `save.dat`      | 修改 `progress.dat`     | ✅ 是       | **冲突**  |

## 用户选项

发生冲突时，玩家在以下选项中做出选择：

* **使用本地数据（保留本地）**：保留设备当前的存档数据。
* **使用云端数据（保留云端）**：下载并使用云端存档数据。

### 关键：解决方式为全有或全无

<Note>
  ⚠️ **重要提示**：虽然原子单元决定了*何时*检测到冲突，但用户对冲突的解决选择会应用于**整个存档**，而不是按原子单元分别应用。
</Note>

### 示例：混合冲突场景

```
SaveRoot/
├── SlotA/ ← Local: modified, Cloud: modified  → CONFLICT
├── SlotB/ ← Local: modified, Cloud: unchanged → Local-only change
├── SlotC/ ← Local: unchanged, Cloud: modified → Cloud-only change
└── SlotD/ ← Local: modified, Cloud: unchanged → Local-only change
```

**用户看到冲突提示**（由于 SlotA）。

| 用户选择     | SlotA  | SlotB         | SlotC        | SlotD         |
| -------- | ------ | ------------- | ------------ | ------------- |
| **保留本地** | ✅ 保留本地 | ✅ 本地已上传       | ❌ 云端更改**丢失** | ✅ 本地已上传       |
| **保留云端** | ✅ 下载云端 | ❌ 本地更改被**覆盖** | ✅ 下载云端       | ❌ 本地更改被**覆盖** |

### 为什么这很重要

1. **保留本地会丢失仅存在于云端的更改**：如果因为 SlotA 中的冲突而选择"保留本地"，你将不会获得另一台设备对 SlotC 所做的云端更新。

2. **保留云端会丢失仅存在于本地的更改**：如果选择"保留云端"，云端状态将覆盖你对 SlotB 和 SlotD 的本地更改。

3. **可用于回滚**：两种选择都会保留被舍弃的分支，以备将来回滚。

### 设计小结

| 方面       | 粒度            |
| -------- | ------------- |
| 冲突**检测** | 按原子单元（根级子文件夹） |
| 冲突**解决** | 整个存档（全有或全无）   |

这种全有或全无的方式简化了玩家体验。尽管从技术上讲按原子单元解决冲突可以保持数据一致性（因为原子单元定义了一致性边界），但会带来用户体验方面的问题：

* 玩家需要理解原子单元与文件夹边界的概念。
* 混合结果（有些文件夹来自本地，有些来自云端）可能让玩家对最终状态感到困惑。
* 分别对每个冲突的原子单元进行提示，会让冲突解决过程令人不知所措。

通过给出单一的**保留本地**与**保留云端**选项，玩家只需做一次清晰的决定，而无需理解底层的存档结构。

## 不发生冲突的情况

### 双端均删除

如果两台设备都删除了同一个文件（或同一原子单元中的文件），**不会发生冲突**。系统识别到两台设备都同意应该移除该文件。

### 不同原子单元中的更改

如果设备 A 修改 `SlotA/` 中的文件、设备 B 修改 `SlotB/` 中的文件，就不会发生冲突。同步会自动合并两项更改。

## 最佳实践

### 1. 谨慎设计文件夹结构

为独立的存档单元使用子文件夹：

```
SaveRoot/
├── Slot1/           ← Each slot is independent
│   └── save.dat
├── Slot2/
│   └── save.dat
```

将每个槽位作为一个根级子文件夹的基于槽位的游戏存档系统，是有效利用原子单元的一个简单示例。

其他示例包括：

* **共享参考数据**：将任何存档槽位都可能使用的数据（如已解锁内容或成就）存储在其自己的子文件夹中，使其独立于各存档槽位进行同步。
* **大型资源集合**：如果你的游戏存储的大量文件是独立更新的（例如已下载的内容包或用户创建的关卡），可以考虑将它们拆分到多个根级子文件夹，这样一个集合的更新不会与另一个集合的更新冲突。

### 2. 对频繁修改的数据避免使用根级文件

所有根级文件共享一个原子单元。如果你希望它们独立同步，请避免将频繁修改的文件放在根目录。

**不建议：**

```
SaveRoot/
├── autosave.dat     ← All root files = 1 atomic unit
└── save1.dat
└── save2.dat
```

**建议：**

```
SaveRoot/
├── AutoSave/
│   └── autosave.dat     ← Independent unit
└── Save1/
    └── gamesave.dat ← Independent unit
```

### 3. 将相关数据分组

将必须保持一致的文件放入同一文件夹。

```
SaveRoot/
├── Save1/
│   ├── stats.json       ← These files are interdependent
│   ├── inventory.json   ← and should conflict together
│   └── quests.json
```

### 4. 尽量减少冲突

频繁上传以降低发生冲突的可能。你同步得越频繁，两台设备产生分歧更改的可能性就越小。

### 5. 监控冲突率

使用 [PlayStream 事件](/services/playfab/player-progression/game-saves/playstream-events) 跟踪冲突发生频率以及玩家如何解决冲突。冲突率上升，或"保留本地"与"保留远程"选择明显偏向某一端，都可能预示着需要研究的用户体验或存档节奏问题。


## Related topics

- [GameSaveConflict](/zh-CN/services/playfab/api-references/events/data-types/gamesaveconflict.md)
- [XGameSave API 概述](/zh-CN/build/core-features/common/game-save/xgamesave.md)
- [XGameSaveFiles API 概述](/zh-CN/build/core-features/common/game-save/xgamesavefiles.md)
- [Game Saves 活动设备变更](/zh-CN/services/playfab/player-progression/game-saves/activedevicechanges.md)
- [XGameSaveCloseProvider](/zh-CN/reference/system/xgamesave/functions/xgamesavecloseprovider.md)
