rendering, physics, player and camera WIP
This commit is contained in:
656
CLAUDE.md
Normal file
656
CLAUDE.md
Normal file
@@ -0,0 +1,656 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a pure Rust game project using SDL3 for windowing/input, wgpu for rendering, rapier3d for physics, and a low-res retro aesthetic with dithering. This is a migration from the Godot-based snow_trail project, implementing the same snow deformation system and character controller without engine dependencies.
|
||||
|
||||
## Code Style
|
||||
|
||||
**Code Documentation Guidelines**:
|
||||
- **NO inline comments unless ABSOLUTELY necessary**
|
||||
- Code must be self-documenting through clear naming and structure
|
||||
- Use doc comments (`///`) only for public APIs and complex algorithms
|
||||
- Avoid obvious comments that restate what the code does
|
||||
- Let the code speak for itself
|
||||
|
||||
**Formatting:**
|
||||
- All code must follow the project's `rustfmt.toml` configuration
|
||||
- Always run `cargo fmt` before committing to ensure consistent formatting
|
||||
- Current rustfmt settings: brace_style = "AlwaysNextLine", control_brace_style = "AlwaysNextLine"
|
||||
- **NO inline paths** - always add `use` statements at the top of files (e.g., `use std::rc::Rc;` instead of `std::rc::Rc` inline in code)
|
||||
- **NO inline `use` statements in functions** - all `use` statements must be at the file level (module top), not inside function bodies or impl blocks
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### ECS Architecture
|
||||
|
||||
The project uses a **pure ECS (Entity Component System)** architecture:
|
||||
|
||||
**Entities:**
|
||||
- Just IDs (`EntityHandle = u64`)
|
||||
- Managed by `EntityManager` (spawn/despawn)
|
||||
- No data themselves - just containers for components
|
||||
|
||||
**Components:**
|
||||
- Pure data structures stored in component storages
|
||||
- Each storage is a `HashMap<EntityHandle, ComponentData>`
|
||||
- No `Rc<RefCell<>>` - clean ownership model
|
||||
- Components: Transform, Mesh, Physics, Movement, Input, PlayerTag, StateMachine
|
||||
|
||||
**Systems:**
|
||||
- Functions that query entities with specific component combinations
|
||||
- Run each frame in defined order
|
||||
- Read from and write to component storages
|
||||
- Examples: `player_input_system`, `state_machine_system`, `physics_sync_system`, `render_system`
|
||||
|
||||
**Component Storages (World-owned):**
|
||||
- `TransformStorage` - Position, rotation, scale
|
||||
- `MeshStorage` - Mesh data + render pipeline
|
||||
- `PhysicsStorage` - Rapier3d rigidbody/collider handles
|
||||
- `MovementStorage` - Movement config + state
|
||||
- `InputStorage` - Gameplay input commands
|
||||
- `PlayerTagStorage` - Marker for player entities
|
||||
- `StateMachineStorage` - Behavior state machines
|
||||
- All storages owned by single `World` struct for clean ownership
|
||||
|
||||
**Key Benefits:**
|
||||
- No `Rc<RefCell<>>` needed - components are just data
|
||||
- Clear data flow through systems
|
||||
- Easy to add/remove components at runtime
|
||||
- Testable - systems are pure functions
|
||||
- StateMachine integrates as a component for complex behaviors
|
||||
- EventBus remains for irregular events and cross-system messaging
|
||||
|
||||
### SDL3 vs SDL2
|
||||
We are using SDL3 (latest stable bindings) rather than SDL2. SDL3 provides:
|
||||
- Modern GPU API integration
|
||||
- Better input handling
|
||||
- Active development and future-proofing
|
||||
|
||||
As of December 2025, SDL3 Rust bindings are usable but still maturing:
|
||||
- `sdl3` crate: v0.16.2 (high-level bindings)
|
||||
- `sdl3-sys` crate: v0.5.11 (low-level FFI)
|
||||
- Some features may be incomplete, but core functionality is stable
|
||||
|
||||
### wgpu for Rendering
|
||||
|
||||
**Using wgpu instead of OpenGL:**
|
||||
- Modern GPU API abstraction (Vulkan/Metal/DX12/OpenGL backends)
|
||||
- Better cross-platform support
|
||||
- WGSL shader language (WebGPU Shading Language)
|
||||
- Type-safe API with explicit resource management
|
||||
- Low-res framebuffer rendering with 3-bit RGB dithering (retro aesthetic)
|
||||
|
||||
**Rendering Architecture:**
|
||||
- wgpu for 3D mesh rendering with custom shaders
|
||||
- Low-resolution framebuffer (160×120) upscaled to window size
|
||||
- Bayer 8×8 dithering for 3-bit RGB color (8 colors total)
|
||||
- Multiple rendering pipelines: standard meshes and terrain
|
||||
- Separate bind groups for different material types
|
||||
|
||||
### Future: Debug UI
|
||||
- Debug UI system not yet implemented
|
||||
- Will be used for real-time parameter tweaking (replacing Godot's exported properties)
|
||||
- Current debugging relies on println! and recompilation
|
||||
|
||||
## Physics Integration
|
||||
|
||||
Using rapier3d for 3D physics:
|
||||
- Character controller implemented manually (no built-in CharacterBody equivalent)
|
||||
- Ground detection via raycasting with QueryPipeline
|
||||
- Manual rigidbody velocity application
|
||||
- State machine for movement states (Idle, Walking, Jumping, Falling)
|
||||
|
||||
## Input Handling
|
||||
|
||||
**Two-Layer Input Pipeline:**
|
||||
|
||||
**Layer 1: Raw Input (`utility/input.rs` - `InputState`):**
|
||||
- Global singleton for SDL event handling
|
||||
- Tracks raw hardware state (W/A/S/D pressed, mouse delta, etc.)
|
||||
- Handles SDL events via `handle_event()` method
|
||||
- Manages global state (mouse capture, quit request, noclip mode)
|
||||
- Lives in main event loop
|
||||
|
||||
**Layer 2: Gameplay Commands (`components/input.rs` - `InputComponent`):**
|
||||
- Per-entity ECS component
|
||||
- Stores processed gameplay commands (move_direction, jump_pressed)
|
||||
- Filled by `player_input_system()` which reads `InputState`
|
||||
- Used by movement systems to control entities
|
||||
- Decouples input source from entity control
|
||||
|
||||
**Input Flow:**
|
||||
```
|
||||
SDL Events → InputState → player_input_system() → InputComponent → movement_system()
|
||||
```
|
||||
|
||||
**Current Input Layout:**
|
||||
- `W/A/S/D`: Movement (converted to Vec3 direction in InputComponent)
|
||||
- `Space`: Jump (sets jump_pressed in InputComponent)
|
||||
- `Shift`: Speed boost (for noclip camera)
|
||||
- `I`: Toggle mouse capture (lock/unlock cursor)
|
||||
- `Escape`: Quit game
|
||||
- `N`: Toggle noclip mode
|
||||
- Mouse motion: Camera look (yaw/pitch)
|
||||
|
||||
## Rendering Pipeline
|
||||
|
||||
**wgpu Rendering System:**
|
||||
- Low-res framebuffer (160×120) renders to texture
|
||||
- Bayer 8×8 dithering reduces colors to 3-bit RGB (8 colors)
|
||||
- Final blit pass upscales framebuffer to window using nearest-neighbor sampling
|
||||
- Depth buffer for 3D rendering with proper occlusion
|
||||
|
||||
**Terrain Height Deformation:**
|
||||
- EXR heightmap files loaded via `exr` crate (single-channel R32Float format)
|
||||
- Height displacement applied in vertex shader
|
||||
- Separate terrain pipeline with texture sampling in vertex stage
|
||||
- TerrainUniforms includes height_scale parameter for tweaking displacement strength
|
||||
- R32Float textures require non-filterable samplers (FilterMode::Nearest)
|
||||
|
||||
**Lighting Model:**
|
||||
- Directional light (like Godot's DirectionalLight3D)
|
||||
- Diffuse + ambient lighting (basic Phong model, no specular)
|
||||
- Light direction is uniform across entire scene
|
||||
- No attenuation or distance falloff
|
||||
- Dithering applied after lighting calculations
|
||||
|
||||
## Migration from Godot
|
||||
|
||||
This project ports the snow_trail Godot project (located at `~/shared/projects/snow_trail`) to pure Rust:
|
||||
|
||||
**What carries over:**
|
||||
- Snow deformation compute shader logic (GLSL can be reused with minor adjustments)
|
||||
- Character controller state machine architecture
|
||||
- Movement physics parameters
|
||||
- Camera follow behavior
|
||||
|
||||
**What changes:**
|
||||
- No `Base<Node3D>` pattern → **Pure ECS with EntityHandle + Components**
|
||||
- No Godot scene tree → **Entity-Component-System architecture**
|
||||
- No exported properties → Components with data (debug UI planned for future)
|
||||
- rapier3d RigidBodyHandle in PhysicsComponent instead of Gd<RigidBody3D>
|
||||
- Manual ground detection instead of CharacterBody3D.is_on_floor()
|
||||
- **Component storages** (TransformStorage, MeshStorage, etc.) instead of Godot nodes
|
||||
- **Systems** (player_input_system, state_machine_system, etc.) instead of _process()
|
||||
- **No `Rc<RefCell<>>`** - components are just data in hashmaps
|
||||
- Event bus implemented from scratch (complementary to systems)
|
||||
- State machine implemented from scratch (integrates as ECS component)
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
cargo build --release
|
||||
cargo check
|
||||
cargo test
|
||||
cargo run
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
## Shader Files
|
||||
|
||||
WGSL shaders are stored in the `shaders/` directory:
|
||||
- `shaders/standard.wgsl` - Standard mesh rendering with directional lighting
|
||||
- `shaders/terrain.wgsl` - Terrain rendering with height displacement
|
||||
- `shaders/blit.wgsl` - Fullscreen blit for upscaling low-res framebuffer
|
||||
|
||||
Shaders are loaded at runtime via `std::fs::read_to_string()`, allowing hot-reloading by restarting the application.
|
||||
|
||||
## Module Structure
|
||||
|
||||
**Core:**
|
||||
- `main.rs` - SDL3 event loop, game loop orchestration, system execution order
|
||||
- `entity.rs` - EntityManager for entity lifecycle (spawn/despawn/query)
|
||||
- `world.rs` - World struct that owns all component storages and EntityManager
|
||||
|
||||
**ECS Components (`components/`):**
|
||||
- `input.rs` - InputComponent (gameplay commands)
|
||||
- `mesh.rs` - MeshComponent (mesh + pipeline)
|
||||
- `movement.rs` - MovementComponent (movement config/state)
|
||||
- `physics.rs` - PhysicsComponent (rigidbody/collider handles)
|
||||
- `player_tag.rs` - PlayerTag marker component
|
||||
- `state_machine.rs` - (empty, StateMachine defined in state.rs)
|
||||
- Note: Component *storages* are defined in `world.rs`, not in component files
|
||||
|
||||
**ECS Systems (`systems/`):**
|
||||
- `input.rs` - player_input_system (InputState → InputComponent)
|
||||
- `state_machine.rs` - state_machine_system (updates all state machines)
|
||||
- `physics_sync.rs` - physics_sync_system (physics → transforms)
|
||||
- `render.rs` - render_system (queries entities, generates DrawCalls)
|
||||
|
||||
**Rendering:**
|
||||
- `render.rs` - wgpu renderer, pipelines, bind groups, DrawCall execution
|
||||
- `shader.rs` - Standard mesh shader (WGSL) with diffuse+ambient lighting
|
||||
- `terrain.rs` - Terrain mesh generation and pipeline creation
|
||||
- `postprocess.rs` - Low-res framebuffer and blit shader for upscaling
|
||||
- `mesh.rs` - Vertex/Mesh structs, plane/cube mesh generation, glTF loading
|
||||
- `heightmap.rs` - EXR heightmap loading using `exr` crate
|
||||
- `draw.rs` - DrawManager (legacy, kept for compatibility)
|
||||
|
||||
**Game Logic:**
|
||||
- `player.rs` - Player entity spawning function
|
||||
- `camera.rs` - 3D camera with rotation and follow behavior
|
||||
- `movement.rs` - Movement configuration and state structs
|
||||
- `state.rs` - Generic StateMachine implementation
|
||||
- `physics.rs` - PhysicsManager singleton (rapier3d world)
|
||||
|
||||
**Utilities:**
|
||||
- `utility/input.rs` - InputState (raw SDL input handling)
|
||||
- `utility/time.rs` - Time singleton (game time tracking)
|
||||
- `utility/transform.rs` - Transform struct (position/rotation/scale data type)
|
||||
|
||||
**Debug:**
|
||||
- `debug/noclip.rs` - Noclip camera controller for development
|
||||
|
||||
**Other:**
|
||||
- `event.rs` - Type-safe event bus (complementary to ECS for irregular events)
|
||||
- `picking.rs` - Ray casting for mouse picking (unused currently)
|
||||
|
||||
## Dependencies Rationale
|
||||
|
||||
- **sdl3**: Windowing, input events, and platform integration
|
||||
- **wgpu**: Modern GPU API abstraction for rendering (Vulkan/Metal/DX12 backends)
|
||||
- **pollster**: Simple blocking executor for async wgpu initialization
|
||||
- **rapier3d**: Fast physics engine with good Rust integration
|
||||
- **glam**: Fast vector/matrix math library (vec3, mat4, quaternions)
|
||||
- **nalgebra**: Linear algebra for rapier3d integration (Isometry3 conversions)
|
||||
- **bytemuck**: Safe byte casting for GPU buffer uploads (Pod/Zeroable for vertex data)
|
||||
- **anyhow**: Ergonomic error handling
|
||||
- **gltf**: Loading 3D models in glTF format
|
||||
- **exr**: Loading EXR heightmap files (single-channel float data)
|
||||
- **image**: Image loading and processing (includes EXR support)
|
||||
- **half**: Float16 support (dependency of exr)
|
||||
- **kurbo**: Bezier curve evaluation for movement acceleration curves
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### EXR Heightmap Loading
|
||||
When loading EXR files with the `exr` crate:
|
||||
- Must import traits: `use exr::prelude::{ReadChannels, ReadLayers};`
|
||||
- Use builder pattern: `.no_deep_data().largest_resolution_level().all_channels().all_layers().all_attributes().from_file(path)`
|
||||
- Extract float data: `channel.sample_data.values_as_f32().collect()`
|
||||
- Create R32Float texture for height data
|
||||
- R32Float is non-filterable, requires `FilterMode::Nearest` sampler
|
||||
|
||||
### wgpu Texture Formats
|
||||
- R32Float = single-channel 32-bit float, **non-filterable**
|
||||
- Use `TextureSampleType::Float { filterable: false }` in bind group layout
|
||||
- Use `SamplerBindingType::NonFiltering` for sampler binding
|
||||
- Attempting linear filtering on R32Float causes validation errors
|
||||
|
||||
### Multiple Render Pipelines
|
||||
- `Pipeline` enum determines which pipeline to use per DrawCall
|
||||
- Different pipelines can have different shaders, bind group layouts, uniforms
|
||||
- Terrain pipeline: includes height texture binding in vertex stage
|
||||
- Standard pipeline: basic mesh rendering without height displacement
|
||||
- Each pipeline writes to its own uniform buffer before rendering
|
||||
|
||||
### ECS Component Storages
|
||||
|
||||
**Pattern:**
|
||||
All component storages are owned by the `World` struct:
|
||||
|
||||
```rust
|
||||
pub struct World {
|
||||
pub entities: EntityManager,
|
||||
pub transforms: TransformStorage,
|
||||
pub meshes: MeshStorage,
|
||||
pub physics: PhysicsStorage,
|
||||
pub movements: MovementStorage,
|
||||
pub inputs: InputStorage,
|
||||
pub player_tags: PlayerTagStorage,
|
||||
pub state_machines: StateMachineStorage,
|
||||
}
|
||||
|
||||
pub struct TransformStorage {
|
||||
pub components: HashMap<EntityHandle, Transform>,
|
||||
}
|
||||
|
||||
impl TransformStorage {
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: Transform) { }
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&Transform> { }
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R> { }
|
||||
pub fn remove(&mut self, entity: EntityHandle) { }
|
||||
pub fn all(&self) -> Vec<EntityHandle> { }
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- No `Rc<RefCell<>>` needed - clean ownership model
|
||||
- World owns all component data - explicit ownership
|
||||
- Instance methods instead of static methods
|
||||
- Systems receive `&mut World` - clear data dependencies
|
||||
- Easy to test - can create multiple worlds
|
||||
- Safe lookups return `Option`
|
||||
|
||||
**Example Usage:**
|
||||
```rust
|
||||
// Create world and entity
|
||||
let mut world = World::new();
|
||||
let entity = world.spawn();
|
||||
|
||||
// Insert components via world
|
||||
world.transforms.insert(entity, Transform::IDENTITY);
|
||||
world.meshes.insert(entity, MeshComponent { ... });
|
||||
|
||||
// Query and update via world
|
||||
world.transforms.with_mut(entity, |transform| {
|
||||
transform.position += velocity * delta;
|
||||
});
|
||||
|
||||
// Systems receive world
|
||||
pub fn my_system(world: &mut World) {
|
||||
for entity in world.player_tags.all() {
|
||||
if let Some(input) = world.inputs.get(entity) {
|
||||
// Process input...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
world.despawn(entity); // Removes from all storages
|
||||
```
|
||||
|
||||
### State Machine as ECS Component
|
||||
|
||||
StateMachine integrates into ECS as a component for complex entity behaviors:
|
||||
|
||||
**State Machine Pattern:**
|
||||
- StateMachine owns all states via `HashMap<TypeId, Box<dyn State>>`
|
||||
- TypeId-based state identification
|
||||
- Transition conditions are simple closures (can capture entity ID)
|
||||
- State callbacks receive `&mut World` and can access any component
|
||||
- Updated by `state_machine_system()` each frame using safe remove/insert pattern
|
||||
|
||||
**Integration Example:**
|
||||
```rust
|
||||
// Create state machine for entity
|
||||
let mut sm = StateMachine::new(Box::new(IdleState { entity }));
|
||||
sm.add_state(WalkingState { entity });
|
||||
|
||||
// Transitions can capture entity for checking
|
||||
sm.add_transition::<IdleState, WalkingState>(move || {
|
||||
// Note: transitions run before update, so they don't access world
|
||||
false // Placeholder - implement proper transition logic
|
||||
});
|
||||
|
||||
// Insert into world
|
||||
world.state_machines.insert(entity, sm);
|
||||
|
||||
// States receive world when updated
|
||||
impl State for IdleState {
|
||||
fn on_state_update(&mut self, world: &mut World, delta: f32) {
|
||||
// States can access any component via world
|
||||
if let Some(input) = world.inputs.get(self.entity) {
|
||||
// React to input...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**State Machine System (Safe Pattern):**
|
||||
```rust
|
||||
pub fn state_machine_system(world: &mut World, delta: f32) {
|
||||
let entities: Vec<_> = world.state_machines.all();
|
||||
|
||||
for entity in entities {
|
||||
// Temporarily remove state machine to avoid borrow conflicts
|
||||
if let Some(mut state_machine) = world.state_machines.components.remove(&entity) {
|
||||
state_machine.update(world, delta); // States can now safely access world
|
||||
world.state_machines.components.insert(entity, state_machine);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event System (event.rs)
|
||||
|
||||
**Complementary to ECS:**
|
||||
- Events handle irregular, one-time occurrences
|
||||
- Systems handle regular per-frame updates
|
||||
- Events enable cross-system messaging without tight coupling
|
||||
|
||||
**Event Bus Features:**
|
||||
- No `Clone` requirement on events (fire-and-forget)
|
||||
- `FnMut` handlers allow stateful callbacks
|
||||
- Global `add_listener()` and `emit()` functions
|
||||
- Handlers can access ECS components directly via storages
|
||||
|
||||
**ECS Integration:**
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
struct FootstepEvent { position: Vec3, force: f32 }
|
||||
impl Event for FootstepEvent {}
|
||||
|
||||
// System emits event
|
||||
pub fn foot_contact_system(world: &World) {
|
||||
for player in world.player_tags.all() {
|
||||
if is_on_ground(player) {
|
||||
let pos = world.transforms.get(player).unwrap().position;
|
||||
emit(&FootstepEvent { position: pos, force: 10.0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler (global listener, not part of World)
|
||||
add_listener(|event: &FootstepEvent| {
|
||||
snow_terrain::deform_at_position(event.position, event.force);
|
||||
});
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- ✅ One-time events (collision, death, pickup)
|
||||
- ✅ Cross-system communication (audio, VFX triggers)
|
||||
- ✅ Spawning/despawning entities
|
||||
- ❌ Regular updates (use systems instead)
|
||||
|
||||
### ECS Systems
|
||||
|
||||
**System Execution Order (main.rs game loop):**
|
||||
```rust
|
||||
let mut world = World::new();
|
||||
|
||||
'running: loop {
|
||||
// 1. SDL Events → InputState
|
||||
for event in event_pump.poll_iter() {
|
||||
input_state.handle_event(&event);
|
||||
}
|
||||
|
||||
// 2. InputState → InputComponent (player_input_system)
|
||||
player_input_system(&mut world, &input_state);
|
||||
|
||||
// 3. Update state machines (state_machine_system)
|
||||
state_machine_system(&mut world, delta);
|
||||
|
||||
// 4. Simulate physics (PhysicsManager)
|
||||
PhysicsManager::physics_step();
|
||||
|
||||
// 5. Sync physics → transforms (physics_sync_system)
|
||||
physics_sync_system(&mut world);
|
||||
|
||||
// 6. Render (render_system)
|
||||
let draw_calls = render_system(&world);
|
||||
render::render(&camera, &draw_calls, time);
|
||||
|
||||
// 7. Cleanup
|
||||
input_state.clear_just_pressed();
|
||||
}
|
||||
```
|
||||
|
||||
**System Patterns:**
|
||||
|
||||
**Query Pattern:**
|
||||
```rust
|
||||
pub fn my_system(world: &mut World) {
|
||||
let entities = world.my_storage.all(); // All entities with this component
|
||||
|
||||
for entity in entities {
|
||||
world.my_storage.with_mut(entity, |component| {
|
||||
// Process component
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Multi-Component Query:**
|
||||
```rust
|
||||
pub fn movement_system(world: &mut World) {
|
||||
for entity in world.player_tags.all() {
|
||||
if let Some(input) = world.inputs.get(entity) {
|
||||
if let Some(movement) = world.movements.get(entity) {
|
||||
world.transforms.with_mut(entity, |transform| {
|
||||
// Update position based on input + movement
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Movement System (movement.rs)
|
||||
Configuration and state for character movement physics:
|
||||
|
||||
**Horizontal Movement:**
|
||||
- `HorizontalMovementConfig`: Parameters for ground movement (acceleration, damping, speed limits)
|
||||
- `HorizontalMovementState`: Runtime state (input direction, flooring status, surface normal)
|
||||
- Uses Bezier curves (kurbo::CubicBez) for smooth acceleration ramps
|
||||
- Separate damping for walking vs idle states
|
||||
|
||||
**Vertical Movement:**
|
||||
- `VerticalMovementConfig`: Jump parameters (height, duration, air control)
|
||||
- `VerticalMovementState`: Jump execution tracking (progress, peak detection, abort state)
|
||||
- Bezier curves for jump height progression over time
|
||||
- Peak detection allows early jump termination with smooth falloff
|
||||
|
||||
**Key Features:**
|
||||
- Physics parameters tuned to match Godot prototype
|
||||
- Curve-based interpolation for responsive feel
|
||||
- State tracking for ground detection and jump execution
|
||||
- Configurable air control and momentum limits
|
||||
- Integration with Time singleton for execution timing
|
||||
|
||||
**Usage Pattern:**
|
||||
```rust
|
||||
let config = HorizontalMovementConfig::new();
|
||||
let mut state = HorizontalMovementState::new();
|
||||
|
||||
state.move_input = Vec3::new(input.x, 0.0, input.z);
|
||||
state.forward_direction = camera.forward();
|
||||
state.is_floored = ground_check.is_grounded;
|
||||
|
||||
// Apply movement physics using config + state
|
||||
```
|
||||
|
||||
### Time System (utility/time.rs)
|
||||
Global game time tracking using OnceLock singleton:
|
||||
|
||||
**Implementation:**
|
||||
```rust
|
||||
static GAME_START: OnceLock<Instant> = OnceLock::new();
|
||||
|
||||
pub struct Time;
|
||||
impl Time {
|
||||
pub fn init() { GAME_START.get_or_init(Instant::now); }
|
||||
pub fn get_time_elapsed() -> f32 { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Thread-safe singleton using std::sync::OnceLock
|
||||
- Single initialization point (call Time::init() at startup)
|
||||
- Returns elapsed time as f32 seconds
|
||||
- Used for animation, jump timing, and time-based effects
|
||||
- Zero-cost after initialization (static lookup)
|
||||
|
||||
**Usage:**
|
||||
```rust
|
||||
Time::init(); // In main() before game loop
|
||||
|
||||
let time = Time::get_time_elapsed(); // Anywhere in code
|
||||
```
|
||||
|
||||
## Current Implementation Status
|
||||
|
||||
### Implemented Features
|
||||
|
||||
**ECS Architecture:**
|
||||
- ✅ Full ECS conversion completed
|
||||
- ✅ Entity system with EntityManager (spawn/despawn/query)
|
||||
- ✅ Component storages (Transform, Mesh, Physics, Movement, Input, PlayerTag, StateMachine)
|
||||
- ✅ Systems pipeline (input → state machine → physics → physics sync → render)
|
||||
- ✅ No `Rc<RefCell<>>` - clean component ownership
|
||||
- ✅ Event bus integrated as complementary to systems
|
||||
|
||||
**Core Rendering:**
|
||||
- ✅ wgpu renderer with Vulkan backend
|
||||
- ✅ Low-res framebuffer (160×120) with Bayer dithering
|
||||
- ✅ Multiple render pipelines (standard mesh + terrain)
|
||||
- ✅ Directional lighting with diffuse + ambient
|
||||
- ✅ EXR heightmap loading and terrain displacement
|
||||
- ✅ glTF mesh loading
|
||||
- ✅ render_system (ECS-based DrawCall generation)
|
||||
|
||||
**Input System:**
|
||||
- ✅ Two-layer input pipeline (InputState → InputComponent)
|
||||
- ✅ player_input_system converts raw input to gameplay commands
|
||||
- ✅ SDL event handling in InputState
|
||||
- ✅ Per-entity InputComponent for controllable entities
|
||||
|
||||
**Camera & Debug:**
|
||||
- ✅ 3D camera with rotation (yaw/pitch)
|
||||
- ✅ Noclip mode for development (in debug/noclip.rs)
|
||||
- ✅ Mouse look with relative mouse mode
|
||||
- ✅ Toggle with 'I' key, 'N' for noclip mode
|
||||
|
||||
**Physics:**
|
||||
- ✅ rapier3d integration with PhysicsManager singleton
|
||||
- ✅ PhysicsComponent storage (rigidbody/collider handles)
|
||||
- ✅ physics_sync_system (syncs physics → transforms)
|
||||
- ✅ Physics step integrated into game loop
|
||||
- ⚠️ Ground detection not yet implemented
|
||||
- ⚠️ Movement physics not yet connected
|
||||
|
||||
**State Machines:**
|
||||
- ✅ Generic StateMachine implementation
|
||||
- ✅ StateMachineStorage (ECS component)
|
||||
- ✅ state_machine_system updates all state machines
|
||||
- ✅ Transitions can query ECS components
|
||||
- ⚠️ Player state transitions not yet configured
|
||||
|
||||
**Player:**
|
||||
- ✅ Player entity spawning function
|
||||
- ✅ Components: Transform, Mesh, Physics, Movement, Input, PlayerTag
|
||||
- ⚠️ Movement system not yet implemented
|
||||
- ⚠️ State machine not yet attached to player
|
||||
- ⚠️ Currently inactive (noclip camera used instead)
|
||||
|
||||
**Movement Configuration:**
|
||||
- ✅ Horizontal movement config (Bezier acceleration curves)
|
||||
- ✅ Vertical movement config (jump mechanics)
|
||||
- ✅ MovementComponent storage
|
||||
- ⚠️ Movement system not yet implemented
|
||||
- ⚠️ Not yet integrated with physics
|
||||
|
||||
### Not Yet Implemented
|
||||
|
||||
- ❌ Movement system (apply InputComponent → physics velocities)
|
||||
- ❌ Ground detection and collision response
|
||||
- ❌ Player state machine configuration
|
||||
- ❌ Camera follow behavior (tracks player entity)
|
||||
- ❌ Snow deformation compute shaders
|
||||
- ❌ Debug UI system
|
||||
|
||||
### Current Focus
|
||||
|
||||
**ECS migration is complete!** The architecture is now fully entity-component-system based with clean separation of data and logic. The next steps are:
|
||||
|
||||
1. Implement movement_system to apply InputComponent to physics
|
||||
2. Configure player state machine transitions
|
||||
3. Implement ground detection
|
||||
4. Add camera follow system
|
||||
5. Integrate snow deformation
|
||||
|
||||
The noclip camera mode serves as the primary navigation method for testing. Press 'N' to toggle noclip mode.
|
||||
Reference in New Issue
Block a user