Compare commits
4 Commits
209576bc18
...
bb66d28f06
| Author | SHA1 | Date |
|---|---|---|
|
|
bb66d28f06 | 3 days ago |
|
|
ff8578fd82 | 7 days ago |
|
|
b6ac56aba4 | 7 days ago |
|
|
10e5e3523d | 7 days ago |
17 changed files with 3401 additions and 196 deletions
@ -0,0 +1,3 @@ |
||||
[env] |
||||
# Makes Bevy look for the `assets/` directory in the right place. |
||||
BEVY_ASSET_ROOT = { value = ".", relative = true } |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@ |
||||
[package] |
||||
name = "bevy-bloxel-classic" |
||||
version = "0.1.0" |
||||
edition = "2024" |
||||
|
||||
[dependencies] |
||||
common = { path = "../common", package = "bevy-bloxel-classic-common" } |
||||
|
||||
bevy.workspace = true |
||||
lightyear.workspace = true |
||||
serde.workspace = true |
||||
|
||||
bevy_fix_cursor_unlock_web = "0.2.0" |
||||
@ -0,0 +1,31 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
pub use common::block::*; |
||||
|
||||
#[derive(Resource)] |
||||
pub struct BlockResources { |
||||
mesh: Handle<Mesh>, |
||||
material: Handle<StandardMaterial>, |
||||
} |
||||
|
||||
pub fn setup_blocks( |
||||
mut commands: Commands, |
||||
mut meshes: ResMut<Assets<Mesh>>, |
||||
mut materials: ResMut<Assets<StandardMaterial>>, |
||||
) { |
||||
commands.insert_resource(BlockResources { |
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), |
||||
material: materials.add(Color::srgb_u8(124, 144, 255)), |
||||
}); |
||||
} |
||||
|
||||
pub fn add_block_visuals( |
||||
add: On<Add, Block>, |
||||
mut commands: Commands, |
||||
resources: Res<BlockResources>, |
||||
) { |
||||
commands.entity(add.entity).insert(( |
||||
Mesh3d(resources.mesh.clone()), |
||||
MeshMaterial3d(resources.material.clone()), |
||||
)); |
||||
} |
||||
@ -0,0 +1,83 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
use bevy::window::WindowResolution; |
||||
|
||||
mod block; |
||||
mod camera; |
||||
mod placement; |
||||
|
||||
use block::*; |
||||
use camera::*; |
||||
use placement::*; |
||||
|
||||
#[rustfmt::skip] |
||||
fn main() { |
||||
let mut app = App::new(); |
||||
|
||||
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() |
||||
})); |
||||
|
||||
// Fixes issue on web where the cursor isn't ungrabbed properly.
|
||||
app.add_plugins(bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin); |
||||
|
||||
app.add_systems(Startup, setup_crosshair); |
||||
app.add_systems(Startup, setup_blocks); |
||||
app.add_systems(Startup, setup_scene.after(setup_blocks)); |
||||
|
||||
app.add_systems(Update, cursor_grab); |
||||
app.add_systems(Update, update_crosshair_visibility.after(cursor_grab)); |
||||
app.add_systems(Update, camera_look.after(cursor_grab).run_if(is_cursor_grabbed)); |
||||
app.add_systems(Update, noclip_controller.after(camera_look).run_if(is_cursor_grabbed)); |
||||
|
||||
// `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)); |
||||
|
||||
app.add_observer(add_block_visuals); |
||||
|
||||
// FIXME: Don't hardcode this!
|
||||
#[cfg(not(target_arch = "wasm32"))] |
||||
app.add_plugins(common::network::ServerPlugin); |
||||
#[cfg(target_arch = "wasm32")] |
||||
app.add_plugins(common::network::ClientPlugin); |
||||
|
||||
app.run(); |
||||
} |
||||
|
||||
fn setup_scene(mut commands: Commands, mut blocks: Blocks) { |
||||
// light
|
||||
commands.spawn(( |
||||
PointLight { |
||||
shadows_enabled: true, |
||||
..default() |
||||
}, |
||||
Transform::from_xyz(4.0, 8.0, 4.0), |
||||
)); |
||||
|
||||
// camera
|
||||
commands.spawn(( |
||||
Camera3d::default(), |
||||
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), |
||||
CameraFreeLook::default(), |
||||
CameraNoClip::default(), |
||||
)); |
||||
|
||||
// blocks
|
||||
for x in -8..8 { |
||||
for z in -8..8 { |
||||
blocks.spawn(IVec3::new(x, 0, z)); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
[package] |
||||
name = "bevy-bloxel-classic-common" |
||||
version = "0.1.0" |
||||
edition = "2024" |
||||
|
||||
[dependencies] |
||||
bevy.workspace = true |
||||
lightyear.workspace = true |
||||
serde.workspace = true |
||||
@ -0,0 +1,31 @@ |
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::*; |
||||
|
||||
use bevy::ecs::system::SystemParam; |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[derive(Component, PartialEq, Deserialize, Serialize)] |
||||
pub struct Block; |
||||
|
||||
#[derive(SystemParam)] |
||||
pub struct Blocks<'w, 's> { |
||||
commands: Commands<'w, 's>, |
||||
blocks: Query<'w, 's, &'static Transform, With<Block>>, |
||||
} |
||||
|
||||
impl Blocks<'_, '_> { |
||||
pub fn spawn(&mut self, pos: IVec3) { |
||||
self.commands.spawn(( |
||||
Block, |
||||
Transform::from_translation(pos.as_vec3() + Vec3::ONE / 2.), |
||||
// Currently prints some warnings when no `Server` is active, but this is fine.
|
||||
Replicate::to_clients(NetworkTarget::All), |
||||
)); |
||||
} |
||||
|
||||
/// Gets the position of a block entity, or `None` if not a block.
|
||||
pub fn position(&self, entity: Entity) -> Option<IVec3> { |
||||
let transform = self.blocks.get(entity).ok(); |
||||
transform.map(|t| t.translation.floor().as_ivec3()) |
||||
} |
||||
} |
||||
@ -0,0 +1,2 @@ |
||||
pub mod block; |
||||
pub mod network; |
||||
@ -0,0 +1,63 @@ |
||||
use std::net::{Ipv4Addr, SocketAddr}; |
||||
|
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::client::*; |
||||
use lightyear::prelude::*; |
||||
|
||||
// FIXME: Don't hardcode this!
|
||||
pub const DIGEST: &'static str = ""; |
||||
|
||||
pub struct ClientPlugin; |
||||
|
||||
impl Plugin for ClientPlugin { |
||||
fn build(&self, app: &mut App) { |
||||
app.add_plugins(ClientPlugins::default()); |
||||
// This maybe should be added by `ClientPlugins` but it currently isn't.
|
||||
// (Unless we're using `NetcodeClientPlugin`, which would've added it.)
|
||||
if !app.is_plugin_added::<lightyear::connection::client::ConnectionPlugin>() { |
||||
app.add_plugins(lightyear::connection::client::ConnectionPlugin); |
||||
} |
||||
|
||||
if !app.is_plugin_added::<super::ProtocolPlugin>() { |
||||
app.add_plugins(super::ProtocolPlugin); |
||||
} |
||||
|
||||
app.add_systems(Startup, connect_to_server); |
||||
|
||||
app.add_observer(on_connecting); |
||||
app.add_observer(on_connected); |
||||
app.add_observer(on_disconnected); |
||||
} |
||||
} |
||||
|
||||
fn connect_to_server(mut commands: Commands) { |
||||
let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); |
||||
let server_addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), super::DEFAULT_PORT); |
||||
let certificate_digest = DIGEST.to_string(); |
||||
|
||||
commands |
||||
.spawn(( |
||||
Name::from("Client"), |
||||
LocalAddr(client_addr), |
||||
PeerAddr(server_addr), |
||||
ReplicationReceiver::default(), |
||||
WebTransportClientIo { certificate_digest }, |
||||
RawClient, |
||||
)) |
||||
.trigger(|entity| LinkStart { entity }); |
||||
} |
||||
|
||||
fn on_connecting(event: On<Add, Connecting>) { |
||||
let client = event.entity; |
||||
info!("Client '{client}' connecting ..."); |
||||
} |
||||
|
||||
fn on_connected(event: On<Add, Connected>) { |
||||
let client = event.entity; |
||||
info!("Client '{client}' connected!"); |
||||
} |
||||
|
||||
fn on_disconnected(event: On<Remove, Connected>) { |
||||
let client = event.entity; |
||||
info!("Client '{client}' disconnected!"); |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
mod client; |
||||
mod protocol; |
||||
mod server; |
||||
|
||||
pub use client::*; |
||||
pub use protocol::*; |
||||
pub use server::*; |
||||
|
||||
pub const DEFAULT_PORT: u16 = 13580; |
||||
@ -0,0 +1,13 @@ |
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::*; |
||||
|
||||
use crate::block::Block; |
||||
|
||||
pub struct ProtocolPlugin; |
||||
|
||||
impl Plugin for ProtocolPlugin { |
||||
fn build(&self, app: &mut App) { |
||||
app.register_component::<Transform>(); |
||||
app.register_component::<Block>(); |
||||
} |
||||
} |
||||
@ -0,0 +1,91 @@ |
||||
use std::net::{Ipv4Addr, SocketAddr}; |
||||
|
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::server::*; |
||||
use lightyear::prelude::*; |
||||
|
||||
pub struct ServerPlugin; |
||||
|
||||
impl Plugin for ServerPlugin { |
||||
fn build(&self, app: &mut App) { |
||||
app.add_plugins(ServerPlugins::default()); |
||||
// These maybe should be added by `ServerPlugins` but currently aren't.
|
||||
// (Unless we're using `NetcodeServerPlugin`, which would've added it.)
|
||||
if !app.is_plugin_added::<lightyear::connection::client::ConnectionPlugin>() { |
||||
app.add_plugins(lightyear::connection::client::ConnectionPlugin); |
||||
} |
||||
if !app.is_plugin_added::<lightyear::connection::server::ConnectionPlugin>() { |
||||
app.add_plugins(lightyear::connection::server::ConnectionPlugin); |
||||
} |
||||
|
||||
if !app.is_plugin_added::<super::ProtocolPlugin>() { |
||||
app.add_plugins(super::ProtocolPlugin); |
||||
} |
||||
|
||||
app.add_systems(Startup, start_server); |
||||
|
||||
// TODO: See what happens when we try to start a server, but it
|
||||
// can't, for example due to the port already being in use.
|
||||
app.add_observer(on_server_started); |
||||
app.add_observer(on_server_stopped); |
||||
app.add_observer(handle_client_connected); |
||||
app.add_observer(handle_client_disconnected); |
||||
} |
||||
} |
||||
|
||||
fn start_server(mut commands: Commands) -> Result { |
||||
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), super::DEFAULT_PORT); |
||||
let certificate = Identity::self_signed(["localhost", "127.0.0.1", "::1"])?; |
||||
|
||||
commands |
||||
.spawn(( |
||||
Name::from("Server"), |
||||
LocalAddr(server_addr), |
||||
WebTransportServerIo { certificate }, |
||||
RawServer, |
||||
)) |
||||
.trigger(|entity| LinkStart { entity }); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn on_server_started(event: On<Add, Started>, servers: Query<&WebTransportServerIo>) -> Result { |
||||
let server = event.entity; |
||||
info!("Server '{server}' started!"); |
||||
|
||||
let certificate = &servers.get(server)?.certificate; |
||||
let certificate_hash = certificate.certificate_chain().as_slice()[0].hash(); |
||||
let certificate_digest = certificate_hash.to_string().replace(':', ""); |
||||
|
||||
info!("== Certificate Digest =="); |
||||
info!(" Clients use this to securely connect to the server."); |
||||
info!(" {certificate_digest}"); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn on_server_stopped(event: On<Add, Stopped>) { |
||||
let server = event.entity; |
||||
info!("Server '{server}' stopped!"); |
||||
} |
||||
|
||||
fn handle_client_connected( |
||||
event: On<Add, Connected>, |
||||
clients: Query<&LinkOf>, |
||||
mut commands: Commands, |
||||
) -> Result { |
||||
let client = event.entity; |
||||
let server = clients.get(client)?.server; |
||||
info!("Client '{client}' connected to server '{server}'"); |
||||
|
||||
commands.entity(client).insert(ReplicationSender::default()); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn handle_client_disconnected(event: On<Add, Disconnected>, clients: Query<&LinkOf>) -> Result { |
||||
let client = event.entity; |
||||
let server = clients.get(client)?.server; |
||||
info!("Client '{client}' disconnected from server '{server}'"); |
||||
Ok(()) |
||||
} |
||||
@ -1,47 +0,0 @@ |
||||
use bevy::ecs::system::SystemParam; |
||||
use bevy::prelude::*; |
||||
|
||||
#[derive(Component)] |
||||
pub struct Block; |
||||
|
||||
#[derive(SystemParam)] |
||||
pub struct Blocks<'w, 's> { |
||||
commands: Commands<'w, 's>, |
||||
block_resources: Res<'w, BlockResources>, |
||||
blocks: Query<'w, 's, &'static Transform, With<Block>>, |
||||
} |
||||
|
||||
impl Blocks<'_, '_> { |
||||
pub fn spawn(&mut self, pos: IVec3) { |
||||
self.commands.spawn(( |
||||
Block, |
||||
Mesh3d(self.block_resources.mesh.clone()), |
||||
MeshMaterial3d(self.block_resources.material.clone()), |
||||
Transform::from_translation(pos.as_vec3() + Vec3::ONE / 2.), |
||||
)); |
||||
} |
||||
|
||||
/// Gets the position of a block entity, or `None`
|
||||
/// if the given entity is not alive or not a block.
|
||||
pub fn position(&self, entity: Entity) -> Option<IVec3> { |
||||
let transform = self.blocks.get(entity).ok(); |
||||
transform.map(|t| t.translation.floor().as_ivec3()) |
||||
} |
||||
} |
||||
|
||||
#[derive(Resource)] |
||||
pub struct BlockResources { |
||||
mesh: Handle<Mesh>, |
||||
material: Handle<StandardMaterial>, |
||||
} |
||||
|
||||
pub fn setup_blocks( |
||||
mut commands: Commands, |
||||
mut meshes: ResMut<Assets<Mesh>>, |
||||
mut materials: ResMut<Assets<StandardMaterial>>, |
||||
) { |
||||
commands.insert_resource(BlockResources { |
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), |
||||
material: materials.add(Color::srgb_u8(124, 144, 255)), |
||||
}); |
||||
} |
||||
@ -1,55 +0,0 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
mod block; |
||||
mod camera; |
||||
mod placement; |
||||
|
||||
use block::*; |
||||
use camera::*; |
||||
use placement::*; |
||||
|
||||
fn main() { |
||||
#[rustfmt::skip] |
||||
App::new() |
||||
.add_plugins(DefaultPlugins) |
||||
|
||||
.add_systems(Startup, setup_crosshair) |
||||
.add_systems(Startup, setup_blocks) |
||||
.add_systems(Startup, setup_scene.after(setup_blocks)) |
||||
|
||||
.add_systems(Update, cursor_grab) |
||||
.add_systems(Update, camera_look.after(cursor_grab).run_if(is_cursor_grabbed)) |
||||
.add_systems(Update, noclip_controller.after(camera_look).run_if(is_cursor_grabbed)) |
||||
|
||||
// This system requires the camera's `GlobalTransform`, so for
|
||||
// a most up-to-date value, run it right after it's been updated.
|
||||
.add_systems(PostUpdate, place_break_blocks.after(TransformSystems::Propagate)) |
||||
|
||||
.run(); |
||||
} |
||||
|
||||
fn setup_scene(mut commands: Commands, mut blocks: Blocks) { |
||||
// light
|
||||
commands.spawn(( |
||||
Camera3d::default(), |
||||
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), |
||||
CameraFreeLook::default(), |
||||
CameraNoClip::default(), |
||||
)); |
||||
|
||||
// camera
|
||||
commands.spawn(( |
||||
PointLight { |
||||
shadows_enabled: true, |
||||
..default() |
||||
}, |
||||
Transform::from_xyz(4.0, 8.0, 4.0), |
||||
)); |
||||
|
||||
// blocks
|
||||
for x in -8..8 { |
||||
for z in -8..8 { |
||||
blocks.spawn(IVec3::new(x, 0, z)); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue