持久化
设置、存档管理器和世界存储,用于游戏数据
持久化(feature "persistence")
持久化模块提供三个层次:玩家设置(Settings)、多存档槽位存档管理器(SaveManager)和原始键值世界存储(WorldStorage)。
Resource 派生: 当启用
bevy_ecsfeature 时,所有持久化类型 —SaveManager、Settings、WorldStorage、AutoSaveConfig、AutoSaveState和MigrationRunner— 均派生Resource,可以直接插入到 ECS world 中。
PersistencePlugin
PersistencePlugin 受 "persistence" feature 门控,在 ECS 调度中配置自动存档支持:
- 将
AutoSaveConfig和AutoSaveState注册为 ECS 资源。 - 向调度中添加
auto_save_tick_system,该系统计算经过时间,并在配置的间隔到期时触发存档。
use anvilkit_app::plugins::PersistencePlugin;
app.add_plugins(PersistencePlugin);
// Optionally configure the auto-save interval (default: 300 seconds)
app.insert_resource(AutoSaveConfig { interval_secs: 120.0 });SettingsApplyPlugin
SettingsApplyPlugin 在每帧的 PostUpdate 阶段运行,将 Settings 资源同步到运行时游戏状态:
- 音频: 将
Settings.audio字段(主音量、音乐、音效音量)复制到AudioBus资源中。 - 输入: 将
Settings.input.mouse_sensitivity复制到CameraController资源中。
这意味着游戏只需修改 Settings — 插件会自动传播这些值。
错误分类
持久化函数返回类型化的 AnvilKitError::Persistence 变体,取代旧的 generic() 错误。使用专用构造器:
AnvilKitError::persistence("save slot corrupted");
AnvilKitError::persistence_with_path("failed to write", &path);Settings
以 RON 格式存储的类型化配置。当文件缺失或损坏时,回退到默认值。
子结构
GraphicsSettings
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | u32 | 1280 | 窗口宽度 |
height | u32 | 720 | 窗口高度 |
fullscreen | bool | false | 全屏模式 |
vsync | bool | true | 垂直同步 |
msaa | u32 | 4 | MSAA 采样数(1/2/4) |
bloom | bool | true | 泛光后处理 |
ssao | bool | true | 屏幕空间环境光遮蔽 |
shadow_quality | u32 | 2 | 0=关闭, 1=低, 2=中, 3=高 |
AudioSettings
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
master_volume | f32 | 1.0 | 主音量 0.0..1.0 |
music_volume | f32 | 0.8 | 音乐音量 0.0..1.0 |
sfx_volume | f32 | 1.0 | 音效音量 0.0..1.0 |
InputSettings
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mouse_sensitivity | f32 | 0.003 | 鼠标灵敏度倍率 |
invert_y | bool | false | 反转 Y 轴 |
action_overrides | HashMap<String, String> | {} | 自定义按键重绑定 |
Settings API
| 方法 | 签名 | 说明 |
|---|---|---|
load | (path: &Path) -> Self | 从 RON 加载;反序列化失败时输出警告并返回默认值 |
save | (&self, path: &Path) -> Result<(), AnvilKitError> | 写入 RON(自动创建目录) |
default_path | () -> PathBuf | "config/settings.ron" |
use anvilkit_core::persistence::Settings;
// 加载(或使用默认值)
let mut settings = Settings::load(&Settings::default_path());
settings.graphics.vsync = false;
settings.audio.master_volume = 0.7;
settings.save(&Settings::default_path()).unwrap();SaveManager
管理多个存档槽位,每个槽位包含元数据(meta.ron)和一个用于 WorldStorage 的 data/ 目录。
目录结构
saves/
quick/
meta.ron
data/ <-- WorldStorage 根目录
slot_1/
meta.ron
data/SaveSlotInfo 字段
| 字段 | 类型 | 说明 |
|---|---|---|
name | String | 槽位名称(如 "quick"、"slot_1") |
timestamp | u64 | Unix 时间戳(秒) |
play_time_secs | f64 | 累计游戏时间 |
game_version | String | 游戏版本字符串 |
metadata | HashMap<String, String> | 游戏特定的元数据 |
SaveManager API
| 方法 | 签名 | 说明 |
|---|---|---|
new | (dir: impl AsRef<Path>, version: &str) -> Result<Self, AnvilKitError> | 创建/打开存档目录 |
save | (&self, slot: &str, play_time: f64, metadata: HashMap) -> Result<PathBuf, AnvilKitError> | 写入元数据,返回 data/ 路径 |
list_saves | (&self) -> Vec<SaveSlotInfo> | 所有槽位按最新排序 |
get_save_info | (&self, slot: &str) -> Option<SaveSlotInfo> | 读取单个槽位的元数据 |
delete | (&self, slot: &str) -> Result<(), AnvilKitError> | 递归删除槽位目录 |
slot_data_path | (&self, slot: &str) -> PathBuf | 获取 data/ 路径而不写入元数据 |
use anvilkit_core::persistence::SaveManager;
use std::collections::HashMap;
let mgr = SaveManager::new("saves", "1.0.0").unwrap();
// 创建存档
let mut meta = HashMap::new();
meta.insert("level".into(), "3".into());
let data_path = mgr.save("slot_1", 3600.0, meta).unwrap();
// 列出存档
for info in mgr.list_saves() {
println!("{}: played {}s", info.name, info.play_time_secs);
}
// 删除
mgr.delete("slot_1").unwrap();WorldStorage
基于文件系统的键值存储。每个键映射到基目录下的一个文件。键中的斜杠会创建子目录。写入是原子的(先写入 .tmp 文件再重命名)。
API
| 方法 | 签名 | 说明 |
|---|---|---|
open | (path: impl AsRef<Path>) -> Result<Self, AnvilKitError> | 打开/创建存储目录 |
get | (&self, key: &str) -> Option<Vec<u8>> | 读取值;缺失时返回 None |
put | (&self, key: &str, value: &[u8]) -> Result<(), AnvilKitError> | 原子写入(tmp + 重命名) |
delete | (&self, key: &str) -> Result<(), AnvilKitError> | 删除一个键 |
keys_with_prefix | (&self, prefix: &str) -> Vec<String> | 列出以指定前缀开头的键 |
batch_put | (&self, entries: &[(&str, &[u8])]) -> Result<(), AnvilKitError> | 批量写入多个键(每个均为原子操作) |
path | (&self) -> &Path | 基目录 |
use anvilkit_core::persistence::WorldStorage;
let storage = WorldStorage::open("saves/slot_1/data").unwrap();
// 存储区块数据
storage.put("chunk/0/0", &bincode::serialize(&chunk).unwrap()).unwrap();
// 读取回来
if let Some(data) = storage.get("chunk/0/0") {
let chunk: Chunk = bincode::deserialize(&data).unwrap();
}
// 枚举所有区块
let keys = storage.keys_with_prefix("chunk/");
println!("Stored {} chunks", keys.len());
// 批量写入
storage.batch_put(&[
("meta/version", b"1"),
("meta/seed", b"42"),
]).unwrap();