bevy-persistent 0.10.0

A Bevy helper to easily manage resources that need to persist across game sessions.
Documentation
use bevy::prelude::*;
use bevy_persistent::prelude::*;
use serde::{
    Deserialize,
    Serialize,
};
use std::{
    path::Path,
    time::Duration,
};

const PLAYER_SIZE: f32 = 50.00;
const PLAYER_SPEED: f32 = 500.00;

const AUTOSAVE_INTERVAL_SECONDS: f32 = 3.0;

#[derive(Component)]
struct Player;

#[derive(Default, Resource, Serialize, Deserialize)]
struct GameState {
    player_position: Vec3,
}

#[derive(Resource)]
struct AutosaveTimer {
    timer: Timer,
}

fn main() {
    let state_dir = dirs::state_dir()
        .map(|native_state_dir| native_state_dir.join("bevy-persistent"))
        .unwrap_or(Path::new("local").join("state"))
        .join("examples")
        .join("autosave");

    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(
            Persistent::<GameState>::builder()
                .name("game state")
                .format(StorageFormat::Bincode)
                .path(state_dir.join("game-state.bin"))
                .default(GameState::default())
                .build()
                .expect("failed to initialize game state"),
        )
        .add_systems(Startup, setup)
        .add_systems(Update, player_movement)
        .add_systems(Update, autosave.after(player_movement))
        .run();
}

fn setup(
    mut commands: Commands,

    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,

    game_state: Res<Persistent<GameState>>,
) {
    commands.spawn(Camera2d);
    commands.spawn((
        Player,
        Mesh2d(meshes.add(Circle::new(PLAYER_SIZE))),
        MeshMaterial2d(materials.add(ColorMaterial::from(Color::WHITE))),
        Transform::from_translation(game_state.player_position),
    ));

    commands.insert_resource(AutosaveTimer {
        timer: Timer::new(Duration::from_secs_f32(AUTOSAVE_INTERVAL_SECONDS), TimerMode::Repeating),
    });
}

fn player_movement(
    time: Res<Time>,

    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut player_query: Query<&mut Transform, With<Player>>,

    mut game_state: ResMut<Persistent<GameState>>,
) {
    if let Ok(mut transform) = player_query.single_mut() {
        let mut direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::KeyW) || keyboard_input.pressed(KeyCode::ArrowUp) {
            direction += Vec3::new(0.0, 1.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::KeyA) || keyboard_input.pressed(KeyCode::ArrowLeft) {
            direction += Vec3::new(-1.0, 0.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::KeyS) || keyboard_input.pressed(KeyCode::ArrowDown) {
            direction += Vec3::new(0.0, -1.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::KeyD) || keyboard_input.pressed(KeyCode::ArrowRight) {
            direction += Vec3::new(1.0, 0.0, 0.0);
        }

        if direction.length() == 0.0 {
            return;
        }

        direction = direction.normalize();
        transform.translation += direction * PLAYER_SPEED * time.delta_secs();

        game_state.player_position = transform.translation;
    }
}

fn autosave(
    time: Res<Time>,
    mut autosave: ResMut<AutosaveTimer>,
    game_state: Res<Persistent<GameState>>,
) {
    autosave.timer.tick(time.delta());
    if autosave.timer.is_finished() {
        game_state.persist().ok();
    }
}