Fixed Rotation and scale and rotation sync.

Moved Input To its own Plugin.
Moved the camera to the Environment Plugin
This commit is contained in:
Elias Stepanik 2025-04-21 17:35:00 +07:00
parent 9442befb27
commit befbfb8935
28 changed files with 366 additions and 211 deletions

View File

@ -5,6 +5,7 @@ edition = "2024"
description = "Horror-Game"
repository = "https://github.com/eliasstepanik/horror-game"
license = "MIT OR Apache-2.0"
build = "build.rs"
[dependencies]
@ -20,3 +21,7 @@ spacetimedb-sdk = "1.0"
hex = "0.4"
random_word = { version = "0.5.0", features = ["en"] }
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"

6
client/Config.toml Normal file
View File

@ -0,0 +1,6 @@
[server]
host = "http://100.85.241.101:3000"
database = "network-game"
[movement]
mode = "fligt"

10
client/build.rs Normal file
View File

@ -0,0 +1,10 @@
use std::fs;
use std::path::Path;
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let target_dir = Path::new(&out_dir).ancestors().nth(3).unwrap(); // gets target/debug or release
fs::copy("config.toml", target_dir.join("config.toml"))
.expect("Failed to copy config.toml to target directory");
}

View File

@ -24,12 +24,10 @@ impl Plugin for AppPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(UiState::new());
app.insert_resource(InspectorVisible(true));
app.add_plugins(crate::plugins::camera::camera_plugin::CameraPlugin);
app.add_plugins(crate::plugins::ui::ui_plugin::UiPlugin);
app.add_plugins(crate::plugins::environment::environment_plugin::EnvironmentPlugin);
app.add_plugins(crate::plugins::network::network_plugin::NetworkPlugin);
app.add_plugins(crate::plugins::input::input_plugin::InputPlugin);
app.add_systems(Update, (debug_gizmos, toggle_ui_system));
app.add_systems(

15
client/src/config.rs Normal file
View File

@ -0,0 +1,15 @@
use bevy::prelude::Resource;
use serde::Deserialize;
#[derive(Debug, Deserialize, Resource)]
pub struct Config {
pub server: ServerConfig,
}
#[derive(Debug, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub database: String,
}

12
client/src/helper/math.rs Normal file
View File

@ -0,0 +1,12 @@
pub trait RoundTo {
/// Rounds `self` to `decimals` places.
fn round_to(self, decimals: u32) -> f32;
}
impl RoundTo for f32 {
#[inline]
fn round_to(self, decimals: u32) -> f32 {
let factor = 10f32.powi(decimals as i32);
(self * factor).round() / factor
}
}

View File

@ -1,2 +1,4 @@
pub mod debug_gizmos;
pub mod egui_dock;
pub mod vector_helper;
pub mod math;

View File

@ -0,0 +1,30 @@
use bevy::math::Vec3;
use bevy::prelude::{Quat, Transform};
use rand::Rng;
use crate::helper::math::RoundTo;
use crate::module_bindings::DbTransform;
pub(crate) fn random_vec3(min: f32, max: f32) -> Vec3 {
let mut rng = rand::thread_rng();
Vec3::new(
rng.gen_range(min..max),
rng.gen_range(min..max),
rng.gen_range(min..max),
)
}
impl From<DbTransform> for Transform {
fn from(db: DbTransform) -> Self {
Transform {
translation: Vec3::new(db.position.x, db.position.y, db.position.z),
rotation: //Quat::from_xyzw(0.0, 0.0, 0.0, 0.0),
Quat::from_xyzw(
db.rotation.x.round_to(3),
db.rotation.y.round_to(3),
db.rotation.z.round_to(3),
db.rotation.w.round_to(3),
),
scale: Vec3::new(db.scale.x.round_to(3), db.scale.y.round_to(3), db.scale.z.round_to(3)),
}
}
}

View File

@ -2,7 +2,9 @@ mod app;
mod helper;
mod plugins;
mod module_bindings;
mod config;
use std::fs;
use crate::app::AppPlugin;
use bevy::gizmos::{AppGizmoBuilder, GizmoPlugin};
use bevy::log::info;
@ -13,6 +15,8 @@ use bevy::DefaultPlugins;
use bevy_egui::EguiPlugin;
use bevy_inspector_egui::DefaultInspectorConfigPlugin;
use bevy_window::{PresentMode, Window, WindowPlugin};
use toml;
use crate::config::Config;
const TITLE: &str = "horror-game";
const RESOLUTION: (f32, f32) = (1920f32, 1080f32);
@ -21,13 +25,27 @@ const DECORATIONS: bool = true;
const TRANSPARENT: bool = true;
const PRESENT_MODE: PresentMode = PresentMode::AutoVsync;
fn main() {
let config_str = fs::read_to_string("Config.toml").expect("Failed to read config file");
let config: Config = toml::from_str(&config_str).expect("Failed to parse config");
let mut app = App::new();
app.insert_resource(config);
register_platform_plugins(&mut app);
app.add_plugins(AppPlugin);
app.add_plugins(EguiPlugin);
app.add_plugins(DefaultInspectorConfigPlugin);
/*app.add_plugins(GizmoPlugin);*/
app.run();

View File

@ -1,16 +0,0 @@
use bevy::a11y::AccessibilitySystem::Update;
use bevy::app::{App, Plugin, PreUpdate, Startup};
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, _app: &mut App) {
_app.add_systems(
Startup,
(crate::plugins::camera::systems::camera_system::setup),
);
_app.add_systems(
PreUpdate,
(crate::plugins::camera::systems::camera_system::camera_controller_system),
);
}
}

View File

@ -1,2 +0,0 @@
pub mod camera_plugin;
pub mod systems;

View File

@ -1 +0,0 @@
pub mod camera_system;

View File

@ -1,6 +1,11 @@
use bevy::app::{App, Plugin};
use bevy::app::{App, Plugin, PreStartup, PreUpdate, Startup};
pub struct EnvironmentPlugin;
impl Plugin for EnvironmentPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Startup,
(crate::plugins::environment::systems::camera_system::setup),
);
}
}

View File

@ -0,0 +1,53 @@
use crate::helper::egui_dock::MainCamera;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::Vec3;
use bevy::prelude::*;
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
use bevy_window::CursorGrabMode;
use rand::Rng;
use random_word::Lang;
use crate::module_bindings::{set_name, set_position, spawn_entity, DbTransform, DbVector3, DbVector4};
use crate::plugins::network::systems::database::DbConnectionResource;
#[derive(Component)]
pub struct CameraController {
pub yaw: f32,
pub pitch: f32,
pub speed: f32,
pub sensitivity: f32,
}
impl Default for CameraController {
fn default() -> Self {
Self {
yaw: 0.0,
pitch: 0.0,
speed: 10.0,
sensitivity: 0.1,
}
}
}
pub fn setup(mut commands: Commands,) {
commands.spawn((
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
GlobalTransform::default(),
Camera3d::default(),
Projection::from(PerspectiveProjection {
near: 0.0001,
..default()
}),
MainCamera,
CameraController::default(),
Exposure::from_physical_camera(PhysicalCameraParameters {
aperture_f_stops: 1.0,
shutter_speed_s: 1.0 / 125.0,
sensitivity_iso: 100.0,
sensor_height: 0.01866,
}),
));
}

View File

@ -1 +1,2 @@
pub mod environment_system;
pub mod camera_system;

View File

@ -0,0 +1,20 @@
use bevy::app::{App, Plugin, PreUpdate, Startup};
use bevy::prelude::{IntoSystemConfigs, Update};
pub struct InputPlugin;
impl Plugin for InputPlugin {
fn build(&self, _app: &mut App) {
_app.add_systems(
Update,
(
crate::plugins::input::systems::console::console_system,
crate::plugins::input::systems::flight::flight_systems,
crate::plugins::input::systems::ui::ui_system,
crate::plugins::input::systems::network::network_system,
crate::plugins::input::systems::movement::movement_system,
),
);
}
}

View File

@ -0,0 +1,2 @@
pub mod systems;
pub mod input_plugin;

View File

@ -0,0 +1,23 @@
use bevy::app::AppExit;
use bevy::input::ButtonInput;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Time, Transform};
use bevy_window::Window;
use crate::plugins::environment::systems::camera_system::CameraController;
use crate::plugins::network::systems::database::DbConnectionResource;
pub fn console_system(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>, /*
mouse_button_input: Res<ButtonInput<MouseButton>>,*/
mut mouse_motion_events: EventReader<MouseMotion>,
mut mouse_wheel_events: EventReader<MouseWheel>,
mut windows: Query<&mut Window>,
mut query: Query<(&mut Transform, &mut CameraController)>,
mut app_exit_events: EventWriter<AppExit>,
mut ctx: ResMut<DbConnectionResource>,
) {
let mut window = windows.single_mut();
let (mut transform, mut controller) = query.single_mut();
}

View File

@ -1,68 +1,16 @@
use crate::helper::egui_dock::MainCamera;
use bevy::app::AppExit;
use bevy::input::ButtonInput;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::Vec3;
use bevy::prelude::*;
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
use bevy_window::CursorGrabMode;
use rand::Rng;
use bevy::math::{Quat, Vec3};
use bevy::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Time, Transform};
use bevy_window::{CursorGrabMode, Window};
use random_word::Lang;
use crate::module_bindings::{set_name, set_position, spawn_entity, DbTransform, DbVector3, DbVector4};
use crate::plugins::environment::systems::camera_system::CameraController;
use crate::plugins::network::systems::database::DbConnectionResource;
#[derive(Component)]
pub struct CameraController {
pub yaw: f32,
pub pitch: f32,
pub speed: f32,
pub sensitivity: f32,
}
impl Default for CameraController {
fn default() -> Self {
Self {
yaw: 0.0,
pitch: 0.0,
speed: 10.0,
sensitivity: 0.1,
}
}
}
pub fn setup(mut commands: Commands,
db_resource: Res<DbConnectionResource>,) {
commands.spawn((
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
GlobalTransform::default(),
Camera3d::default(),
Projection::from(PerspectiveProjection {
near: 0.0001,
..default()
}),
MainCamera,
CameraController::default(),
Exposure::from_physical_camera(PhysicalCameraParameters {
aperture_f_stops: 1.0,
shutter_speed_s: 1.0 / 125.0,
sensitivity_iso: 100.0,
sensor_height: 0.01866,
}),
));
}
fn random_vec3(min: f32, max: f32) -> Vec3 {
let mut rng = rand::thread_rng();
Vec3::new(
rng.gen_range(min..max),
rng.gen_range(min..max),
rng.gen_range(min..max),
)
}
/// Example system to control a camera using double-precision for position.
pub fn camera_controller_system(
/// Example system to input a camera using double-precision for position.
pub fn flight_systems(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>, /*
mouse_button_input: Res<ButtonInput<MouseButton>>,*/
@ -110,34 +58,6 @@ pub fn camera_controller_system(
}
}
let word = random_word::get(Lang::En);
if keyboard_input.just_pressed(KeyCode::KeyQ) {
ctx.0.reducers.set_name(word.to_string()).unwrap();
}
if keyboard_input.just_pressed(KeyCode::KeyE) {
let rand_position = random_vec3(-10.0,10.0);
let rand_rotation = random_vec3(-10.0,10.0);
let rand_scale = random_vec3(0.1,1.0);
ctx.0.reducers.spawn_entity(DbTransform{
position: DbVector3{
x: rand_position.x,
y: rand_position.y,
z: rand_position.z,
},
rotation: DbVector4 {
x: rand_rotation.x,
y: rand_rotation.y,
z: rand_position.z,
w: 0.0,
},
scale: DbVector3 {
x: rand_scale.x,
y: rand_scale.x,
z: rand_scale.x,
},
}).unwrap();
}
// ====================
// 3) Handle Keyboard Movement (WASD, Space, Shift)
@ -185,28 +105,5 @@ pub fn camera_controller_system(
}).expect("TODO: panic message");
// =========================
// 4) Lock/Unlock Mouse (L)
// =========================
if keyboard_input.just_pressed(KeyCode::KeyL) {
// Toggle between locked and unlocked
if window.cursor_options.grab_mode == CursorGrabMode::None {
// Lock
window.cursor_options.visible = false;
window.cursor_options.grab_mode = CursorGrabMode::Locked;
} else {
// Unlock
window.cursor_options.visible = true;
window.cursor_options.grab_mode = CursorGrabMode::None;
}
}
// =======================
// 7) Exit on Escape
// =======================
if keyboard_input.pressed(KeyCode::Escape) {
app_exit_events.send(Default::default());
}
}

View File

@ -0,0 +1,5 @@
pub mod movement;
pub mod flight;
pub mod console;
pub mod ui;
pub mod network;

View File

@ -0,0 +1,22 @@
use bevy::app::AppExit;
use bevy::input::ButtonInput;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Time, Transform};
use bevy_window::Window;
use crate::plugins::environment::systems::camera_system::CameraController;
use crate::plugins::network::systems::database::DbConnectionResource;
///TODO
pub fn movement_system(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>, /*
mouse_button_input: Res<ButtonInput<MouseButton>>,*/
mut mouse_motion_events: EventReader<MouseMotion>,
mut mouse_wheel_events: EventReader<MouseWheel>,
mut windows: Query<&mut Window>,
mut query: Query<(&mut Transform, &mut CameraController)>,
mut app_exit_events: EventWriter<AppExit>,
mut ctx: ResMut<DbConnectionResource>,
) {
}

View File

@ -0,0 +1,47 @@
use bevy::input::ButtonInput;
use bevy::math::{EulerRot, Quat};
use bevy::prelude::{KeyCode, Res, ResMut,};
use random_word::Lang;
use crate::module_bindings::{set_name, spawn_entity, DbTransform, DbVector3, DbVector4};
use crate::plugins::network::systems::database::DbConnectionResource;
pub fn network_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
ctx: ResMut<DbConnectionResource>,
) {
let word = random_word::get(Lang::En);
if keyboard_input.just_pressed(KeyCode::KeyQ) {
ctx.0.reducers.set_name(word.to_string()).unwrap();
}
if keyboard_input.just_pressed(KeyCode::KeyE) {
let rand_position = crate::helper::vector_helper::random_vec3(-10.0, 10.0);
let rand_rotation = crate::helper::vector_helper::random_vec3(0.0, 10.0);
let rand_rotation = Quat::from_euler(EulerRot::XYZ,rand_rotation.x,rand_rotation.y,rand_rotation.z).normalize();
let rand_scale = crate::helper::vector_helper::random_vec3(0.1, 1.0);
ctx.0.reducers.spawn_entity(DbTransform{
position: DbVector3{
x: rand_position.x,
y: rand_position.y,
z: rand_position.z,
},
rotation: DbVector4 {
x: rand_rotation.x,
y: rand_rotation.y,
z: rand_rotation.z,
w: rand_rotation.w,
},
scale: DbVector3 {
x: rand_scale.x,
y: rand_scale.x,
z: rand_scale.x,
},
}).unwrap();
}
}

View File

@ -0,0 +1,29 @@
use bevy::app::AppExit;
use bevy::input::ButtonInput;
use bevy::prelude::{EventWriter, KeyCode, Query, Res,};
use bevy_window::{CursorGrabMode, Window};
pub fn ui_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut app_exit_events: EventWriter<AppExit>,
mut windows: Query<&mut Window>,
) {
let mut window = windows.single_mut();
if keyboard_input.just_pressed(KeyCode::KeyL) {
// Toggle between locked and unlocked
if window.cursor_options.grab_mode == CursorGrabMode::None {
// Lock
window.cursor_options.visible = false;
window.cursor_options.grab_mode = CursorGrabMode::Locked;
} else {
// Unlock
window.cursor_options.visible = true;
window.cursor_options.grab_mode = CursorGrabMode::None;
}
}
if keyboard_input.pressed(KeyCode::Escape) {
app_exit_events.send(Default::default());
}
}

View File

@ -1,5 +1,6 @@
pub mod camera;
pub mod environment;
pub mod ui;
pub mod network;
pub mod input;

View File

@ -10,7 +10,6 @@ pub struct NetworkPlugin;
impl Plugin for NetworkPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreStartup, setup_database);
app.add_systems(Startup, init);
app.add_systems(Update, sync_entities_system);
app.add_systems(PostUpdate, sync_entities_system);
}
}

View File

@ -1,7 +1,10 @@
use std::fmt::Debug;
use std::ops::Deref;
use bevy::ecs::system::SystemState;
use bevy::prelude::{Commands, Resource, World};
use bevy::prelude::{Commands, DetectChanges, Mut, Res, ResMut, Resource, World};
use bevy::utils::info;
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
use crate::config::ServerConfig;
use crate::module_bindings::*;
use crate::plugins::network::systems::callbacks::*;
use crate::plugins::network::systems::connection::*;
@ -16,9 +19,9 @@ const DB_NAME: &str = "network-game";
#[derive(Resource)]
pub struct DbConnectionResource(pub(crate) DbConnection);
pub fn setup_database(mut commands: Commands) {
pub fn setup_database(mut commands: Commands, config: Res<crate::Config>) {
// Call your connection function and insert the connection as a resource.
let ctx = connect_to_db();
let ctx = connect_to_db(config);
register_callbacks(&ctx);
subscribe_to_tables(&ctx);
ctx.run_threaded();
@ -30,13 +33,16 @@ pub fn setup_database(mut commands: Commands) {
/// Register subscriptions for all rows of both tables
fn connect_to_db() -> DbConnection {
fn connect_to_db(config: Res<crate::Config>) -> DbConnection {
println!("It's there: {:?}", &config.server);
DbConnection::builder()
.on_connect(on_connected)
.on_connect_error(on_connect_error)
.on_disconnect( on_disconnected)
.with_module_name(DB_NAME)
.with_uri(HOST)
.with_module_name(&config.server.database)
.with_uri(&config.server.host)
.build()
.expect("Failed to connect")
}

View File

@ -1,11 +1,13 @@
use std::collections::HashSet;
use bevy::math::Vec3;
use bevy::log::debug;
use bevy::math::{NormedVectorSpace, Vec3};
use bevy::pbr::{MeshMaterial3d, StandardMaterial};
use bevy::prelude::{default, Bundle, Commands, Component, Cuboid, DespawnRecursiveExt, Entity, GlobalTransform, Mesh, Quat, Query, Res, ResMut, Sphere, Transform};
use bevy::prelude::{default, info, Bundle, Commands, Component, Cuboid, DespawnRecursiveExt, DetectChangesMut, Entity, GlobalTransform, Mesh, PbrBundle, Quat, Query, Res, ResMut, Sphere, Transform, TransformBundle};
use bevy_asset::Assets;
use bevy_reflect::Reflect;
use bevy_render::mesh::Mesh3d;
use spacetimedb_sdk::Table;
use crate::helper::math::RoundTo;
use crate::module_bindings::{DbTransform, DbVector3, EntityTableAccess, EntityType};
use crate::plugins::network::systems::database::DbConnectionResource;
@ -18,35 +20,18 @@ pub struct EntityDto {
pub transform: DbTransform,
}
pub fn init(mut commands: Commands,
ctx: Res<DbConnectionResource>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,) {
let debug_material = materials.add(StandardMaterial { ..default() });
for entity in ctx.0.db.entity().iter() {
commands.spawn((
Mesh3d(meshes.add(Cuboid::default()),),
MeshMaterial3d(debug_material.clone ()),
db_transfrom_to_transfrom(entity.transform.clone()),
EntityDto{
entity_id: entity.entity_id,
transform: entity.transform
impl From<crate::module_bindings::Entity> for EntityDto {
fn from(e: crate::module_bindings::Entity) -> Self {
EntityDto {
entity_id: e.entity_id,
transform: e.transform,
}
));
}
}
// System that syncs DB entities with the Bevy ECS
pub fn sync_entities_system(
mut commands: Commands,
@ -54,7 +39,7 @@ pub fn sync_entities_system(
// We need the Entity handle for potential despawning,
// plus mutable references if we want to update Transform/EntityDto
mut query: Query<(Entity, &mut Transform, &mut EntityDto)>,
mut query: Query<(Entity, &mut Transform, &mut GlobalTransform, &mut EntityDto)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
@ -67,16 +52,20 @@ pub fn sync_entities_system(
for db_entity in db_entities.iter() {
// Try to find a matching ECS entity by entity_id
if let Some((_, mut transform, mut dto)) =
query.iter_mut().find(|(_, _, dto)| dto.entity_id == db_entity.entity_id)
if let Some((_, mut transform, mut global, mut dto)) =
query.iter_mut().find(|(_, _, _, dto)| dto.entity_id == db_entity.entity_id)
{
// Update fields
dto.transform.position = db_entity.transform.position.clone();
transform.translation = Vec3::new(
db_entity.transform.position.x,
db_entity.transform.position.y,
db_entity.transform.position.z,
);
// build the new local Transform
let new_tf = Transform::from(db_entity.transform.clone());
// overwrite both components
*transform = new_tf;
*global = GlobalTransform::from(new_tf);
// keep your DTO in sync
dto.transform = db_entity.transform.clone();
} else {
// Not found in ECS, so spawn a new entity
@ -92,46 +81,25 @@ pub fn sync_entities_system(
EntityType::Custom => todo!(),
};
let new_tf = Transform::from(db_entity.transform.clone());
commands.spawn((
db_transfrom_to_transfrom(db_entity.transform.clone()),
GlobalTransform::default(),
TransformBundle::from_transform(new_tf), // inserts BOTH Transform and GlobalTransform
entity_type,
MeshMaterial3d(debug_material),
EntityDto {
entity_id: db_entity.entity_id,
transform: db_entity.transform.clone(),
},
EntityDto::from(db_entity.clone()),
));
}
}
// --- 3) Despawn any ECS entity that doesn't exist in the DB anymore ---
for (entity, _, dto) in query.iter_mut() {
for (entity,_, _, dto) in query.iter_mut() {
if !db_ids.contains(&dto.entity_id) {
// This ECS entity no longer matches anything in the DB => remove it
commands.entity(entity).despawn_recursive();
}
}
}
fn db_transfrom_to_transfrom(db_transform: DbTransform) -> Transform{
Transform::from_xyz(
db_transform.position.x,
db_transform.position.y,
db_transform.position.z,
).with_rotation(
Quat::from_xyzw(
db_transform.rotation.x,
db_transform.rotation.y,
db_transform.rotation.z,
db_transform.rotation.w
)
).with_scale(
Vec3::new(
db_transform.scale.x,
db_transform.scale.y,
db_transform.scale.z,
)
)
}

View File

@ -1,4 +1,4 @@
use crate::plugins::camera::systems::camera_system::CameraController;
use crate::plugins::environment::systems::camera_system::CameraController;
use bevy::asset::AssetServer;
use bevy::prelude::*;