From 720ffc743a10323d3d0a7f6ed2c7fca1ce4d8160 Mon Sep 17 00:00:00 2001 From: Elias Stepanik Date: Wed, 26 Mar 2025 19:45:13 +0100 Subject: [PATCH] Initial commit --- .gitignore | 36 ++ .idea/runConfigurations/PublishServer.xml | 17 + .idea/runConfigurations/Run_horror_game.xml | 20 + CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 80 ++++ LICENSE-APACHE.txt | 176 +++++++++ LICENSE-MIT.txt | 21 ++ NOTES.md | 0 README.md | 22 ++ client/Cargo.toml | 20 + client/assets/fonts/minecraft_font.ttf | Bin 0 -> 15700 bytes client/src/app.rs | 73 ++++ client/src/helper/debug_gizmos.rs | 18 + client/src/helper/egui_dock.rs | 342 ++++++++++++++++++ client/src/helper/mod.rs | 2 + client/src/main.rs | 84 +++++ client/src/plugins/camera/camera_plugin.rs | 16 + client/src/plugins/camera/mod.rs | 2 + .../plugins/camera/systems/camera_system.rs | 156 ++++++++ client/src/plugins/camera/systems/mod.rs | 1 + .../plugins/environment/environment_plugin.rs | 9 + client/src/plugins/environment/mod.rs | 2 + .../environment/systems/environment_system.rs | 10 + client/src/plugins/environment/systems/mod.rs | 1 + client/src/plugins/mod.rs | 3 + client/src/plugins/ui/mod.rs | 2 + client/src/plugins/ui/systems/mod.rs | 1 + client/src/plugins/ui/systems/ui_system.rs | 62 ++++ client/src/plugins/ui/ui_plugin.rs | 11 + publish_server.bat | 5 + server/Cargo.toml | 13 + server/src/lib.rs | 34 ++ 32 files changed, 1242 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/runConfigurations/PublishServer.xml create mode 100644 .idea/runConfigurations/Run_horror_game.xml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE-APACHE.txt create mode 100644 LICENSE-MIT.txt create mode 100644 NOTES.md create mode 100644 README.md create mode 100644 client/Cargo.toml create mode 100644 client/assets/fonts/minecraft_font.ttf create mode 100644 client/src/app.rs create mode 100644 client/src/helper/debug_gizmos.rs create mode 100644 client/src/helper/egui_dock.rs create mode 100644 client/src/helper/mod.rs create mode 100644 client/src/main.rs create mode 100644 client/src/plugins/camera/camera_plugin.rs create mode 100644 client/src/plugins/camera/mod.rs create mode 100644 client/src/plugins/camera/systems/camera_system.rs create mode 100644 client/src/plugins/camera/systems/mod.rs create mode 100644 client/src/plugins/environment/environment_plugin.rs create mode 100644 client/src/plugins/environment/mod.rs create mode 100644 client/src/plugins/environment/systems/environment_system.rs create mode 100644 client/src/plugins/environment/systems/mod.rs create mode 100644 client/src/plugins/mod.rs create mode 100644 client/src/plugins/ui/mod.rs create mode 100644 client/src/plugins/ui/systems/mod.rs create mode 100644 client/src/plugins/ui/systems/ui_system.rs create mode 100644 client/src/plugins/ui/ui_plugin.rs create mode 100644 publish_server.bat create mode 100644 server/Cargo.toml create mode 100644 server/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..746dd53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# Generated by Cargo +# will have compiled files and executables +client/debug/ +server/debug/ + + +server/target/ +client/target/ + + +server/Cargo.lock +client/Cargo.lock + +# These are backup files generated by rustfmt +client/**/*.rs.bk +server/**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +client/*.pdb +server/*.pdb + + +server/.spacetime + + + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/vcs.xml +.idea/modules.xml +.idea/.gitignore +.idea/horror-game.iml +.idea/workspace.xml \ No newline at end of file diff --git a/.idea/runConfigurations/PublishServer.xml b/.idea/runConfigurations/PublishServer.xml new file mode 100644 index 0000000..ca40c38 --- /dev/null +++ b/.idea/runConfigurations/PublishServer.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_horror_game.xml b/.idea/runConfigurations/Run_horror_game.xml new file mode 100644 index 0000000..dea1c47 --- /dev/null +++ b/.idea/runConfigurations/Run_horror_game.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..278b1f2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +This project adheres to the Rust Code of Conduct, which can be found [here](https://www.rust-lang.org/conduct.html). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2f16e98 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contribution guidelines + +First off, thank you for considering contributing to horror-game. + +If your contribution is not straightforward, please first discuss the change you +wish to make by creating a new issue before making the change. + +## Reporting issues + +Before reporting an issue on the +[issue tracker](https://github.com/eliasstepanik/horror-game/issues), +please check that it has not already been reported by searching for some related +keywords. + +## Pull requests + +Try to do one pull request per change. + +### Updating the changelog + +Update the changes you have made in +[CHANGELOG](https://github.com/eliasstepanik/horror-game/blob/main/CHANGELOG.md) +file under the **Unreleased** section. + +Add the changes of your pull request to one of the following subsections, +depending on the types of changes defined by +[Keep a changelog](https://keepachangelog.com/en/1.0.0/): + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +If the required subsection does not exist yet under **Unreleased**, create it! + +## Developing + +### Set up + +This is no different than other Rust projects. + +```shell +git clone https://github.com/eliasstepanik/horror-game +cd horror-game +cargo test +``` + +### Useful Commands + +- Build and run release version: + + ```shell + cargo build --release && cargo run --release + ``` + +- Run Clippy: + + ```shell + cargo clippy --all-targets --all-features --workspace + ``` + +- Run all tests: + + ```shell + cargo test --all-features --workspace + ``` + +- Check to see if there are code formatting issues + + ```shell + cargo fmt --all -- --check + ``` + +- Format the code in the project + + ```shell + cargo fmt --all + ``` diff --git a/LICENSE-APACHE.txt b/LICENSE-APACHE.txt new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE-APACHE.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt new file mode 100644 index 0000000..4a58307 --- /dev/null +++ b/LICENSE-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Elias Stepanik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0041882 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# horror-game + + + +## License + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000..f899c43 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "horror-game" +version = "0.1.0" +edition = "2021" +description = "Horror-Game" +repository = "https://github.com/eliasstepanik/horror-game" +license = "MIT OR Apache-2.0" + + +[dependencies] +bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory"] } +bevy_egui = "0.31.1" +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" +spacetimedb-sdk = "1.0" +hex = "0.4" \ No newline at end of file diff --git a/client/assets/fonts/minecraft_font.ttf b/client/assets/fonts/minecraft_font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..61b4610bd20cd58a9782bd5e1cc8f217a9858f30 GIT binary patch literal 15700 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 g 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::camera::camera_plugin::CameraPlugin); + app.add_plugins(crate::plugins::ui::ui_plugin::UiPlugin); + + app.add_plugins(crate::plugins::environment::environment_plugin::EnvironmentPlugin); + + 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.register_type::>>(); + app.register_type::(); + } +} + +fn toggle_ui_system( + keyboard_input: Res>, + mut inspector_visible: ResMut, +) { + // ======================= + // 6) Hide Inspector + // ======================= + if keyboard_input.just_pressed(KeyCode::F1) { + inspector_visible.0 = !inspector_visible.0 + } +} + +fn should_display_inspector(inspector_visible: Res) -> bool { + inspector_visible.0 +} + +fn should_not_display_inspector(inspector_visible: Res) -> bool { + !inspector_visible.0 +} diff --git a/client/src/helper/debug_gizmos.rs b/client/src/helper/debug_gizmos.rs new file mode 100644 index 0000000..5df061c --- /dev/null +++ b/client/src/helper/debug_gizmos.rs @@ -0,0 +1,18 @@ +use bevy::color::palettes::css::{BLUE, GREEN, RED}; +use bevy::prelude::*; + +pub fn debug_gizmos(mut gizmos: Gizmos) { + + /* // Draw a line + gizmos.line( + Vec3::ZERO, + Vec3::new(1.0, 1.0, 1.0), + RED, + ); + + // Draw a sphere + gizmos.sphere(Vec3::new(2.0, 2.0, 2.0), 0.5, BLUE); + + // Draw a wireframe cube + gizmos.rect(Isometry3d::IDENTITY, Vec2::ONE, GREEN);*/ +} diff --git a/client/src/helper/egui_dock.rs b/client/src/helper/egui_dock.rs new file mode 100644 index 0000000..57f83f1 --- /dev/null +++ b/client/src/helper/egui_dock.rs @@ -0,0 +1,342 @@ +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>() + .get_single(world) + else { + return; + }; + let mut egui_context = egui_context.clone(); + + world.resource_scope::(|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, + primary_window: Query<&mut Window, With>, + egui_settings: Query<&bevy_egui::EguiSettings>, + mut cameras: Query<&mut Camera, With>, +) { + 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>) { + 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>, mut ui_state: ResMut) { + #[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, + 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::().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>() + .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::(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::(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::().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::()?; + 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); + } + } + }); + } +} diff --git a/client/src/helper/mod.rs b/client/src/helper/mod.rs new file mode 100644 index 0000000..8aa673e --- /dev/null +++ b/client/src/helper/mod.rs @@ -0,0 +1,2 @@ +pub mod debug_gizmos; +pub mod egui_dock; diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100644 index 0000000..2e10c8e --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,84 @@ +mod app; +mod helper; +mod plugins; +use crate::app::AppPlugin; +use bevy::gizmos::{AppGizmoBuilder, GizmoPlugin}; +use bevy::log::info; +use bevy::prelude::{default, App, GizmoConfigGroup, PluginGroup, Reflect, Res, Resource}; +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}; + +const TITLE: &str = "horror-game"; +const RESOLUTION: (f32, f32) = (1920f32, 1080f32); +const RESIZABLE: bool = true; +const DECORATIONS: bool = true; +const TRANSPARENT: bool = true; +const PRESENT_MODE: PresentMode = PresentMode::AutoVsync; + +fn main() { + let mut app = App::new(); + register_platform_plugins(&mut app); + + app.add_plugins(AppPlugin); + app.add_plugins(EguiPlugin); + app.add_plugins(DefaultInspectorConfigPlugin); + /*app.add_plugins(GizmoPlugin);*/ + + app.run(); +} + +#[derive(Resource)] +pub struct InspectorVisible(bool); +fn register_platform_plugins(app: &mut App) { + #[cfg(target_os = "windows")] + { + // Register Windows-specific plugins + info!("Adding Windows-specific plugins"); + app.add_plugins( + DefaultPlugins + .set(RenderPlugin { + render_creation: RenderCreation::Automatic(WgpuSettings { + backends: Some(Backends::VULKAN), + ..default() + }), + ..default() + }) + .set(WindowPlugin { + primary_window: Some(Window { + title: TITLE.to_string(), // Window title + resolution: RESOLUTION.into(), // Initial resolution (width x height) + resizable: RESIZABLE, // Allow resizing + decorations: DECORATIONS, // Enable window decorations + transparent: TRANSPARENT, // Opaque background + present_mode: PRESENT_MODE, // VSync mode + ..default() + }), + ..default() + }), + ); + } + + #[cfg(target_os = "macos")] + { + info!("Adding macOS-specific plugins"); + app.add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: crate::TITLE.to_string(), // Window title + resolution: crate::RESOLUTION.into(), // Initial resolution (width x height) + resizable: crate::RESIZABLE, // Allow resizing + decorations: crate::DECORATIONS, // Enable window decorations + transparent: crate::TRANSPARENT, // Opaque background + present_mode: crate::PRESENT_MODE, // VSync mode + ..default() + }), + ..default() + })); + } +} +fn should_display_inspector(inspector_visible: Res) -> bool { + inspector_visible.0 +} diff --git a/client/src/plugins/camera/camera_plugin.rs b/client/src/plugins/camera/camera_plugin.rs new file mode 100644 index 0000000..ae6d357 --- /dev/null +++ b/client/src/plugins/camera/camera_plugin.rs @@ -0,0 +1,16 @@ +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), + ); + } +} diff --git a/client/src/plugins/camera/mod.rs b/client/src/plugins/camera/mod.rs new file mode 100644 index 0000000..a75e95e --- /dev/null +++ b/client/src/plugins/camera/mod.rs @@ -0,0 +1,2 @@ +pub mod camera_plugin; +pub mod systems; diff --git a/client/src/plugins/camera/systems/camera_system.rs b/client/src/plugins/camera/systems/camera_system.rs new file mode 100644 index 0000000..64c80f0 --- /dev/null +++ b/client/src/plugins/camera/systems/camera_system.rs @@ -0,0 +1,156 @@ +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; + +#[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, + }), + )); +} + +/// Example system to control a camera using double-precision for position. +pub fn camera_controller_system( + time: Res