SuperAPI AI 工具集

bevy-save-load

编程与开发

查看项目

更新于 10-16 06:08

技能介绍

用途

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:加载内容,不是存档槽。