create scaffolding
This commit is contained in:
commit
82a0fbdb50
25 changed files with 6993 additions and 0 deletions
3
.cargo/config.toml
Normal file
3
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
6148
Cargo.lock
generated
Normal file
6148
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
43
Cargo.toml
Normal file
43
Cargo.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[package]
|
||||
name = "coreheart"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.lints]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
# increases incremental compilation speed by about 10% while keeping debug info
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
bevy = { version = "0.16.1", features = ["serialize"] }
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
itertools = "0.13.0"
|
||||
avian3d = "0.3.1"
|
||||
bevy-inspector-egui = "0.33.1"
|
||||
ron = "0.10.1"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
bevy-inspector-egui = { workspace = true }
|
||||
|
||||
#internal crates
|
||||
state = { path = "crates/state" }
|
||||
physics = { path = "crates/physics" }
|
||||
|
||||
ui_state = { path = "crates/ui_state" }
|
||||
ui_debug = { path = "crates/ui_debug" }
|
||||
input = { path = "crates/input" }
|
||||
ui = { path = "crates/ui" }
|
||||
setup = { path = "crates/setup" }
|
||||
ui_pause = { path = "crates/ui_pause" }
|
||||
#new internal crates go here
|
||||
6
assets/settings/input_map.ron
Normal file
6
assets/settings/input_map.ron
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(
|
||||
map: {
|
||||
KeyCode(F1): ToggleDebugUI,
|
||||
KeyCode(KeyA): TogglePause,
|
||||
},
|
||||
)
|
||||
13
crates/input/Cargo.toml
Normal file
13
crates/input/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "input"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
ron = { workspace = true }
|
||||
state = { path = "../state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
27
crates/input/src/actions.rs
Normal file
27
crates/input/src/actions.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use bevy::{platform::collections::HashMap, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{InputMap, InputMethod};
|
||||
|
||||
#[derive(Hash, Clone, Copy, Debug, Reflect, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InputAction {
|
||||
ToggleDebugUI,
|
||||
TogglePause,
|
||||
}
|
||||
|
||||
impl Default for InputMap {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: HashMap::from_iter(vec![
|
||||
(
|
||||
InputMethod::KeyCode(KeyCode::F1),
|
||||
InputAction::ToggleDebugUI,
|
||||
),
|
||||
(
|
||||
InputMethod::KeyCode(KeyCode::Escape),
|
||||
InputAction::TogglePause,
|
||||
),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
27
crates/input/src/conditions.rs
Normal file
27
crates/input/src/conditions.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{Inputs, actions::InputAction};
|
||||
|
||||
pub fn action_pressed<T: Send + Sync + 'static>(
|
||||
action: InputAction,
|
||||
) -> impl FnMut(Res<Inputs<T>>) -> bool + Clone {
|
||||
move |inputs: Res<Inputs<T>>| inputs.pressed(action)
|
||||
}
|
||||
|
||||
pub fn action_just_pressed<T: Send + Sync + 'static>(
|
||||
action: InputAction,
|
||||
) -> impl FnMut(Res<Inputs<T>>) -> bool + Clone {
|
||||
move |inputs: Res<Inputs<T>>| inputs.just_pressed(action)
|
||||
}
|
||||
|
||||
pub fn action_released<T: Send + Sync + 'static>(
|
||||
action: InputAction,
|
||||
) -> impl FnMut(Res<Inputs<T>>) -> bool + Clone {
|
||||
move |inputs: Res<Inputs<T>>| inputs.released(action)
|
||||
}
|
||||
|
||||
pub fn action_just_released<T: Send + Sync + 'static>(
|
||||
action: InputAction,
|
||||
) -> impl FnMut(Res<Inputs<T>>) -> bool + Clone {
|
||||
move |inputs: Res<Inputs<T>>| inputs.just_released(action)
|
||||
}
|
||||
212
crates/input/src/lib.rs
Normal file
212
crates/input/src/lib.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub mod actions;
|
||||
pub mod conditions;
|
||||
|
||||
use std::{fs, marker::PhantomData};
|
||||
|
||||
use bevy::{input::mouse::AccumulatedMouseMotion, platform::collections::HashMap, prelude::*};
|
||||
use ron::ser::PrettyConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use state::GameSystemSet;
|
||||
|
||||
use crate::actions::InputAction;
|
||||
|
||||
pub struct InputPlugin;
|
||||
|
||||
impl Plugin for InputPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<Inputs<FixedUpdate>>()
|
||||
.init_resource::<Inputs<Update>>()
|
||||
.configure_sets(Update, GameSystemSet::UpdateInputs)
|
||||
.add_systems(Startup, setup_input_map)
|
||||
.add_systems(
|
||||
Update,
|
||||
(update_inputs_frame, save_input_map).in_set(GameSystemSet::UpdateInputs),
|
||||
)
|
||||
.add_systems(FixedPostUpdate, clear_changed_fixed);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, Copy, Debug, Reflect, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InputMethod {
|
||||
MouseButton(MouseButton),
|
||||
KeyCode(KeyCode),
|
||||
MouseMovementX,
|
||||
MouseMovementY,
|
||||
}
|
||||
|
||||
#[derive(Resource, Serialize, Deserialize)]
|
||||
pub struct InputMap {
|
||||
pub map: HashMap<InputMethod, InputAction>,
|
||||
}
|
||||
|
||||
impl InputMap {
|
||||
fn save_to_file(&self, path: &str) -> Result<()> {
|
||||
let json = ron::ser::to_string_pretty(self, PrettyConfig::default())?;
|
||||
fs::write(path, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_from_file(path: &str) -> Result<Self> {
|
||||
let content = fs::read_to_string(path)?;
|
||||
let input_map = ron::from_str(&content)?;
|
||||
Ok(input_map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct InputState {
|
||||
changed: bool,
|
||||
value: f32,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
pub fn pressed(&self) -> bool {
|
||||
self.value > 0.
|
||||
}
|
||||
pub fn just_pressed(&self) -> bool {
|
||||
self.pressed() && self.changed
|
||||
}
|
||||
pub fn released(&self) -> bool {
|
||||
!self.pressed()
|
||||
}
|
||||
pub fn just_released(&self) -> bool {
|
||||
self.released() && self.changed
|
||||
}
|
||||
pub fn get(&self) -> f32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
/// updates before pretick
|
||||
pub struct Inputs<T> {
|
||||
schedule: PhantomData<T>,
|
||||
map: HashMap<InputAction, InputState>,
|
||||
}
|
||||
|
||||
impl<T> Inputs<T> {
|
||||
pub fn pressed(&self, action: InputAction) -> bool {
|
||||
self.map.get(&action).map_or(false, |state| state.pressed())
|
||||
}
|
||||
|
||||
pub fn just_pressed(&self, action: InputAction) -> bool {
|
||||
self.map
|
||||
.get(&action)
|
||||
.map_or(false, |state| state.just_pressed())
|
||||
}
|
||||
|
||||
pub fn released(&self, action: InputAction) -> bool {
|
||||
self.map
|
||||
.get(&action)
|
||||
.map_or(false, |state| state.released())
|
||||
}
|
||||
|
||||
pub fn just_released(&self, action: InputAction) -> bool {
|
||||
self.map
|
||||
.get(&action)
|
||||
.map_or(false, |state| state.just_released())
|
||||
}
|
||||
|
||||
pub fn get(&self, action: InputAction) -> f32 {
|
||||
self.map.get(&action).map_or(0.0, |state| state.get())
|
||||
}
|
||||
}
|
||||
|
||||
fn update_inputs_frame(
|
||||
mut fixed_inputs: ResMut<Inputs<FixedUpdate>>,
|
||||
mut frame_inputs: ResMut<Inputs<Update>>,
|
||||
input_map: Res<InputMap>,
|
||||
mouse_movement: Res<AccumulatedMouseMotion>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
fn set_value(action: &InputAction, value: f32, map: &mut HashMap<InputAction, InputState>) {
|
||||
let mut current_state = map.get(action).copied().unwrap_or_default();
|
||||
if current_state.value != value {
|
||||
current_state.changed = true;
|
||||
}
|
||||
current_state.value = value;
|
||||
map.insert(*action, current_state);
|
||||
}
|
||||
fn add_value(action: &InputAction, value: f32, map: &mut HashMap<InputAction, InputState>) {
|
||||
let mut current_state = map.get(action).copied().unwrap_or_default();
|
||||
if value != 0. {
|
||||
current_state.changed = true;
|
||||
}
|
||||
current_state.value += value;
|
||||
map.insert(*action, current_state);
|
||||
}
|
||||
for input in frame_inputs.map.values_mut() {
|
||||
input.changed = false;
|
||||
}
|
||||
for (method, action) in input_map.map.iter() {
|
||||
match method {
|
||||
InputMethod::MouseButton(mouse_button) => {
|
||||
let value = if mouse_buttons.pressed(*mouse_button) {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
set_value(action, value, &mut frame_inputs.map);
|
||||
set_value(action, value, &mut fixed_inputs.map);
|
||||
}
|
||||
InputMethod::KeyCode(key_code) => {
|
||||
let value = if keyboard.pressed(*key_code) {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
set_value(action, value, &mut frame_inputs.map);
|
||||
set_value(action, value, &mut fixed_inputs.map);
|
||||
}
|
||||
InputMethod::MouseMovementX => {
|
||||
let value = mouse_movement.delta.x;
|
||||
set_value(action, value, &mut frame_inputs.map);
|
||||
// want to get accumulated mouse movement in here if needed
|
||||
add_value(action, value, &mut fixed_inputs.map);
|
||||
}
|
||||
InputMethod::MouseMovementY => {
|
||||
let value = mouse_movement.delta.y;
|
||||
set_value(action, value, &mut frame_inputs.map);
|
||||
// want to get accumulated mouse movement in here if needed
|
||||
add_value(action, value, &mut fixed_inputs.map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_changed_fixed(mut fixed_inputs: ResMut<Inputs<FixedUpdate>>) {
|
||||
for input in fixed_inputs.map.values_mut() {
|
||||
input.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
const INPUT_MAP_PATH: &'static str = "assets/settings/input_map.ron";
|
||||
|
||||
fn save_input_map(input_map: Res<InputMap>) {
|
||||
if input_map.is_changed() {
|
||||
if let Err(e) = input_map.save_to_file(INPUT_MAP_PATH) {
|
||||
error!("Failed to save input map: {}", e);
|
||||
} else {
|
||||
info!("Input map saved successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_input_map(mut commands: Commands) {
|
||||
let input_map = match InputMap::load_from_file(INPUT_MAP_PATH) {
|
||||
Ok(map) => {
|
||||
info!("Loaded input map");
|
||||
map
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to load input map: {}", e);
|
||||
default()
|
||||
}
|
||||
};
|
||||
commands.insert_resource(input_map);
|
||||
}
|
||||
13
crates/physics/Cargo.toml
Normal file
13
crates/physics/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "physics"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
avian3d = { workspace = true }
|
||||
|
||||
state = { path = "../state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
55
crates/physics/src/lib.rs
Normal file
55
crates/physics/src/lib.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use state::AppState;
|
||||
|
||||
pub struct PhysicsPlugin;
|
||||
|
||||
impl Plugin for PhysicsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(PhysicsPlugins::default())
|
||||
.add_systems(OnEnter(AppState::Game), setup);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// Static physics object with a collision shape
|
||||
commands.spawn((
|
||||
RigidBody::Static,
|
||||
Collider::cylinder(4.0, 0.1),
|
||||
Mesh3d(meshes.add(Cylinder::new(4.0, 0.1))),
|
||||
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||
));
|
||||
|
||||
// Dynamic physics object with a collision shape and initial angular velocity
|
||||
commands.spawn((
|
||||
RigidBody::Dynamic,
|
||||
Collider::cuboid(1.0, 1.0, 1.0),
|
||||
AngularVelocity(Vec3::new(2.5, 3.5, 1.5)),
|
||||
Mesh3d(meshes.add(Cuboid::from_length(1.0))),
|
||||
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||
Transform::from_xyz(0.0, 4.0, 0.0),
|
||||
));
|
||||
|
||||
// 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, Dir3::Y),
|
||||
));
|
||||
}
|
||||
12
crates/setup/Cargo.toml
Normal file
12
crates/setup/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "setup"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
|
||||
state = { path = "../state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
49
crates/setup/src/lib.rs
Normal file
49
crates/setup/src/lib.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use state::AppState;
|
||||
|
||||
pub struct SetupPlugin;
|
||||
|
||||
impl Plugin for SetupPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_sub_state::<LoadState>()
|
||||
.add_sub_state::<ExampleLoadSubState>()
|
||||
.add_systems(OnEnter(LoadState::NotStarted), start_loading)
|
||||
.add_systems(
|
||||
Update,
|
||||
finish_loading.run_if(
|
||||
in_state(LoadState::Loading).and(in_state(ExampleLoadSubState::Loaded)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, SubStates, Default)]
|
||||
#[source(AppState = AppState::Setup)]
|
||||
enum LoadState {
|
||||
#[default]
|
||||
NotStarted,
|
||||
Loading,
|
||||
}
|
||||
|
||||
fn start_loading(mut next_state: ResMut<NextState<LoadState>>) {
|
||||
info!("started loading...");
|
||||
next_state.set(LoadState::Loading);
|
||||
}
|
||||
|
||||
fn finish_loading(mut next_state: ResMut<NextState<AppState>>) {
|
||||
info!("finished loading");
|
||||
next_state.set(AppState::Game);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, SubStates, Default)]
|
||||
#[source(LoadState = LoadState::Loading)]
|
||||
#[allow(dead_code)]
|
||||
enum ExampleLoadSubState {
|
||||
Loading,
|
||||
#[default]
|
||||
Loaded,
|
||||
}
|
||||
11
crates/state/Cargo.toml
Normal file
11
crates/state/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "state"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
avian3d = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
88
crates/state/src/lib.rs
Normal file
88
crates/state/src/lib.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct StatePlugin;
|
||||
|
||||
impl Plugin for StatePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_state::<AppState>()
|
||||
.enable_state_scoped_entities::<AppState>()
|
||||
.add_sub_state::<GameState>()
|
||||
.enable_state_scoped_entities::<GameState>()
|
||||
.configure_sets(
|
||||
FixedUpdate,
|
||||
GameSystemSet::LoadingTick.run_if(in_state(GameState::Loading)),
|
||||
)
|
||||
.configure_sets(
|
||||
FixedUpdate,
|
||||
(
|
||||
// these all run before physics, which is in FixedPostUpdate by default
|
||||
GameSystemSet::PreTick,
|
||||
GameSystemSet::Tick,
|
||||
GameSystemSet::PostTick,
|
||||
)
|
||||
.run_if(in_state(GameState::Running))
|
||||
.chain(),
|
||||
)
|
||||
.configure_sets(
|
||||
Update,
|
||||
(
|
||||
GameSystemSet::UpdateInputs,
|
||||
(
|
||||
GameSystemSet::PreUpdate,
|
||||
GameSystemSet::Update,
|
||||
GameSystemSet::PostUpdate,
|
||||
)
|
||||
.run_if(in_state(GameState::Running).or(in_state(GameState::Paused)))
|
||||
.chain(),
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_systems(OnEnter(GameState::Running), start_physics)
|
||||
.add_systems(OnExit(GameState::Running), stop_physics);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(States, Default, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum AppState {
|
||||
#[default]
|
||||
Setup,
|
||||
Menu,
|
||||
Game,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, SubStates, Default)]
|
||||
#[source(AppState = AppState::Game)]
|
||||
pub enum GameState {
|
||||
Loading,
|
||||
#[default]
|
||||
Running,
|
||||
Paused,
|
||||
}
|
||||
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum GameSystemSet {
|
||||
// runs even if game is not loaded/active
|
||||
UpdateInputs,
|
||||
// update - cosmetic only. runs while game is paused
|
||||
PreUpdate,
|
||||
Update,
|
||||
PostUpdate,
|
||||
// fixed update - contains all gameplay logic. does not run when game is paused
|
||||
LoadingTick,
|
||||
PreTick,
|
||||
Tick,
|
||||
PostTick,
|
||||
}
|
||||
|
||||
fn start_physics(mut time: ResMut<Time<Physics>>) {
|
||||
time.unpause();
|
||||
}
|
||||
|
||||
fn stop_physics(mut time: ResMut<Time<Physics>>) {
|
||||
time.pause();
|
||||
}
|
||||
11
crates/ui/Cargo.toml
Normal file
11
crates/ui/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "ui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
bevy-inspector-egui = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
14
crates/ui/src/lib.rs
Normal file
14
crates/ui/src/lib.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_inspector_egui::bevy_egui::EguiPlugin;
|
||||
|
||||
pub struct UiPlugin;
|
||||
|
||||
impl Plugin for UiPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(EguiPlugin::default());
|
||||
}
|
||||
}
|
||||
14
crates/ui_debug/Cargo.toml
Normal file
14
crates/ui_debug/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "ui_debug"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
bevy-inspector-egui = { workspace = true }
|
||||
|
||||
input = { path = "../input" }
|
||||
state = { path = "../state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
40
crates/ui_debug/src/lib.rs
Normal file
40
crates/ui_debug/src/lib.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
use input::{actions::InputAction, conditions::action_just_pressed};
|
||||
use state::GameSystemSet;
|
||||
|
||||
pub struct UiDebugPlugin;
|
||||
|
||||
impl Plugin for UiDebugPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_state::<DebugUIState>()
|
||||
.add_plugins(WorldInspectorPlugin::new().run_if(in_state(DebugUIState::Enabled)))
|
||||
.add_systems(
|
||||
Update,
|
||||
toggle_debug_ui
|
||||
.run_if(action_just_pressed::<Update>(InputAction::ToggleDebugUI))
|
||||
.after(GameSystemSet::UpdateInputs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(States, Default, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum DebugUIState {
|
||||
#[default]
|
||||
Disabled,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
fn toggle_debug_ui(
|
||||
mut next_state: ResMut<NextState<DebugUIState>>,
|
||||
state: Res<State<DebugUIState>>,
|
||||
) {
|
||||
match state.get() {
|
||||
DebugUIState::Disabled => next_state.set(DebugUIState::Enabled),
|
||||
DebugUIState::Enabled => next_state.set(DebugUIState::Disabled),
|
||||
}
|
||||
}
|
||||
14
crates/ui_pause/Cargo.toml
Normal file
14
crates/ui_pause/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "ui_pause"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
|
||||
state = { path = "../state" }
|
||||
input = { path = "../input" }
|
||||
ui_state = { path = "../ui_state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
30
crates/ui_pause/src/lib.rs
Normal file
30
crates/ui_pause/src/lib.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use input::{actions::InputAction, conditions::action_just_pressed};
|
||||
use state::{GameState, GameSystemSet};
|
||||
|
||||
pub struct UiPausePlugin;
|
||||
|
||||
impl Plugin for UiPausePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
toggle_pause
|
||||
.in_set(GameSystemSet::PreUpdate)
|
||||
.run_if(action_just_pressed::<Update>(InputAction::TogglePause)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_pause(state: Res<State<GameState>>, mut next_state: ResMut<NextState<GameState>>) {
|
||||
let next = match state.get() {
|
||||
GameState::Loading => GameState::Loading,
|
||||
GameState::Running => GameState::Paused,
|
||||
GameState::Paused => GameState::Running,
|
||||
};
|
||||
info!("toggle pause, changing to {:?}", next);
|
||||
next_state.set(next);
|
||||
}
|
||||
12
crates/ui_state/Cargo.toml
Normal file
12
crates/ui_state/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "ui_state"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
|
||||
state = { path = "../state" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
32
crates/ui_state/src/lib.rs
Normal file
32
crates/ui_state/src/lib.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use state::{AppState, GameState};
|
||||
|
||||
pub struct UiStatePlugin;
|
||||
|
||||
impl Plugin for UiStatePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_sub_state::<GameUIState>()
|
||||
.add_systems(OnEnter(GameState::Paused), on_pause)
|
||||
.add_systems(OnExit(GameState::Paused), on_unpause);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, SubStates, Default)]
|
||||
#[source(AppState = AppState::Game)]
|
||||
pub enum GameUIState {
|
||||
#[default]
|
||||
Default,
|
||||
Paused,
|
||||
}
|
||||
|
||||
fn on_pause(mut next: ResMut<NextState<GameUIState>>) {
|
||||
next.set(GameUIState::Paused)
|
||||
}
|
||||
|
||||
fn on_unpause(mut next: ResMut<NextState<GameUIState>>) {
|
||||
next.set(GameUIState::Default)
|
||||
}
|
||||
92
mkcrate
Executable file
92
mkcrate
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Exit on any error
|
||||
set -e
|
||||
|
||||
# Check if a name was provided
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <crate-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CRATE_NAME=$1
|
||||
# Create the PascalCase version of the name for the plugin
|
||||
PLUGIN_NAME="$(echo $CRATE_NAME | sed -r 's/(^|_)(.)/\U\2/g')"
|
||||
CRATE_PATH="crates/$CRATE_NAME"
|
||||
|
||||
# Step 2: Create a new library crate
|
||||
echo "Creating new library crate: $CRATE_NAME"
|
||||
cargo new --lib "$CRATE_PATH"
|
||||
|
||||
# Step 3 & 4: Modify the Cargo.toml file to add bevy dependency and lints
|
||||
echo "Updating $CRATE_PATH/Cargo.toml"
|
||||
# Create a temporary file
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
# Process the Cargo.toml file
|
||||
cat "$CRATE_PATH/Cargo.toml" | awk -v name="$CRATE_NAME" '
|
||||
BEGIN {print_mode=1}
|
||||
/\[dependencies\]/ {
|
||||
print $0;
|
||||
print "bevy = { workspace = true }";
|
||||
print_mode=0;
|
||||
next;
|
||||
}
|
||||
{
|
||||
if (print_mode) print $0;
|
||||
}
|
||||
END {
|
||||
if (!print_mode) print "";
|
||||
print "[lints]";
|
||||
print "workspace = true";
|
||||
}' > "$TMP_FILE"
|
||||
|
||||
# Replace the original file
|
||||
mv "$TMP_FILE" "$CRATE_PATH/Cargo.toml"
|
||||
|
||||
# Step 5: Replace the default lib.rs file
|
||||
echo "Updating $CRATE_PATH/src/lib.rs"
|
||||
cat > "$CRATE_PATH/src/lib.rs" << EOF
|
||||
//bevy system signatures often violate these rules
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct ${PLUGIN_NAME}Plugin;
|
||||
|
||||
impl Plugin for ${PLUGIN_NAME}Plugin {
|
||||
fn build(&self, app: &mut App) {}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Step 6: Add as a dependency to the main Cargo.toml
|
||||
echo "Adding dependency to main Cargo.toml"
|
||||
TMP_FILE=$(mktemp)
|
||||
awk -v name="$CRATE_NAME" '
|
||||
/^#new internal crates go here/ {
|
||||
print name " = { path = \"crates/" name "\" }";
|
||||
print $0;
|
||||
next;
|
||||
}
|
||||
{ print $0; }
|
||||
' Cargo.toml > "$TMP_FILE"
|
||||
mv "$TMP_FILE" Cargo.toml
|
||||
|
||||
# Step 7: Add plugin to main.rs
|
||||
echo "Adding plugin to main.rs"
|
||||
TMP_FILE=$(mktemp)
|
||||
awk -v name="$CRATE_NAME" -v plugin_name="$PLUGIN_NAME" '
|
||||
/[[:space:]]*\/\/ new internal crates go here/ {
|
||||
# Preserve the same indentation as the comment line
|
||||
match($0, /^[[:space:]]*/);
|
||||
indent = substr($0, RSTART, RLENGTH);
|
||||
print indent name "::" plugin_name "Plugin,";
|
||||
print $0;
|
||||
next;
|
||||
}
|
||||
{ print $0; }
|
||||
' src/main.rs > "$TMP_FILE"
|
||||
mv "$TMP_FILE" src/main.rs
|
||||
|
||||
echo "Successfully created and integrated the $CRATE_NAME crate!"
|
||||
26
src/main.rs
Normal file
26
src/main.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Coreheart".to_string(),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugins((
|
||||
state::StatePlugin,
|
||||
physics::PhysicsPlugin,
|
||||
// ui must come before the sub crates
|
||||
ui::UiPlugin,
|
||||
ui_state::UiStatePlugin,
|
||||
ui_debug::UiDebugPlugin,
|
||||
input::InputPlugin,
|
||||
setup::SetupPlugin,
|
||||
ui_pause::UiPausePlugin,
|
||||
// new internal crates go here
|
||||
));
|
||||
|
||||
app.run();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue