用途
bevy-save-load 用于在 Bevy 项目中持久化玩家进度。当设计需要保存玩家进度时使用它;资源加载与场景生成应交给 bevy-asset-scenes 处理。在官方文档明确指定某种格式之前,存档字节应保持为开发者自行序列化的数据。
主要能力
- 将应用级进度作为 Resource 自行序列化。
- 进度结构体使用 serde 与 RON,文件保存基本类型与一个 version 字段。
- 实体 ID 与资源句柄不写入文件。
- 通过显式请求系统写入存档槽,而不是每帧 Update 都写文件。
- 支持旧存档迁移:旧文件缺少 version 时,先构建当前结构体再插入。
使用方式
依赖:
[dependencies]
bevy = "0.20"
serde = { version = "1", features = ["derive"] }
ron = "0.10"
进度类型是一个普通 Resource:
use serde::{Deserialize, Serialize};
#[derive(Resource, Clone, Serialize, Deserialize)]
struct Progress {
version: u32,
coins: u32,
level: String,
}
fn fresh_progress() -> Progress {
Progress {
version: 2,
coins: 0,
level: "town".to_string(),
}
}
编码与解码是普通函数,从请求系统调用,而不是每帧查询调用:
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
struct Progress {
version: u32,
coins: u32,
level: String,
}
fn encode_slot(progress: &Progress) -> String {
ron::ser::to_string_pretty(progress, ron::ser::PrettyConfig::default())
.expect("progress encodes")
}
fn decode_slot(text: &str) -> Progress {
ron::from_str(text).expect("progress decodes")
}
fn round_trip() {
let saved = Progress {
version: 2,
coins: 4,
level: "town".to_string(),
};
let loaded = decode_slot(&encode_slot(&saved));
assert_eq!(loaded.coins, 4);
}
旧文件迁移:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct SaveV1 {
coins: u32,
}
#[derive(Resource, Clone, Serialize, Deserialize)]
struct Progress {
version: u32,
coins: u32,
}
fn load_any(text: &str) -> Progress {
if let Ok(current) = ron::from_str::<Progress>(text) {
return current;
}
let legacy: SaveV1 = ron::from_str(text).expect("legacy slot");
Progress {
version: 2,
coins: legacy.coins,
}
}
存档系统仅在 SaveRequest 存在时运行,随后移除它。一个系统负责该资源与磁盘写入:
use serde::{Deserialize, Serialize};
#[derive(Resource, Clone, Serialize, Deserialize)]
struct Progress {
version: u32,
coins: u32,
}
#[derive(Resource)]
struct SaveRequest {
path: String,
}
fn write_slot(progress: Res<Progress>, request: Res<SaveRequest>) -> std::io::Result<()> {
let text = ron::ser::to_string(&*progress).expect("progress encodes");
std::fs::write(&request.path, text)
}
fn save_when_asked(mut commands: Commands, progress: Res<Progress>, request: Option<Res<SaveRequest>>) {
let Some(request) = request else {
return;
};
let _ = write_slot(progress, request);
commands.remove_resource::<SaveRequest>();
}
pub struct SavePlugin;
impl Plugin for SavePlugin {
fn build(&self, app: &mut App) {
app.insert_resource(Progress {
version: 2,
coins: 0,
})
.add_systems(Update, save_when_asked);
}
}
应持久化的内容
| 需求 | Bevy 惯用法 |
|---|---|
| 设计者内容 | 资源 Handle,或代码中自有的 Resource |
| 关卡实例 | 在 bevy-asset-scenes 上做场景生成 |
| 玩家存档槽 | 存在 SaveRequest 时以 RON 写入的带版本 Resource |
适用场景
- 需要保存玩家进度、金币、关卡等应用级状态。
- 需要带版本号、可迁移的存档格式。
- 桌面端使用原生 std::fs 写入存档槽。
限制
- 场景生成不是存档游戏,不要把 bsn! 当作玩家存档。
- 原生 std::fs 写入是桌面存档方案;Web 构建需要不同的存储方案,不能假设浏览器有该路径。
- 存档槽中不要放引擎配置文件类型。
- 不使用本文件未列出的 DynamicScene 或场景文件写入器。
- 不使用第三方存档 crate。
- 不要从两个无序系统可变查询同一组件;调度会并行运行系统,除非用 .chain() 排序。
- 不要在本技能中发明物理、导航、网络或 XR crate。
- 文档 URL 不要固定 /0.20.0/。
相关技能
- bevy-master:状态归属。
- bevy-data-resources:Resource 与 Component 的区别。
- bevy-asset-scenes:加载内容,不是存档槽。