Bloxel sandbox game similar to Minecraft "Classic" (2009) written in Rust with Bevy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
4.1 KiB

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<Args>, 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<Add, Started>, 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<Add, Predicted>, mut commands: Commands) {
let player = event.entity;
commands.entity(player).insert((
// Handle inputs on this entity.
InputMarker::<Inputs>::default(),
// TODO: Attach camera to player head eventually.
Camera3d::default(),
));
}