-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split bevy_ggrs example into lib & bin
- Loading branch information
Showing
3 changed files
with
213 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
use bevy::{log::LogPlugin, prelude::*}; | ||
use bevy_ggrs::{GGRSPlugin, GGRSSchedule, Session}; | ||
use bevy_matchbox::prelude::*; | ||
use ggrs::SessionBuilder; | ||
|
||
mod args; | ||
mod box_game; | ||
|
||
use args::*; | ||
use box_game::*; | ||
|
||
const FPS: usize = 60; | ||
|
||
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, States)] | ||
enum AppState { | ||
#[default] | ||
Lobby, | ||
InGame, | ||
} | ||
|
||
const SKY_COLOR: Color = Color::rgb(0.69, 0.69, 0.69); | ||
|
||
pub fn run() { | ||
// read query string or command line arguments | ||
let args = Args::get(); | ||
info!("{:?}", args); | ||
|
||
let mut app = App::new(); | ||
|
||
GGRSPlugin::<GGRSConfig>::new() | ||
// define frequency of rollback game logic update | ||
.with_update_frequency(FPS) | ||
// define system that returns inputs given a player handle, so GGRS can send the inputs | ||
// around | ||
.with_input_system(input) | ||
// register types of components AND resources you want to be rolled back | ||
.register_rollback_component::<Transform>() | ||
.register_rollback_component::<Velocity>() | ||
.register_rollback_resource::<FrameCount>() | ||
// make it happen in the bevy app | ||
.build(&mut app); | ||
|
||
app.insert_resource(ClearColor(SKY_COLOR)) | ||
.add_plugins( | ||
DefaultPlugins | ||
.set(LogPlugin { | ||
filter: "info,wgpu_core=warn,wgpu_hal=warn,matchbox_socket=debug".into(), | ||
level: bevy::log::Level::DEBUG, | ||
}) | ||
.set(WindowPlugin { | ||
primary_window: Some(Window { | ||
title: "Matchbox Bevy GGRS Example".to_string(), | ||
canvas: Some("#bevy_ggrs_example".to_string()), | ||
fit_canvas_to_parent: true, // behave on wasm | ||
..default() | ||
}), | ||
..default() | ||
}), | ||
) | ||
// Some of our systems need the query parameters | ||
.insert_resource(args) | ||
.init_resource::<FrameCount>() | ||
.add_state::<AppState>() | ||
.add_systems((lobby_startup, start_matchbox_socket).in_schedule(OnEnter(AppState::Lobby))) | ||
.add_system(lobby_system.in_set(OnUpdate(AppState::Lobby))) | ||
.add_system(lobby_cleanup.in_schedule(OnExit(AppState::Lobby))) | ||
.add_system(setup_scene_system.in_schedule(OnEnter(AppState::InGame))) | ||
.add_system(log_ggrs_events.in_set(OnUpdate(AppState::InGame))) | ||
// these systems will be executed as part of the advance frame update | ||
.add_systems((move_cube_system, increase_frame_system).in_schedule(GGRSSchedule)) | ||
.run(); | ||
} | ||
|
||
fn start_matchbox_socket(mut commands: Commands, args: Res<Args>) { | ||
let room_id = match &args.room { | ||
Some(id) => id.clone(), | ||
None => format!("bevy_ggrs?next={}", &args.players), | ||
}; | ||
|
||
let room_url = format!("{}/{}", &args.matchbox, room_id); | ||
info!("connecting to matchbox server: {:?}", room_url); | ||
|
||
commands.insert_resource(MatchboxSocket::new_ggrs(room_url)); | ||
} | ||
|
||
// Marker components for UI | ||
#[derive(Component)] | ||
struct LobbyText; | ||
#[derive(Component)] | ||
struct LobbyUI; | ||
|
||
fn lobby_startup(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
commands.spawn(Camera3dBundle::default()); | ||
|
||
// All this is just for spawning centered text. | ||
commands | ||
.spawn(NodeBundle { | ||
style: Style { | ||
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), | ||
position_type: PositionType::Absolute, | ||
justify_content: JustifyContent::Center, | ||
align_items: AlignItems::FlexEnd, | ||
..default() | ||
}, | ||
background_color: Color::rgb(0.43, 0.41, 0.38).into(), | ||
..default() | ||
}) | ||
.with_children(|parent| { | ||
parent | ||
.spawn(TextBundle { | ||
style: Style { | ||
align_self: AlignSelf::Center, | ||
justify_content: JustifyContent::Center, | ||
..default() | ||
}, | ||
text: Text::from_section( | ||
"Entering lobby...", | ||
TextStyle { | ||
font: asset_server.load("fonts/quicksand-light.ttf"), | ||
font_size: 96., | ||
color: Color::BLACK, | ||
}, | ||
), | ||
..default() | ||
}) | ||
.insert(LobbyText); | ||
}) | ||
.insert(LobbyUI); | ||
} | ||
|
||
fn lobby_cleanup(query: Query<Entity, With<LobbyUI>>, mut commands: Commands) { | ||
for e in query.iter() { | ||
commands.entity(e).despawn_recursive(); | ||
} | ||
} | ||
|
||
fn lobby_system( | ||
mut app_state: ResMut<NextState<AppState>>, | ||
args: Res<Args>, | ||
mut socket: ResMut<MatchboxSocket<SingleChannel>>, | ||
mut commands: Commands, | ||
mut query: Query<&mut Text, With<LobbyText>>, | ||
) { | ||
// regularly call update_peers to update the list of connected peers | ||
for (peer, new_state) in socket.update_peers() { | ||
// you can also handle the specific dis(connections) as they occur: | ||
match new_state { | ||
PeerState::Connected => info!("peer {peer:?} connected"), | ||
PeerState::Disconnected => info!("peer {peer:?} disconnected"), | ||
} | ||
} | ||
|
||
let connected_peers = socket.connected_peers().count(); | ||
let remaining = args.players - (connected_peers + 1); | ||
query.single_mut().sections[0].value = format!("Waiting for {remaining} more player(s)",); | ||
if remaining > 0 { | ||
return; | ||
} | ||
|
||
info!("All peers have joined, going in-game"); | ||
|
||
// extract final player list | ||
let players = socket.players(); | ||
|
||
let max_prediction = 12; | ||
|
||
// create a GGRS P2P session | ||
let mut sess_build = SessionBuilder::<GGRSConfig>::new() | ||
.with_num_players(args.players) | ||
.with_max_prediction_window(max_prediction) | ||
.with_input_delay(2) | ||
.with_fps(FPS) | ||
.expect("invalid fps"); | ||
|
||
for (i, player) in players.into_iter().enumerate() { | ||
sess_build = sess_build | ||
.add_player(player, i) | ||
.expect("failed to add player"); | ||
} | ||
|
||
let channel = socket.take_channel(0).unwrap(); | ||
|
||
// start the GGRS session | ||
let sess = sess_build | ||
.start_p2p_session(channel) | ||
.expect("failed to start session"); | ||
|
||
commands.insert_resource(Session::P2PSession(sess)); | ||
|
||
// transition to in-game state | ||
app_state.set(AppState::InGame); | ||
} | ||
|
||
fn log_ggrs_events(mut session: ResMut<Session<GGRSConfig>>) { | ||
match session.as_mut() { | ||
Session::P2PSession(s) => { | ||
for event in s.events() { | ||
info!("GGRS Event: {:?}", event); | ||
} | ||
} | ||
_ => panic!("This example focuses on p2p."), | ||
} | ||
} |
Oops, something went wrong.