feat(gameplay): implement in-system gameplay with camera modes and flight controls

Add comprehensive in-system gameplay features including:

Camera System:
- Orbit mode for galaxy/inspection views
- Follow mode for tracking player ship during flight
- Cinematic mode for docked/cutscene views
- Smooth interpolation and orbit controls

In-System Gameplay:
- Docked state at stations with undock functionality
- Flight mode with velocity and target-based navigation
- Point-and-click movement via ground plane projection
- Target selection system for POIs

Flight Controls:
- Flight state tracking with speed monitoring
- Automatic camera transitions between modes
- Flight HUD with speed indicator and status panel
- Contextual action system for approach/dock/mining

UI Updates:
- Docked station panel with system information
- Flight mode controls and hints
- Dynamic population display

This implementation provides the foundation for tactical
space gameplay with smooth camera transitions and
intuitive point-and-click navigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 20:02:19 -04:00
parent 07316dbcd7
commit 98c2ba59df
14 changed files with 1338 additions and 43 deletions

View File

@@ -0,0 +1,174 @@
//! Flight state and controls for in-system gameplay.
//!
//! Handles the transition from docked to active flight mode, including
//! ship controls, camera transitions, and flight state management.
//!
//! Navigation is point-and-click tactical mode: select targets and click
//! "Approach" to auto-navigate. No WASD controls.
use bevy::prelude::*;
use crate::camera::{CameraState, CameraMode};
use crate::gameplay::movement::components::{Velocity, MoveTarget};
use crate::gameplay::galaxy::Identifiable;
use super::{DockedState, UndockEvent};
use super::scene::{Docked, PlayerShip};
use super::flight_ui::setup_flight_ui;
/// Flight state component attached to the player ship when actively flying.
#[derive(Component, Debug, Clone, Default)]
pub struct FlightState {
pub is_flying: bool,
pub current_speed: f32,
}
/// Event fired when the player docks at a station.
#[derive(Event, Debug, Clone)]
pub struct DockEvent {
pub ship: Entity,
pub station: Entity,
}
/// Plugin for flight controls and state management.
pub struct FlightControlsPlugin;
impl Plugin for FlightControlsPlugin {
fn build(&self, app: &mut App) {
app.add_event::<DockEvent>()
.add_systems(
Update,
(
handle_undock,
handle_docking,
flight_input_system,
).run_if(in_state(crate::state::AppState::InGame)),
);
}
}
/// Handle undock event: transition from docked to flight mode.
fn handle_undock(
mut commands: Commands,
mut events: EventReader<UndockEvent>,
mut docked_state: ResMut<DockedState>,
mut camera_state: ResMut<CameraState>,
player_query: Query<(Entity, &Transform), (With<PlayerShip>, With<Docked>)>,
docked_ui_query: Query<Entity, With<super::ui::DockedUi>>,
) {
for event in events.read() {
bevy::log::info!("Handling undock from station {:?}", event.station_entity);
// Find player ship
let Ok((player_entity, ship_transform)) = player_query.single() else {
bevy::log::warn!("No docked player ship found");
continue;
};
// Remove docked component
commands.entity(player_entity).remove::<Docked>();
// Add flight state with initial velocity away from station
let initial_speed = 10.0; // Start with slow drift
commands.entity(player_entity).insert((
FlightState {
is_flying: true,
current_speed: initial_speed,
},
Velocity(Vec3::new(0.0, 0.0, -1.0) * initial_speed),
MoveTarget(Vec3::new(0.0, 0.0, -50.0)), // Initial target away from station
super::SelectedTarget {
entity: Entity::PLACEHOLDER,
kind: super::TargetKind::Manual,
name: "None".to_string(),
},
));
// Update docked state resource
docked_state.undock();
// Transition camera to follow mode
camera_state.mode = CameraMode::Follow;
camera_state.target_entity = Some(player_entity);
camera_state.follow_distance = 15.0;
camera_state.follow_height = 5.0;
// Spawn flight HUD
setup_flight_ui(commands.reborrow());
// Despawn docked UI
for entity in docked_ui_query.iter() {
commands.entity(entity).despawn();
}
bevy::log::info!("Transitioned to flight mode");
}
}
/// Handle docking event: transition from flight to docked mode.
fn handle_docking(
mut commands: Commands,
mut events: EventReader<DockEvent>,
mut docked_state: ResMut<DockedState>,
mut camera_state: ResMut<CameraState>,
identifiable_query: Query<&Identifiable>,
flight_ui_query: Query<Entity, With<super::flight_ui::FlightUi>>,
) {
for event in events.read() {
bevy::log::info!("Handling docking at target {:?}", event.station);
let Ok(identifiable) = identifiable_query.get(event.station) else {
bevy::log::warn!("Docking target has no Identifiable component");
continue;
};
// Add docked component
commands.entity(event.ship).insert(Docked {
station_entity: event.station,
});
// Remove flight state
commands.entity(event.ship)
.remove::<FlightState>();
// Update docked state resource
docked_state.dock_at(event.station);
// Transition camera to cinematic mode
camera_state.mode = CameraMode::Cinematic;
camera_state.target_entity = Some(event.station);
// Despawn flight HUD
for entity in flight_ui_query.iter() {
commands.entity(entity).despawn();
}
// Respawn docked UI
super::ui::setup_docked_ui(commands.reborrow());
bevy::log::info!("Docked at {}", identifiable.display_name);
}
}
/// Flight input system: point-and-click navigation only.
///
/// This system handles updating the flight state based on movement.
/// Actual navigation is handled by click-to-move (setting MoveTarget)
/// and the kinematic systems (steer_to_target, integrate_velocity).
///
/// This is a minimal system - the real action happens in the movement systems.
fn flight_input_system(
mut player_query: Query<(&Velocity, &mut FlightState), With<PlayerShip>>,
docked_state: Res<DockedState>,
) {
// Only process flight input when not docked
if docked_state.is_docked {
return;
}
let Ok((velocity, mut flight_state)) = player_query.single_mut() else {
return;
};
// Update current speed from actual velocity
flight_state.current_speed = velocity.0.length();
}