Fixed Commit History

This commit is contained in:
Elias Stepanik 2025-05-10 21:08:18 +02:00
parent befbfb8935
commit 9074854ce9
23 changed files with 271 additions and 473 deletions

View File

@ -10,13 +10,12 @@ build = "build.rs"
[dependencies]
bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory"] }
bevy_egui = "0.31.1"
bevy_egui = "0.33.0"
bevy_asset = "0.15.0"
bevy-inspector-egui = "0.28.0"
bevy_reflect = "0.15.0"
bevy_render = "0.15.0"
bevy_window = "0.15.0"
egui_dock = "0.14.0"
egui_tiles = "0.12.0"
spacetimedb-sdk = "1.0"
hex = "0.4"
random_word = { version = "0.5.0", features = ["en"] }

View File

@ -1,5 +1,5 @@
[server]
host = "http://100.85.241.101:3000"
host = "http://localhost:3000"
database = "network-game"
[movement]

View File

@ -1,76 +1,16 @@
use crate::helper::debug_gizmos::debug_gizmos;
use crate::helper::egui_dock::{
reset_camera_viewport, set_camera_viewport, set_gizmo_mode, show_ui_system, UiState,
};
use bevy::prelude::*;
use crate::helper::*;
use bevy_egui::EguiSet;
use bevy_render::extract_resource::ExtractResourcePlugin;
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
use crate::plugins::network::systems::database::setup_database;
use crate::module_bindings::DbConnection;
pub struct AppPlugin;
#[derive(Resource, Debug)]
pub struct InspectorVisible(pub bool);
impl Default for InspectorVisible {
fn default() -> Self {
InspectorVisible(false)
}
}
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::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::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(
PostUpdate,
show_ui_system
.before(EguiSet::ProcessOutput)
.before(bevy_egui::systems::end_pass_system)
.before(TransformSystem::TransformPropagate)
.run_if(should_display_inspector),
);
app.add_systems(
PostUpdate,
(
set_camera_viewport
.after(show_ui_system)
.run_if(should_display_inspector),
reset_camera_viewport
.run_if(should_not_display_inspector)
.after(set_camera_viewport),
),
);
app.add_systems(Update, set_gizmo_mode);
app.add_systems(Update, (debug_gizmos));
app.register_type::<Option<Handle<Image>>>();
app.register_type::<AlphaMode>();
}
}
fn toggle_ui_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut inspector_visible: ResMut<InspectorVisible>,
) {
// =======================
// 6) Hide Inspector
// =======================
if keyboard_input.just_pressed(KeyCode::F1) {
inspector_visible.0 = !inspector_visible.0
}
}
fn should_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
inspector_visible.0
}
fn should_not_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
!inspector_visible.0
}

View File

@ -1,342 +0,0 @@
use bevy::prelude::*;
use bevy_asset::{ReflectAsset, UntypedAssetId};
use bevy_egui::{egui, EguiContext};
use bevy_inspector_egui::bevy_inspector::hierarchy::{hierarchy_ui, SelectedEntities};
use bevy_inspector_egui::bevy_inspector::{
self, ui_for_entities_shared_components, ui_for_entity_with_children,
};
use bevy_reflect::TypeRegistry;
use bevy_render::camera::{CameraProjection, Viewport};
use bevy_window::{PrimaryWindow, Window};
use egui_dock::{DockArea, DockState, NodeIndex, Style};
use std::any::TypeId;
#[cfg(egui_dock_gizmo)]
use transform_gizmo_egui::GizmoMode;
/// Placeholder type if gizmo is disabled.
#[cfg(not(egui_dock_gizmo))]
#[derive(Clone, Copy)]
pub struct GizmoMode;
#[derive(Component)]
pub struct MainCamera;
pub fn show_ui_system(world: &mut World) {
let Ok(egui_context) = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world)
else {
return;
};
let mut egui_context = egui_context.clone();
world.resource_scope::<UiState, _>(|world, mut ui_state| {
ui_state.ui(world, egui_context.get_mut())
});
}
// make camera only render to view not obpub structed by UI
pub fn set_camera_viewport(
ui_state: Res<UiState>,
primary_window: Query<&mut Window, With<PrimaryWindow>>,
egui_settings: Query<&bevy_egui::EguiSettings>,
mut cameras: Query<&mut Camera, With<MainCamera>>,
) {
let mut cam = cameras.single_mut();
let Ok(window) = primary_window.get_single() else {
return;
};
let scale_factor = window.scale_factor() * egui_settings.single().scale_factor;
let viewport_pos = ui_state.viewport_rect.left_top().to_vec2() * scale_factor;
let viewport_size = ui_state.viewport_rect.size() * scale_factor;
let physical_position = UVec2::new(viewport_pos.x as u32, viewport_pos.y as u32);
let physical_size = UVec2::new(viewport_size.x as u32, viewport_size.y as u32);
// The desired viewport rectangle at its offset in "physical pixel space"
let rect = physical_position + physical_size;
let window_size = window.physical_size();
// wgpu will panic if trying to set a viewport rect which has coordinates extending
// past the size of the render target, i.e. the physical window in our case.
// Typically this shouldn't happen- but during init and resizing etc. edge cases might occur.
// Simply do nothing in those cases.
if rect.x <= window_size.x && rect.y <= window_size.y {
cam.viewport = Some(Viewport {
physical_position,
physical_size,
depth: 0.0..1.0,
});
}
}
pub fn reset_camera_viewport(mut cameras: Query<&mut Camera, With<MainCamera>>) {
if let Ok(mut cam) = cameras.get_single_mut() {
cam.viewport = None; // Reset the viewport to its default state
}
}
pub fn set_gizmo_mode(input: Res<ButtonInput<KeyCode>>, mut ui_state: ResMut<UiState>) {
#[cfg(egui_dock_gizmo)]
let keybinds = [
(KeyCode::KeyR, GizmoMode::Rotate),
(KeyCode::KeyT, GizmoMode::Translate),
(KeyCode::KeyS, GizmoMode::Scale),
];
#[cfg(not(egui_dock_gizmo))]
let keybinds = [];
for (key, mode) in keybinds {
if input.just_pressed(key) {
ui_state.gizmo_mode = mode;
}
}
}
#[derive(Eq, PartialEq)]
pub enum InspectorSelection {
Entities,
Resource(TypeId, String),
Asset(TypeId, String, UntypedAssetId),
}
#[derive(Resource)]
pub struct UiState {
state: DockState<EguiWindow>,
viewport_rect: egui::Rect,
selected_entities: SelectedEntities,
selection: InspectorSelection,
gizmo_mode: GizmoMode,
}
impl UiState {
pub fn new() -> Self {
let mut state = DockState::new(vec![EguiWindow::GameView]);
let tree = state.main_surface_mut();
let [game, _inspector] =
tree.split_right(NodeIndex::root(), 0.75, vec![EguiWindow::Inspector]);
let [game, _hierarchy] = tree.split_left(game, 0.2, vec![EguiWindow::Hierarchy]);
let [_game, _bottom] =
tree.split_below(game, 0.8, vec![EguiWindow::Resources, EguiWindow::Assets]);
Self {
state,
selected_entities: SelectedEntities::default(),
selection: InspectorSelection::Entities,
viewport_rect: egui::Rect::NOTHING,
#[cfg(egui_dock_gizmo)]
gizmo_mode: GizmoMode::Translate,
#[cfg(not(egui_dock_gizmo))]
gizmo_mode: GizmoMode,
}
}
pub fn ui(&mut self, world: &mut World, ctx: &mut egui::Context) {
let mut tab_viewer = TabViewer {
world,
viewport_rect: &mut self.viewport_rect,
selected_entities: &mut self.selected_entities,
selection: &mut self.selection,
gizmo_mode: self.gizmo_mode,
};
DockArea::new(&mut self.state)
.style(Style::from_egui(ctx.style().as_ref()))
.show(ctx, &mut tab_viewer);
}
}
#[derive(Debug)]
pub enum EguiWindow {
GameView,
Hierarchy,
Resources,
Assets,
Inspector,
}
pub struct TabViewer<'a> {
world: &'a mut World,
selected_entities: &'a mut SelectedEntities,
selection: &'a mut InspectorSelection,
viewport_rect: &'a mut egui::Rect,
gizmo_mode: GizmoMode,
}
impl egui_dock::TabViewer for TabViewer<'_> {
type Tab = EguiWindow;
fn ui(&mut self, ui: &mut egui_dock::egui::Ui, window: &mut Self::Tab) {
let type_registry = self.world.resource::<AppTypeRegistry>().0.clone();
let type_registry = type_registry.read();
match window {
EguiWindow::GameView => {
*self.viewport_rect = ui.clip_rect();
draw_gizmo(ui, self.world, self.selected_entities, self.gizmo_mode);
}
EguiWindow::Hierarchy => {
let selected = hierarchy_ui(self.world, ui, self.selected_entities);
if selected {
*self.selection = InspectorSelection::Entities;
}
}
EguiWindow::Resources => select_resource(ui, &type_registry, self.selection),
EguiWindow::Assets => select_asset(ui, &type_registry, self.world, self.selection),
EguiWindow::Inspector => match *self.selection {
InspectorSelection::Entities => match self.selected_entities.as_slice() {
&[entity] => ui_for_entity_with_children(self.world, entity, ui),
entities => ui_for_entities_shared_components(self.world, entities, ui),
},
InspectorSelection::Resource(type_id, ref name) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_resource(
self.world,
type_id,
ui,
name,
&type_registry,
)
}
InspectorSelection::Asset(type_id, ref name, handle) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_asset(
self.world,
type_id,
handle,
ui,
&type_registry,
);
}
},
}
}
fn title(&mut self, window: &mut Self::Tab) -> egui_dock::egui::WidgetText {
format!("{window:?}").into()
}
fn clear_background(&self, window: &Self::Tab) -> bool {
!matches!(window, EguiWindow::GameView)
}
}
#[allow(unused)]
pub fn draw_gizmo(
ui: &mut egui::Ui,
world: &mut World,
selected_entities: &SelectedEntities,
gizmo_mode: GizmoMode,
) {
let (cam_transform, projection) = world
.query_filtered::<(&GlobalTransform, &Projection), With<MainCamera>>()
.single(world);
let view_matrix = Mat4::from(cam_transform.affine().inverse());
let projection_matrix = projection.get_clip_from_view();
if selected_entities.len() != 1 {
#[allow(clippy::needless_return)]
return;
}
/*for selected in selected_entities.iter() {
let Some(transform) = world.get::<Transform>(selected) else {
continue;
};
let model_matrix = transform.compute_matrix();
let mut gizmo = Gizmo::new(GizmoConfig {
view_matrix: view_matrix.into(),
projection_matrix: projection_matrix.into(),
orientation: GizmoOrientation::Local,
modes: EnumSet::from(gizmo_mode),
..Default::default()
});
let Some([result]) = gizmo
.interact(ui, model_matrix.into())
.map(|(_, res)| res.as_slice())
else {
continue;
};
let mut transform = world.get_mut::<Transform>(selected).unwrap();
transform = Transform {
translation: Vec3::from(<[f64; 3]>::from(result.translation)),
rotation: Quat::from_array(<[f64; 4]>::from(result.rotation)),
scale: Vec3::from(<[f64; 3]>::from(result.scale)),
};
}*/
}
pub fn select_resource(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
selection: &mut InspectorSelection,
) {
let mut resources: Vec<_> = type_registry
.iter()
.filter(|registration| registration.data::<ReflectResource>().is_some())
.map(|registration| {
(
registration.type_info().type_path_table().short_path(),
registration.type_id(),
)
})
.collect();
resources.sort_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
for (resource_name, type_id) in resources {
let selected = match *selection {
InspectorSelection::Resource(selected, _) => selected == type_id,
_ => false,
};
if ui.selectable_label(selected, resource_name).clicked() {
*selection = InspectorSelection::Resource(type_id, resource_name.to_string());
}
debug!("{}", resource_name);
}
}
pub fn select_asset(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
world: &World,
selection: &mut InspectorSelection,
) {
let mut assets: Vec<_> = type_registry
.iter()
.filter_map(|registration| {
let reflect_asset = registration.data::<ReflectAsset>()?;
Some((
registration.type_info().type_path_table().short_path(),
registration.type_id(),
reflect_asset,
))
})
.collect();
assets.sort_by(|(name_a, ..), (name_b, ..)| name_a.cmp(name_b));
for (asset_name, asset_type_id, reflect_asset) in assets {
let handles: Vec<_> = reflect_asset.ids(world).collect();
ui.collapsing(format!("{asset_name} ({})", handles.len()), |ui| {
for handle in handles {
let selected = match *selection {
InspectorSelection::Asset(_, _, selected_id) => selected_id == handle,
_ => false,
};
if ui
.selectable_label(selected, format!("{:?}", handle))
.clicked()
{
*selection =
InspectorSelection::Asset(asset_type_id, asset_name.to_string(), handle);
}
}
});
}
}

View File

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

View File

@ -13,7 +13,6 @@ use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
use bevy::render::RenderPlugin;
use bevy::DefaultPlugins;
use bevy_egui::EguiPlugin;
use bevy_inspector_egui::DefaultInspectorConfigPlugin;
use bevy_window::{PresentMode, Window, WindowPlugin};
use toml;
use crate::config::Config;
@ -42,7 +41,6 @@ fn main() {
app.add_plugins(AppPlugin);
app.add_plugins(EguiPlugin);
app.add_plugins(DefaultInspectorConfigPlugin);

View File

@ -5,7 +5,9 @@ impl Plugin for EnvironmentPlugin {
app.add_systems(
Startup,
(crate::plugins::environment::systems::camera_system::setup),
(crate::plugins::environment::systems::environment_system::setup, crate::plugins::environment::systems::camera_system::setup ),
);
}
}

View File

@ -1,4 +1,4 @@
use crate::helper::egui_dock::MainCamera;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::Vec3;
use bevy::prelude::*;
@ -39,7 +39,6 @@ pub fn setup(mut commands: Commands,) {
near: 0.0001,
..default()
}),
MainCamera,
CameraController::default(),
Exposure::from_physical_camera(PhysicalCameraParameters {
aperture_f_stops: 1.0,

View File

@ -2,10 +2,31 @@
use bevy::prelude::*;
pub fn setup(mut commands: Commands) {
pub(crate) fn setup(
mut commands : Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 2) directional light
commands.spawn(DirectionalLightBundle {
transform: Transform::from_rotation(Quat::from_euler(
EulerRot::XYZ,
-std::f32::consts::FRAC_PI_4,
std::f32::consts::FRAC_PI_4,
0.0,
)),
directional_light: DirectionalLight {
shadows_enabled: true,
..Default::default()
},
..Default::default()
});
}
pub fn update(time: Res<Time>,) {
}

View File

@ -1,6 +1,7 @@
use bevy::app::{App, Plugin, PreUpdate, Startup};
use bevy::prelude::{IntoSystemConfigs, Update};
use crate::plugins::input::systems::console::{console_system, toggle_console, ConsoleState};
pub struct InputPlugin;
impl Plugin for InputPlugin {
@ -11,10 +12,14 @@ impl Plugin for InputPlugin {
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::network::network_system,
crate::plugins::input::systems::movement::movement_system,
),
);
_app.insert_resource(ConsoleState::default());
_app.add_systems(Update, (toggle_console, console_system));
}
}

View File

@ -1,23 +1,63 @@
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::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Resource, Time, Transform};
use bevy_egui::{egui, EguiContexts};
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>,
mut ctxs: EguiContexts,
mut state: ResMut<ConsoleState>,
) {
let mut window = windows.single_mut();
let (mut transform, mut controller) = query.single_mut();
if !state.open { return; }
egui::Window::new("Console")
.resizable(true)
.vscroll(true)
.show(ctxs.ctx_mut(), |ui| {
// Output
for line in &state.output {
ui.label(line);
}
// Input line
let resp = ui.text_edit_singleline(&mut state.input);
if resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
let cmd = state.input.trim().to_string();
if !cmd.is_empty() {
state.history.push(cmd.clone());
handle_command(&cmd, &mut state.output);
state.input.clear();
}
}
});
}
/// Press ` to open / close
pub fn toggle_console(
mut state: ResMut<ConsoleState>,
keys: Res<ButtonInput<KeyCode>>,
) {
if keys.just_pressed(KeyCode::KeyC) {
state.open = !state.open;
}
}
/// Add your own commands here.
/// For demo purposes we just echo the input.
fn handle_command(cmd: &str, out: &mut Vec<String>) {
match cmd.trim() {
"help" => out.push("Available: help, clear, echo …".into()),
"clear" => out.clear(),
_ => out.push(format!("> {cmd}")),
}
}
#[derive(Resource, Default)]
pub struct ConsoleState {
pub open: bool,
pub input: String,
pub history: Vec<String>,
pub output: Vec<String>,
}

View File

@ -5,7 +5,8 @@ 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 spacetimedb_sdk::DbContext;
use crate::module_bindings::{set_name, set_position, spawn_entity, DbTransform, DbVector3, DbVector4, PlayerTableAccess};
use crate::plugins::environment::systems::camera_system::CameraController;
use crate::plugins::network::systems::database::DbConnectionResource;
@ -19,8 +20,9 @@ pub fn flight_systems(
mut windows: Query<&mut Window>,
mut query: Query<(&mut Transform, &mut CameraController)>,
mut app_exit_events: EventWriter<AppExit>,
mut ctx: ResMut<DbConnectionResource>,
//mut ctx: ResMut<DbConnectionResource>,
) {
let mut window = windows.single_mut();
let (mut transform, mut controller) = query.single_mut();
@ -98,11 +100,12 @@ pub fn flight_systems(
let distance = controller.speed as f64 * delta_seconds;
transform.translation += direction * distance as f32;
ctx.0.reducers.set_position(DbVector3{
/*ctx.0.reducers.set_position(DbVector3{
x: transform.translation.x,
y: transform.translation.y,
z: transform.translation.z,
}).expect("TODO: panic message");
*/

View File

@ -1,22 +1,5 @@
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

@ -3,16 +3,18 @@ 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::module_bindings::{set_name, spawn_entity, spawn_rigidbody_entity, DbTransform, DbVector3, DbVector4, EntityType};
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) {
let word = random_word::get(Lang::En);
ctx.0.reducers.set_name(word.to_string()).unwrap();
}
if keyboard_input.just_pressed(KeyCode::KeyE) {
@ -20,7 +22,7 @@ pub fn network_system(
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{
ctx.0.reducers.spawn_rigidbody_entity(DbTransform{
position: DbVector3{
x: rand_position.x,
y: rand_position.y,
@ -40,7 +42,9 @@ pub fn network_system(
y: rand_scale.x,
z: rand_scale.x,
},
}).unwrap();
},
EntityType::Cube,
DbVector3{ x:0.0, y:0.0, z:0.0}, 5.0, false).unwrap();
}
}

View File

@ -3,4 +3,3 @@ pub mod environment;
pub mod ui;
pub mod network;
pub mod input;

View File

@ -10,11 +10,6 @@ use crate::plugins::network::systems::callbacks::*;
use crate::plugins::network::systems::connection::*;
use crate::plugins::network::systems::subscriptions::*;
/// The URI of the SpacetimeDB instance hosting our chat module.
const HOST: &str = "http://100.85.241.101:3000";
/// The database name we chose when we published our module.
const DB_NAME: &str = "network-game";
#[derive(Resource)]
pub struct DbConnectionResource(pub(crate) DbConnection);
@ -65,5 +60,5 @@ fn subscribe_to_tables(ctx: &DbConnection) {
ctx.subscription_builder()
.on_applied(on_sub_applied)
.on_error(on_sub_error)
.subscribe(["SELECT * FROM player", "SELECT * FROM entity"]);
.subscribe(["SELECT * FROM player", "SELECT * FROM entity", "SELECT * FROM rigidbody"]);
}

View File

@ -6,9 +6,9 @@ use bevy::prelude::{default, info, Bundle, Commands, Component, Cuboid, DespawnR
use bevy_asset::Assets;
use bevy_reflect::Reflect;
use bevy_render::mesh::Mesh3d;
use spacetimedb_sdk::Table;
use spacetimedb_sdk::{DbContext, Table};
use crate::helper::math::RoundTo;
use crate::module_bindings::{DbTransform, DbVector3, EntityTableAccess, EntityType};
use crate::module_bindings::{DbTransform, DbVector3, EntityTableAccess, EntityType, PlayerTableAccess};
use crate::plugins::network::systems::database::DbConnectionResource;
#[derive(Component)]
@ -44,6 +44,10 @@ pub fn sync_entities_system(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let identity = db_resource.0.identity();
let player = db_resource.0.db.player().identity().find(&identity);
// --- 1) Collect DB entities and build a set of IDs ---
let db_entities = db_resource.0.db.entity();
let db_ids: HashSet<u32> = db_entities.iter().map(|e| e.entity_id).collect();
@ -51,6 +55,10 @@ pub fn sync_entities_system(
// --- 2) For each DB entity, update or spawn in ECS ---
for db_entity in db_entities.iter() {
if db_entity.entity_id == player.clone().unwrap().entity_id {
return;
}
// Try to find a matching ECS entity by entity_id
if let Some((_, mut transform, mut global, mut dto)) =
query.iter_mut().find(|(_, _, _, dto)| dto.entity_id == db_entity.entity_id)
@ -81,6 +89,9 @@ pub fn sync_entities_system(
EntityType::Custom => todo!(),
};
let new_tf = Transform::from(db_entity.transform.clone());
commands.spawn((

View File

@ -1,8 +1,10 @@
mod types;
use spacetimedb::{reducer, ReducerContext, Table};
use std::time::Duration;
use spacetimedb::{reducer, ReducerContext, ScheduleAt, Table, TimeDuration, Timestamp};
use crate::types::entity::{entity, entity__TableHandle, Entity, EntityType};
use crate::types::player::{player, player__TableHandle, Player};
use crate::types::rigidbody::physics_step;
use crate::types::types::{DBVector4, DbTransform, DbVector3};
#[spacetimedb::table(name = config, public)]
@ -78,5 +80,24 @@ pub fn init(ctx: &ReducerContext) -> Result<(), String> {
ctx.db.config().try_insert(Config {
id: 0,
})?;
ctx.db.physics_timer().try_insert(PhysicsTimer {
scheduled_id: 0,
scheduled_at: ScheduleAt::Interval(Duration::from_millis(50).into()),
last_update_ts: ctx.timestamp,
})?;
Ok(())
}
#[spacetimedb::table(name = physics_timer, scheduled(physics_step))]
struct PhysicsTimer {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: ScheduleAt,
last_update_ts: Timestamp,
}

View File

@ -1,3 +1,4 @@
pub mod types;
pub mod player;
pub mod entity;
pub mod rigidbody;

View File

@ -0,0 +1,103 @@
use std::time::Duration;
use spacetimedb::{ReducerContext, Table, TimeDuration, Timestamp};
use crate::{physics_timer, PhysicsTimer};
use crate::types::entity::{entity, Entity, EntityType};
use crate::types::types::{DbTransform, DbVector3};
#[spacetimedb::table(name = rigidbody, public)]
#[derive(Debug, Clone, )]
pub struct Rigidbody {
#[auto_inc]
#[primary_key]
pub rigidbody_id: u32,
#[index(btree)]
pub entity_id: u32,
pub velocity: DbVector3,
pub force: DbVector3,
pub mass: f32,
pub is_fixed: bool,
}
#[spacetimedb::reducer]
pub fn spawn_rigidbody_entity(
ctx: &ReducerContext,
transform: DbTransform,
entity_type: EntityType,
velocity: DbVector3,
mass: f32,
is_fixed: bool,
) -> Result<(), String> {
// 1. insert a new Entity row
let inserted_entity: Entity = ctx
.db
.entity()
.insert(Entity {
entity_id: 0,
transform,
entity_type,
});
// 2. insert its corresponding Rigidbody row
ctx.db
.rigidbody()
.insert(Rigidbody {
rigidbody_id: 0,
entity_id: inserted_entity.entity_id,
velocity,
force: DbVector3::zero(),
mass,
is_fixed,
});
Ok(())
}
#[spacetimedb::reducer]
pub fn physics_step(ctx: &ReducerContext, mut timer: PhysicsTimer) -> Result<(), String> {
let now = ctx.timestamp;
let delta = now
.time_duration_since(timer.last_update_ts)
.unwrap_or(TimeDuration::from(Duration::from_millis(50)));
let dt = delta.to_duration().unwrap().as_secs_f32();
// update timer state
timer.last_update_ts = now;
ctx.db.physics_timer().scheduled_id().update(timer);
// constant gravity
let gravity = DbVector3::new(0.0, -9.81, 0.0);
// process all rigidbodies
let bodies: Vec<Rigidbody> = ctx.db.rigidbody().iter().collect();
for mut rb in bodies {
if rb.is_fixed {
continue;
}
// apply gravity to force
rb.force.add(&gravity.mul_scalar(rb.mass));
// integrate velocity
let inv_mass = 1.0 / rb.mass;
let accel = rb.force.mul_scalar(inv_mass);
rb.velocity.add(&accel.mul_scalar(dt));
// update corresponding entity position
if let Some(mut ent) = ctx.db.entity().iter().find(|e| e.entity_id == rb.entity_id){
ent.transform.position.add(&rb.velocity.mul_scalar(dt));
ctx.db.entity().entity_id().update(ent);
}
// reset force and write back
rb.force = DbVector3::zero();
ctx.db.rigidbody().rigidbody_id().update(rb);
}
Ok(())
}

View File

@ -7,7 +7,22 @@ pub struct DbVector3 {
pub z: f32,
}
impl DbVector3 {
pub(crate) fn new(x: f32, y: f32, z: f32) -> DbVector3 {
DbVector3 { x, y, z }
}
pub(crate) fn zero() -> DbVector3 {
DbVector3::new(0.0, 0.0, 0.0)
}
pub(crate) fn add(&mut self, other: &DbVector3) {
self.x += other.x;
self.y += other.y;
self.z += other.z;
}
pub(crate) fn mul_scalar(&self, s: f32) -> DbVector3 {
DbVector3::new(self.x * s, self.y * s, self.z * s)
}
}
#[derive(SpacetimeType, Clone, Debug)]
pub struct DBVector4 {
pub x: f32,

2
start_server.sh Normal file
View File

@ -0,0 +1,2 @@
spacetime start