use std::time::Duration;
use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, platform::collections::HashMap, prelude::*};
use bevy_ggrs::{
ggrs::{PlayerType, SessionBuilder},
prelude::*,
LocalInputs, LocalPlayers,
};
use bevy_roll_safe::prelude::*;
type GgrsConfig = bevy_ggrs::GgrsConfig<u8, String>;
fn read_local_input(mut commands: Commands, local_players: Res<LocalPlayers>) {
let mut local_inputs = HashMap::new();
for handle in &local_players.0 {
local_inputs.insert(*handle, 0);
}
commands.insert_resource(LocalInputs::<GgrsConfig>(local_inputs));
}
#[derive(States, Hash, Default, Debug, Eq, PartialEq, Clone)]
enum GameplayState {
#[default]
InRound,
GameOver,
}
#[derive(Component, Hash, Debug, Clone, Copy)]
struct Health(u32);
fn main() -> Result<(), Box<dyn std::error::Error>> {
let session = SessionBuilder::<GgrsConfig>::new()
.with_num_players(1)
.with_check_distance(5)
.add_player(PlayerType::Local, 0)?
.start_synctest_session()?;
App::new()
.add_plugins((
GgrsPlugin::<GgrsConfig>::default(),
RollbackSchedulePlugin::new_ggrs(),
))
.insert_resource(RollbackFrameRate(60))
.add_systems(ReadInputs, read_local_input)
.rollback_component_with_copy::<Health>()
.checksum_component_with_hash::<Health>()
.init_ggrs_state::<GameplayState>()
.add_plugins((
MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
1.0 / 10.0,
))),
LogPlugin::default(),
))
.add_systems(OnEnter(GameplayState::InRound), spawn_player)
.add_systems(OnEnter(GameplayState::GameOver), log_game_over)
.add_systems(
RollbackUpdate,
decrease_health
.after(bevy_roll_safe::apply_state_transition::<GameplayState>)
.run_if(in_state(GameplayState::InRound)),
)
.insert_resource(Session::SyncTest(session))
.run();
Ok(())
}
fn spawn_player(mut commands: Commands) {
info!("spawning player");
commands.spawn(Health(10)).add_rollback();
}
fn decrease_health(
mut commands: Commands,
mut players: Query<(Entity, &mut Health)>,
mut state: ResMut<NextState<GameplayState>>,
) {
let (player_entity, mut health) = players.single_mut().expect("player entity");
health.0 = health.0.saturating_sub(1);
info!("{health:?}");
if health.0 == 0 {
info!("despawning player, setting GameOver state");
commands.entity(player_entity).despawn();
state.set(GameplayState::GameOver);
}
}
fn log_game_over() {
info!("you dead");
}