use bevy::{prelude::*, tasks::IoTaskPool};
use bevy_ggrs::*;
use ggrs::PlayerType;
use matchbox_socket::WebRtcNonBlockingSocket;
const INPUT_SIZE: usize = std::mem::size_of::<u8>();
const INPUT_UP: u8 = 1 << 0;
const INPUT_DOWN: u8 = 1 << 1;
const INPUT_LEFT: u8 = 1 << 2;
const INPUT_RIGHT: u8 = 1 << 3;
const INPUT_FIRE: u8 = 1 << 4;
fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.53, 0.53, 0.53)))
.add_plugins(DefaultPlugins)
.add_plugin(GGRSPlugin)
.with_input_system(input)
.add_startup_system(setup)
.add_startup_system(spawn_players)
.add_startup_system(start_matchbox_socket)
.add_system(wait_for_players)
.with_rollback_schedule(Schedule::default().with_stage(
"ROLLBACK_STAGE",
SystemStage::single_threaded().with_system(move_players),
))
.register_rollback_type::<Transform>()
.run();
}
#[derive(Component)]
struct Player {
handle: usize,
}
fn setup(mut commands: Commands) {
let mut camera_bundle = OrthographicCameraBundle::new_2d();
camera_bundle.orthographic_projection.scale = 1. / 50.;
commands.spawn_bundle(camera_bundle);
}
fn start_matchbox_socket(mut commands: Commands, task_pool: Res<IoTaskPool>) {
let room_url = "ws://127.0.0.1:3536/next_2";
info!("connecting to matchbox server: {:?}", room_url);
let (socket, message_loop) = WebRtcNonBlockingSocket::new(room_url);
task_pool.spawn(message_loop).detach();
commands.insert_resource(Some(socket));
}
fn wait_for_players(mut commands: Commands, mut socket: ResMut<Option<WebRtcNonBlockingSocket>>) {
let socket = socket.as_mut();
if socket.is_none() {
return;
}
socket.as_mut().unwrap().accept_new_connections();
let players = socket.as_ref().unwrap().players();
let num_players = 2;
if players.len() < num_players {
return; }
info!("All peers have joined, going in-game");
info!("All peers have joined, going in-game");
let socket = socket.take().unwrap();
let max_prediction = 12;
let mut p2p_session =
ggrs::P2PSession::new_with_socket(num_players as u32, INPUT_SIZE, max_prediction, socket);
for (i, player) in players.into_iter().enumerate() {
p2p_session
.add_player(player, i)
.expect("failed to add player");
if player == PlayerType::Local {
p2p_session.set_frame_delay(2, i).unwrap();
}
}
commands.start_p2p_session(p2p_session);
}
fn spawn_players(mut commands: Commands, mut rip: ResMut<RollbackIdProvider>) {
commands
.spawn_bundle(SpriteBundle {
transform: Transform::from_translation(Vec3::new(-2., 0., 0.)),
sprite: Sprite {
color: Color::rgb(0., 0.47, 1.),
custom_size: Some(Vec2::new(1., 1.)),
..Default::default()
},
..Default::default()
})
.insert(Player { handle: 0 })
.insert(Rollback::new(rip.next_id()));
commands
.spawn_bundle(SpriteBundle {
transform: Transform::from_translation(Vec3::new(2., 0., 0.)),
sprite: Sprite {
color: Color::rgb(0., 0.4, 0.),
custom_size: Some(Vec2::new(1., 1.)),
..Default::default()
},
..Default::default()
})
.insert(Player { handle: 1 })
.insert(Rollback::new(rip.next_id()));
}
fn move_players(
inputs: Res<Vec<ggrs::GameInput>>,
mut player_query: Query<(&mut Transform, &Player)>,
) {
for (mut transform, player) in player_query.iter_mut() {
let input = inputs[player.handle].buffer[0];
let mut direction = Vec2::ZERO;
if input & INPUT_UP != 0 {
direction.y += 1.;
}
if input & INPUT_DOWN != 0 {
direction.y -= 1.;
}
if input & INPUT_RIGHT != 0 {
direction.x += 1.;
}
if input & INPUT_LEFT != 0 {
direction.x -= 1.;
}
if direction == Vec2::ZERO {
continue;
}
let move_speed = 0.13;
let move_delta = (direction * move_speed).extend(0.);
transform.translation += move_delta;
}
}
fn input(_: In<ggrs::PlayerHandle>, keys: Res<Input<KeyCode>>) -> Vec<u8> {
let mut input = 0u8;
if keys.any_pressed([KeyCode::Up, KeyCode::W]) {
input |= INPUT_UP;
}
if keys.any_pressed([KeyCode::Down, KeyCode::S]) {
input |= INPUT_DOWN;
}
if keys.any_pressed([KeyCode::Left, KeyCode::A]) {
input |= INPUT_LEFT
}
if keys.any_pressed([KeyCode::Right, KeyCode::D]) {
input |= INPUT_RIGHT;
}
if keys.any_pressed([KeyCode::Space, KeyCode::Return]) {
input |= INPUT_FIRE;
}
vec![input]
}