chunk_saving_auto/
chunk_saving_auto.rs

1//! Example: Automatic chunk saving/loading with ChunkLoader and ChunkUnloader
2//!
3//! Move the camera with WASD. Chunks auto-save when unloaded and auto-load when spawned.
4
5use bevy::prelude::*;
6use chunky_bevy::prelude::*;
7use serde::{Deserialize, Serialize};
8
9fn main() {
10    App::new()
11        .add_plugins(DefaultPlugins)
12        .add_plugins(ChunkyPlugin::default())
13        .add_plugins(
14            ChunkSavingPlugin::new("saves/auto_world")
15                .with_auto_save()
16                .with_auto_load(),
17        )
18        .register_chunk_data::<TerrainData>()
19        // Enable unloading
20        .insert_resource(ChunkUnloadLimit { max_chunks: 50 })
21        .insert_resource(ChunkUnloadByDistance)
22        .add_systems(Startup, setup)
23        .add_systems(Update, (move_loader, show_info, visualize_chunks))
24        .run();
25}
26
27#[derive(Component, Serialize, Deserialize, Clone, Debug)]
28struct TerrainData {
29    height: f32,
30    moisture: f32,
31}
32
33fn setup(mut commands: Commands) {
34    // Camera looking down
35    commands.spawn((
36        Camera3d::default(),
37        Transform::from_xyz(0.0, 100.0, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
38    ));
39
40    // Chunk loader that moves with input
41    commands.spawn((
42        Transform::default(),
43        ChunkLoader(IVec3::new(3, 0, 3)),
44        ChunkUnloadRadius(IVec3::new(5, 0, 5)),
45        Mover,
46    ));
47
48    info!("WASD to move loader. Chunks auto-save on unload, auto-load on spawn.");
49}
50
51#[derive(Component)]
52struct Mover;
53
54fn move_loader(
55    keys: Res<ButtonInput<KeyCode>>,
56    time: Res<Time>,
57    mut movers: Query<&mut Transform, With<Mover>>,
58) {
59    let speed = 30.0;
60    let mut dir = Vec3::ZERO;
61
62    if keys.pressed(KeyCode::KeyW) {
63        dir.z -= 1.0;
64    }
65    if keys.pressed(KeyCode::KeyS) {
66        dir.z += 1.0;
67    }
68    if keys.pressed(KeyCode::KeyA) {
69        dir.x -= 1.0;
70    }
71    if keys.pressed(KeyCode::KeyD) {
72        dir.x += 1.0;
73    }
74
75    if dir != Vec3::ZERO {
76        let delta = dir.normalize() * speed * time.delta_secs();
77        for mut transform in movers.iter_mut() {
78            transform.translation += delta;
79        }
80    }
81}
82
83fn show_info(
84    chunks: Query<&ChunkPos, With<Chunk>>,
85    movers: Query<&Transform, With<Mover>>,
86    chunk_manager: Res<ChunkManager>,
87) {
88    // Log occasionally
89    static mut FRAME: u32 = 0;
90    unsafe {
91        FRAME += 1;
92        if FRAME % 120 != 0 {
93            return;
94        }
95    }
96
97    let count = chunks.iter().count();
98    if let Ok(transform) = movers.single() {
99        let loader_chunk = chunk_manager.get_chunk_pos(&transform.translation);
100        info!(
101            "Loader at chunk {:?}, {} chunks loaded",
102            loader_chunk, count
103        );
104    }
105}
106
107fn visualize_chunks(
108    chunks: Query<(&ChunkPos, Option<&TerrainData>), With<Chunk>>,
109    movers: Query<&Transform, With<Mover>>,
110    mut gizmos: Gizmos,
111) {
112    let chunk_size = 10.0;
113
114    for (pos, terrain) in chunks.iter() {
115        let world_pos = pos.0.as_vec3() * chunk_size + Vec3::new(5.0, 0.0, 5.0);
116
117        let color = if terrain.is_some() {
118            Color::srgb(0.2, 0.8, 0.2)
119        } else {
120            Color::srgb(0.5, 0.5, 0.5)
121        };
122
123        gizmos.rect(
124            Isometry3d::new(
125                world_pos,
126                Quat::from_rotation_x(std::f32::consts::FRAC_PI_2),
127            ),
128            Vec2::splat(chunk_size - 0.5),
129            color,
130        );
131    }
132
133    // Show loader position
134    if let Ok(transform) = movers.single() {
135        gizmos.sphere(
136            Isometry3d::from_translation(transform.translation),
137            2.0,
138            Color::srgb(1.0, 1.0, 0.0),
139        );
140    }
141}