Rename galaxy_creation to galaxy; add character creation skeleton
- Rename GalaxyCreationPlugin -> GalaxyPlugin, AppState::GalaxyCreation -> AppState::Galaxy, module path galaxy_creation -> galaxy (folder rename preserves git history via git mv). - Rename GalaxyCreationSpawned -> GalaxySpawned, despawn_galaxy_creation -> despawn_galaxy. - Update AGENTS.md module layout, plugin pattern example, and naming table for the new names. - Add apps/game/src/gameplay/character_creation/ skeleton: placeholder UI with Confirm (-> InGame) and Back (-> Galaxy) buttons plus Escape shortcut. Wired into main.rs via CharacterCreationPlugin.
This commit is contained in:
@@ -20,40 +20,44 @@ cargo test # Run unit tests
|
||||
```
|
||||
src/
|
||||
├── main.rs # App builder: plugins, state init, system registration
|
||||
├── state.rs # AppState enum (MainMenu, GalaxyCreation, CharacterCreation, InGame, Options)
|
||||
├── state.rs # AppState enum (MainMenu, Galaxy, CharacterCreation, InGame, Options)
|
||||
├── camera.rs # Camera spawn
|
||||
├── ui/ # UI screens (menus, HUD, etc.)
|
||||
│ ├── mod.rs
|
||||
│ └── main_menu.rs
|
||||
└── gameplay/ # Non-UI gameplay systems
|
||||
├── mod.rs
|
||||
└── galaxy_creation.rs
|
||||
├── character_creation/ # Character creation scene (skeleton)
|
||||
├── galaxy/ # Procedural galaxy inspection scene
|
||||
├── movement/ # Ship movement (kinematic + orbital)
|
||||
├── physics/ # Physics primitives (mass, gravity, etc.)
|
||||
└── star_map/ # Star map data + visualization
|
||||
```
|
||||
|
||||
### When to add a file vs a folder
|
||||
|
||||
- **Default**: one file per feature (e.g. `main_menu.rs`, `galaxy_creation.rs`).
|
||||
- **Default**: one file per feature (e.g. `main_menu.rs`).
|
||||
- **Promote to a folder** when the file exceeds ~300 lines, mixes UI + logic + data, or needs shared private helpers.
|
||||
- A promoted folder should expose its public API via `mod.rs` and ideally bundle its systems into a Bevy `Plugin` (see pattern below).
|
||||
|
||||
### Plugin pattern (recommended once a folder exists)
|
||||
|
||||
```rust
|
||||
// src/gameplay/galaxy_creation/mod.rs
|
||||
pub struct GalaxyCreationPlugin;
|
||||
// src/gameplay/galaxy/mod.rs
|
||||
pub struct GalaxyPlugin;
|
||||
|
||||
impl Plugin for GalaxyCreationPlugin {
|
||||
impl Plugin for GalaxyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnEnter(AppState::GalaxyCreation), setup_galaxy_creation)
|
||||
.add_systems(OnExit(AppState::GalaxyCreation), despawn_galaxy_creation)
|
||||
app.add_systems(OnEnter(AppState::Galaxy), setup_galaxy)
|
||||
.add_systems(OnExit(AppState::Galaxy), despawn_galaxy)
|
||||
.add_systems(Update, (
|
||||
/* update systems */
|
||||
).run_if(in_state(AppState::GalaxyCreation)));
|
||||
).run_if(in_state(AppState::Galaxy)));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then `main.rs` collapses to `app.add_plugins((..., GalaxyCreationPlugin))`.
|
||||
Then `main.rs` collapses to `app.add_plugins((..., GalaxyPlugin))`.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
@@ -61,13 +65,13 @@ Follows [Rust RFC 344](https://rust-lang.github.io/api-guidelines/naming.html).
|
||||
|
||||
| Item | Convention | Example |
|
||||
|---|---|---|
|
||||
| Files, modules, directories | `snake_case` | `galaxy_creation.rs`, `main_menu.rs` |
|
||||
| Files, modules, directories | `snake_case` | `galaxy`, `main_menu` |
|
||||
| Crate name | `snake_case` | `void-nav` |
|
||||
| Structs, enums, enum variants | `UpperCamelCase` | `AppState`, `GalaxyCreation` |
|
||||
| Structs, enums, enum variants | `UpperCamelCase` | `AppState`, `Galaxy` |
|
||||
| Components | `UpperCamelCase` (suffix optional) | `Player`, `MainMenuUi` |
|
||||
| Resources | `UpperCamelCase` | `ClearColor`, `Time` |
|
||||
| States | `UpperCamelCase` + `State` suffix | `AppState`, `GameState` |
|
||||
| Plugins | `UpperCamelCase` + `Plugin` suffix | `GalaxyCreationPlugin` |
|
||||
| Plugins | `UpperCamelCase` + `Plugin` suffix | `GalaxyPlugin` |
|
||||
| Functions, systems, locals | `snake_case`, verb-first | `spawn_camera`, `setup_main_menu` |
|
||||
| Constants, statics | `SCREAMING_SNAKE_CASE` | `MAX_HEALTH` |
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::ui::util::cursor_over_ui;
|
||||
pub struct MainCamera;
|
||||
|
||||
/// Orbit-style camera: rotates around `target` at `distance`, controlled by mouse.
|
||||
/// Used for inspection scenes like GalaxyCreation where there is no player to follow.
|
||||
/// Used for inspection scenes like Galaxy where there is no player to follow.
|
||||
///
|
||||
/// Orientation is stored as a quaternion (`rotation`) to allow true 360° motion
|
||||
/// with no gimbal lock. The base orientation is camera at `target + (0, 0, distance)`
|
||||
|
||||
194
apps/game/src/gameplay/character_creation/mod.rs
Normal file
194
apps/game/src/gameplay/character_creation/mod.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
//! Character creation scene.
|
||||
//!
|
||||
//! Barebones skeleton: spawns a placeholder UI on enter, despawns on exit,
|
||||
//! and wires a `Confirm` button that transitions to [`AppState::InGame`] and
|
||||
//! a `Back` button (or Escape) that returns to [`AppState::Galaxy`].
|
||||
//!
|
||||
//! Intended to grow into the real character creator — name, portrait, stats,
|
||||
//! starting ship, background / origin, etc. Each major aspect should split
|
||||
//! into its own submodule (e.g. `ui.rs`, `params.rs`, `presets.rs`) once it
|
||||
//! outgrows this file (see the module-layout guidelines in `apps/game/AGENTS.md`).
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
pub struct CharacterCreationPlugin;
|
||||
|
||||
impl Plugin for CharacterCreationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnEnter(AppState::CharacterCreation), setup_character_creation)
|
||||
.add_systems(
|
||||
OnExit(AppState::CharacterCreation),
|
||||
despawn_character_creation,
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
escape_to_galaxy,
|
||||
confirm_button_handler,
|
||||
back_button_handler,
|
||||
)
|
||||
.run_if(in_state(AppState::CharacterCreation)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Markers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Tag for everything spawned during character creation so it can be cleanly
|
||||
/// despawned on state exit.
|
||||
#[derive(Component)]
|
||||
pub struct CharacterCreationSpawned;
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum CharacterCreationButton {
|
||||
/// Finalize the character and begin the game.
|
||||
Confirm,
|
||||
/// Discard and return to the galaxy selection screen.
|
||||
Back,
|
||||
}
|
||||
|
||||
// ── Setup ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn setup_character_creation(mut commands: Commands) {
|
||||
let button_style = Node {
|
||||
width: Val::Px(220.0),
|
||||
height: Val::Px(56.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
};
|
||||
let button_font = TextFont {
|
||||
font_size: 24.0,
|
||||
..default()
|
||||
};
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
row_gap: Val::Px(24.0),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(Color::srgb(0.02, 0.02, 0.06)),
|
||||
CharacterCreationSpawned,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Text::new("Character Creation"),
|
||||
TextFont {
|
||||
font_size: 56.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::srgb(0.7, 0.85, 1.0)),
|
||||
Node {
|
||||
margin: UiRect::bottom(Val::Px(16.0)),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
parent.spawn((
|
||||
Text::new("TODO: name, portrait, stats, background, starting ship"),
|
||||
TextFont {
|
||||
font_size: 18.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::srgb(0.4, 0.5, 0.6)),
|
||||
Node {
|
||||
margin: UiRect::bottom(Val::Px(48.0)),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
spawn_button(
|
||||
&mut parent.spawn_empty(),
|
||||
"Confirm",
|
||||
CharacterCreationButton::Confirm,
|
||||
&button_style,
|
||||
&button_font,
|
||||
);
|
||||
spawn_button(
|
||||
&mut parent.spawn_empty(),
|
||||
"Back",
|
||||
CharacterCreationButton::Back,
|
||||
&button_style,
|
||||
&button_font,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_button(
|
||||
cmd: &mut EntityCommands,
|
||||
label: &str,
|
||||
marker: CharacterCreationButton,
|
||||
style: &Node,
|
||||
text_font: &TextFont,
|
||||
) {
|
||||
cmd.insert((
|
||||
Button,
|
||||
style.clone(),
|
||||
BackgroundColor(Color::srgb(0.08, 0.1, 0.18)),
|
||||
BorderColor(Color::srgb(0.3, 0.45, 0.7)),
|
||||
BorderRadius::all(Val::Px(8.0)),
|
||||
marker,
|
||||
))
|
||||
.with_children(|btn| {
|
||||
btn.spawn((
|
||||
Text::new(label),
|
||||
text_font.clone(),
|
||||
TextColor(Color::srgb(0.75, 0.85, 1.0)),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn despawn_character_creation(
|
||||
mut commands: Commands,
|
||||
query: Query<Entity, With<CharacterCreationSpawned>>,
|
||||
) {
|
||||
for entity in &query {
|
||||
// Bevy 0.16: despawn() is recursive by default.
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Input ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn escape_to_galaxy(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
) {
|
||||
if keys.just_pressed(KeyCode::Escape) {
|
||||
next_state.set(AppState::Galaxy);
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_button_handler(
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
query: Query<(&Interaction, &CharacterCreationButton), Changed<Interaction>>,
|
||||
) {
|
||||
for (interaction, button) in &query {
|
||||
if *interaction == Interaction::Pressed && matches!(button, CharacterCreationButton::Confirm) {
|
||||
// TODO: persist the configured character before entering the game.
|
||||
next_state.set(AppState::InGame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn back_button_handler(
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
query: Query<(&Interaction, &CharacterCreationButton), Changed<Interaction>>,
|
||||
) {
|
||||
for (interaction, button) in &query {
|
||||
if *interaction == Interaction::Pressed && matches!(button, CharacterCreationButton::Back) {
|
||||
next_state.set(AppState::Galaxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
//! World-space XYZ axis indicator (red/green/blue cylinders through the origin).
|
||||
//! World-space XYZ axis indicator (grey cylinders through the origin).
|
||||
//!
|
||||
//! Provides spatial reference for the galaxy inspection scene. Drawn as three
|
||||
//! thin emissive cylinders — mesh-based rather than gizmos so they participate
|
||||
//! in normal scene rendering (occlusion, depth).
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{math::InvalidDirectionError::Infinite, prelude::*};
|
||||
|
||||
/// Length of each axis arrow. Picked to be visible from the default orbit
|
||||
/// distance (~420) without dominating the scene.
|
||||
const AXIS_LENGTH: f32 = 60.0;
|
||||
const AXIS_LENGTH: f32 = 100000.00;
|
||||
/// Cylinder radius — same scale as connection lines so axes feel like part of
|
||||
/// the galaxy scaffold.
|
||||
const AXIS_RADIUS: f32 = 0.35;
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Galaxy creation inspection scene.
|
||||
//! Galaxy inspection scene.
|
||||
//!
|
||||
//! Procedural spiral galaxy viewer with editable parameters. The 3D scene is
|
||||
//! regenerated whenever [`GalaxyParams`] changes (via the `generation` counter);
|
||||
@@ -26,19 +26,19 @@ pub use contents::{SystemContents, SystemContext, SystemSummary};
|
||||
pub use params::{GalaxyParams, SelectedStar};
|
||||
use params::{CORE_COUNT, NEAREST_NEIGHBOR_CONNECTIONS, SPACING_ATTEMPTS};
|
||||
|
||||
pub struct GalaxyCreationPlugin;
|
||||
pub struct GalaxyPlugin;
|
||||
|
||||
impl Plugin for GalaxyCreationPlugin {
|
||||
impl Plugin for GalaxyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<GalaxyParams>()
|
||||
.init_resource::<SelectedStar>()
|
||||
.add_systems(
|
||||
OnEnter(AppState::GalaxyCreation),
|
||||
OnEnter(AppState::Galaxy),
|
||||
(setup_galaxy_scene, ui::setup_galaxy_ui),
|
||||
)
|
||||
.add_systems(
|
||||
OnExit(AppState::GalaxyCreation),
|
||||
(despawn_galaxy_creation, reset_selection),
|
||||
OnExit(AppState::Galaxy),
|
||||
(despawn_galaxy, reset_selection),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
@@ -55,7 +55,7 @@ impl Plugin for GalaxyCreationPlugin {
|
||||
orbits::advance_orbital_paths,
|
||||
)
|
||||
.chain()
|
||||
.run_if(in_state(AppState::GalaxyCreation)),
|
||||
.run_if(in_state(AppState::Galaxy)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ impl Plugin for GalaxyCreationPlugin {
|
||||
/// Tag for *anything* spawned during galaxy creation so it can be cleanly
|
||||
/// despawned on state exit. Applies to both 3D scene roots and UI panel roots.
|
||||
#[derive(Component)]
|
||||
pub struct GalaxyCreationSpawned;
|
||||
pub struct GalaxySpawned;
|
||||
|
||||
/// Tag for the 3D scene root only — despawned on regeneration, so we can
|
||||
/// rebuild the galaxy without disturbing the UI panels.
|
||||
@@ -349,7 +349,7 @@ fn spawn_galaxy_scene(
|
||||
|
||||
// Parent group so all galaxy contents despawn together.
|
||||
commands
|
||||
.spawn((Transform::default(), GalaxyScene, GalaxyCreationSpawned))
|
||||
.spawn((Transform::default(), GalaxyScene, GalaxySpawned))
|
||||
.with_children(|parent| {
|
||||
// XYZ reference axes through the origin.
|
||||
axes::spawn_axes(parent, meshes, materials);
|
||||
@@ -467,9 +467,9 @@ fn build_connections(systems: &[GeneratedSystem]) -> Vec<(usize, usize)> {
|
||||
|
||||
// ── Lifecycle systems ───────────────────────────────────────────────────────
|
||||
|
||||
fn despawn_galaxy_creation(
|
||||
fn despawn_galaxy(
|
||||
mut commands: Commands,
|
||||
query: Query<Entity, With<GalaxyCreationSpawned>>,
|
||||
query: Query<Entity, With<GalaxySpawned>>,
|
||||
) {
|
||||
for entity in &query {
|
||||
// Bevy 0.16: despawn() is recursive by default.
|
||||
@@ -10,8 +10,9 @@ use bevy::window::PrimaryWindow;
|
||||
|
||||
use super::StarSystem;
|
||||
use crate::camera::MainCamera;
|
||||
use crate::gameplay::galaxy_creation::params::SelectedStar;
|
||||
use crate::gameplay::galaxy_creation::ui::GalaxyInfoPanel;
|
||||
use crate::gameplay::galaxy::params::SelectedStar;
|
||||
use crate::gameplay::galaxy::ui::GalaxyInfoPanel;
|
||||
use crate::gameplay::galaxy::SystemContents;
|
||||
use crate::ui::util::cursor_over_ui;
|
||||
|
||||
// ── Tunables ────────────────────────────────────────────────────────────────
|
||||
@@ -163,6 +164,8 @@ pub fn refresh_info_panel(
|
||||
));
|
||||
spawn_info_line(parent, "Faction", sys.faction);
|
||||
spawn_info_line(parent, "Security", &format!("{:.2}", sys.security));
|
||||
spawn_info_line(parent, "POIs", &format!("{}", sys.poi_count));
|
||||
|
||||
spawn_info_line(parent, "ID", &sys.id);
|
||||
}
|
||||
None => {
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Galaxy creation UI: parameter slider panel (left) and selected-system
|
||||
//! Galaxy UI: parameter slider panel (left) and selected-system
|
||||
//! info panel (right).
|
||||
//!
|
||||
//! Bevy 0.16 does not ship a native Slider widget, so each parameter is a
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::GalaxyCreationSpawned;
|
||||
use crate::gameplay::galaxy_creation::params::*;
|
||||
use super::GalaxySpawned;
|
||||
use crate::gameplay::galaxy::params::*;
|
||||
|
||||
// ── Markers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -82,7 +82,7 @@ fn spawn_control_panel(commands: &mut Commands) {
|
||||
BorderColor(PANEL_BORDER),
|
||||
BorderRadius::all(Val::Px(8.0)),
|
||||
GalaxyControlPanel,
|
||||
GalaxyCreationSpawned,
|
||||
GalaxySpawned,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
@@ -292,7 +292,7 @@ fn spawn_info_panel_empty(commands: &mut Commands) {
|
||||
BorderColor(PANEL_BORDER),
|
||||
BorderRadius::all(Val::Px(8.0)),
|
||||
GalaxyInfoPanel,
|
||||
GalaxyCreationSpawned,
|
||||
GalaxySpawned,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
@@ -440,6 +440,6 @@ pub fn refresh_control_panel_values(
|
||||
|
||||
// ── State wiring ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// `setup_galaxy_ui` runs on OnEnter(GalaxyCreation) via the plugin in `mod.rs`.
|
||||
// Despawning happens through the shared `GalaxyCreationSpawned` marker in the
|
||||
// `setup_galaxy_ui` runs on OnEnter(Galaxy) via the plugin in `mod.rs`.
|
||||
// Despawning happens through the shared `GalaxySpawned` marker in the
|
||||
// plugin's OnExit handler — no separate UI cleanup needed.
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod galaxy_creation;
|
||||
pub mod character_creation;
|
||||
pub mod galaxy;
|
||||
pub mod movement;
|
||||
pub mod physics;
|
||||
pub mod star_map;
|
||||
|
||||
@@ -7,7 +7,8 @@ use bevy::prelude::*;
|
||||
|
||||
use camera::orbit_camera_control;
|
||||
use gameplay::{
|
||||
galaxy_creation::GalaxyCreationPlugin,
|
||||
character_creation::CharacterCreationPlugin,
|
||||
galaxy::GalaxyPlugin,
|
||||
movement::MovementPlugin,
|
||||
physics::PhysicsPlugin,
|
||||
star_map::StarMapPlugin,
|
||||
@@ -30,7 +31,7 @@ fn main() {
|
||||
// follow camera instead (not yet implemented).
|
||||
.add_systems(
|
||||
Update,
|
||||
orbit_camera_control.run_if(in_state(AppState::GalaxyCreation)),
|
||||
orbit_camera_control.run_if(in_state(AppState::Galaxy)),
|
||||
)
|
||||
.add_systems(OnEnter(AppState::MainMenu), main_menu::setup_main_menu)
|
||||
.add_systems(OnExit(AppState::MainMenu), main_menu::despawn_main_menu)
|
||||
@@ -38,8 +39,9 @@ fn main() {
|
||||
.add_plugins((
|
||||
MovementPlugin,
|
||||
PhysicsPlugin,
|
||||
GalaxyCreationPlugin,
|
||||
GalaxyPlugin,
|
||||
StarMapPlugin,
|
||||
CharacterCreationPlugin,
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use bevy::prelude::*;
|
||||
pub enum AppState {
|
||||
#[default]
|
||||
MainMenu,
|
||||
GalaxyCreation,
|
||||
Galaxy,
|
||||
CharacterCreation,
|
||||
InGame,
|
||||
Options,
|
||||
|
||||
@@ -145,7 +145,7 @@ pub fn main_menu_buttons(
|
||||
if *interaction == Interaction::Pressed {
|
||||
match button {
|
||||
MenuButton::ContinueGame => next_state.set(AppState::InGame), // Placeholder for now
|
||||
MenuButton::NewGame => next_state.set(AppState::GalaxyCreation),
|
||||
MenuButton::NewGame => next_state.set(AppState::Galaxy),
|
||||
MenuButton::Options => next_state.set(AppState::Options),
|
||||
MenuButton::Exit => {
|
||||
exit.write(AppExit::Success);
|
||||
|
||||
Reference in New Issue
Block a user