Data files: tune without restarting
Keep the numbers you tweak (speeds, damage, costs, wave sizes) in a JSON
file in assets/ instead of in code. With hot reload
on (the default in debug builds), saving the file changes them in the
running game, within a quarter of a second, so you can tune a
fight while watching it.
#![allow(unused)]
fn main() {
#[derive(serde::Deserialize)]
struct Tuning {
speed: f32,
damage: f32,
}
fn load_tuning(ctx: &mut Ctx, path: &str) {
ctx.load_resource::<Tuning>(path); // e.g. "tuning.json": {"speed": 30, "damage": 2}
}
fn step_size(ctx: &Ctx) -> f32 {
// The file's current values, every tick.
ctx.world.resource::<Tuning>().speed * ctx.dt()
}
}
struct Tuning { float speed; float damage; };
[[maybe_unused]] static void load_tuning(crabby::ctx& ctx) {
ctx.world().reflect_resource<Tuning>({crabby::field("speed", &Tuning::speed), crabby::field("damage", &Tuning::damage)});
ctx.load_json<Tuning>("tuning.json"); // {"speed": 30, "damage": 2}
// Systems read ctx.world().resource<Tuning>(): the file's current values.
}
In Rust the file becomes a resource: any type that implements
serde::Deserialize, replaced whole on each reload (use #[serde(default)]
for optional fields). In C and C++ the resource is a plain struct described
with reflect_resource, and the file’s fields merge into it, so the file
may list only some of them (C: crabby_ctx_load_resource_json). C++ can
also load a file into a component of one entity: ctx.load_json<T>(path, e).
- Loading fails with a clear message: a missing file lists the folders searched, and bad JSON says the line and column.
- A file that doesn’t parse while the game runs (often one caught halfway through saving) keeps the old values and prints why; the next good save applies.
ctx.reloaded_files()names the files reloaded this frame, textures included, for work that depends on them (rebuilding a map, say). In C++,ctx.was_reloaded("map.json")(C:crabby_ctx_was_reloaded).- Headless runs reload too, so an agent or a test can change a file between ticks.