use bevy::prelude::*; use common::prelude::network::*; use common::prelude::*; use lightyear::prelude::*; use bevy::asset::AssetMetaCheck; use bevy::window::WindowResolution; use lightyear::prelude::input::native::InputMarker; use lightyear::prelude::server::Started; mod args; mod assets; mod input; mod placement; mod ui; use args::*; use placement::*; use input::is_cursor_grabbed; #[derive(States, Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Screen { Loading, Gameplay, } fn main() -> Result { let args = Args::parse(); let mut app = App::new(); app.insert_resource(args); app.insert_state(Screen::Loading); app.add_plugins( DefaultPlugins .set(WindowPlugin { primary_window: Some(Window { title: "bevy-bloxel-classic".into(), // Steam Deck: DPI appears pretty high, causing everything to be scaled up. // Setting `scale_factor_override` prevents this from happening. resolution: WindowResolution::new(1280, 720).with_scale_factor_override(1.0), // WEB: Fit canvas to parent element, so `Window` resizes automatically. fit_canvas_to_parent: true, // WEB: Don't override default event handling like browser hotkeys while focused. prevent_default_event_handling: false, ..default() }), ..default() }) .set(AssetPlugin { // WEB: Don't check for `.meta` files since we don't use them. meta_check: AssetMetaCheck::Never, ..default() }), ); app.add_plugins(( bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin, common::asset_loading::plugin, common::network::plugin, assets::plugin, input::plugin, ui::plugin, )); app.add_systems( OnEnter(Screen::Gameplay), (setup_scene, start_server_or_connect), ); // `place_break_blocks` requires the camera's `GlobalTransform`. // For a most up-to-date value, run it after that's been updated. app.add_systems( PostUpdate, place_break_blocks .after(TransformSystems::Propagate) .run_if(in_state(Screen::Gameplay).and(is_cursor_grabbed)), ); app.add_observer(spawn_initial_blocks); app.add_observer(handle_predicted_player_spawn); app.run(); Ok(()) } fn setup_scene(mut commands: Commands) { commands.spawn(( PointLight { shadows_enabled: true, ..default() }, Transform::from_xyz(4.0, 8.0, 4.0), )); } fn start_server_or_connect(args: Res, mut commands: Commands) -> Result { let mode = args.mode.as_ref(); let default = Mode::default(); match mode.unwrap_or(&default) { Mode::Local => { commands.queue(StartLocalServerCommand); } #[cfg(not(target_family = "wasm"))] Mode::Host { port } => { commands.queue(StartWebTransportServerCommand::new(*port)); } Mode::Connect { address, digest } => { commands.queue(ConnectWebTransportCommand::new(&address, digest.clone())?); } } // NOTE: When setting up a local server, a host-client is automatically // connected to it thanks to the `autoconnect_host_client` observer. Ok(()) } /// When the server is started, spawn the initial blocks the world is made of. fn spawn_initial_blocks(_event: On, mut blocks: Blocks) { for x in -8..8 { for z in -8..8 { blocks.spawn(IVec3::new(x, 0, z)); } } } /// When the `Predicted` player we control is being spawned, insert necessary components. fn handle_predicted_player_spawn(event: On, mut commands: Commands) { let player = event.entity; commands.entity(player).insert(( // Handle inputs on this entity. InputMarker::::default(), // TODO: Attach camera to player head eventually. Camera3d::default(), )); }