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