use bevy::{prelude::*, sprite_render::Wireframe2dPlugin}; const MAX_SAILING_SPEED: f32 = 0.9; const MAX_TURN_SPEED: f32 = 0.1; #[derive(Component, Default)] struct EntityPhysics { x: f32, y: f32, speed: f32, turn_speed: f32, heading: f32, } #[derive(Component, Default)] struct InputState { x: f32, y: f32, } #[derive(Component, Default)] struct SailboatState { sail_extension: f32, rudder_dir: f32, } fn main() { let mut app = App::new(); app.add_plugins((DefaultPlugins, Wireframe2dPlugin::default())) .add_systems(Startup, (hello_world, camera_setup, sprite_setup).chain()) .add_systems(Update, (handle_keys, player_physics).chain()); app.run(); } fn hello_world() { println!("Hello, world!"); } fn camera_setup(mut clear_color: ResMut, mut commands: Commands) { *clear_color = ClearColor(Color::linear_rgb(0.1, 0.4, 0.7)); commands.spawn(Camera2d); } fn sprite_setup(assets: Res, mut commands: Commands) { let ship_sprite = assets.load("sprite-0001.png"); let sail_sprite = assets.load("sail.png"); commands .spawn(( Sprite { image: ship_sprite, ..default() }, EntityPhysics::default(), InputState::default(), SailboatState::default(), )) .with_child(( Sprite { image: sail_sprite, ..default() }, Transform { translation: Vec3 { x: 8.0, y: 1.0, z: 0., }, ..default() }, )); } fn handle_keys(mut q_player: Query<&mut InputState>, keys: Res>) { if keys.just_pressed(KeyCode::KeyQ) { info!("Quit."); } let Ok(mut input) = q_player.single_mut() else { return; }; if keys.pressed(KeyCode::ArrowUp) { input.y = 1.; } else if keys.pressed(KeyCode::ArrowDown) { input.y = -1.; } else { input.y = 0.; } if keys.pressed(KeyCode::ArrowRight) { input.x = 1.; } else if keys.pressed(KeyCode::ArrowLeft) { input.x = -1.; } else { input.x = 0.; } } fn player_physics( mut q_player: Query<( &InputState, &mut SailboatState, &mut EntityPhysics, &mut Transform, )>, ) { let Ok((input, mut boat_state, mut physics, mut transform)) = q_player.single_mut() else { return; }; if input.x < 0. { boat_state.rudder_dir = (-1.0 + boat_state.rudder_dir) * 0.5; } else if input.x > 0. { boat_state.rudder_dir = (1.0 + boat_state.rudder_dir) * 0.5; } else { boat_state.rudder_dir *= 0.5; } physics.turn_speed *= 0.1; physics.turn_speed += boat_state.rudder_dir * (physics.speed.abs() / MAX_SAILING_SPEED); physics.turn_speed = physics.turn_speed.clamp(-MAX_TURN_SPEED, MAX_TURN_SPEED); physics.heading += physics.turn_speed * 0.1; if input.y > 0. { boat_state.sail_extension = (1.0 + boat_state.sail_extension) * 0.5; } else if input.y < 0. { boat_state.sail_extension *= 0.5; } boat_state.sail_extension = boat_state.sail_extension.clamp(0., 1.); physics.speed *= 0.99; if boat_state.sail_extension > 0. { physics.speed += 0.01 * boat_state.sail_extension; } physics.speed = physics.speed.clamp(-MAX_SAILING_SPEED, MAX_SAILING_SPEED); let x_speed = physics.heading.sin() * physics.speed; let y_speed = physics.heading.cos() * physics.speed; physics.x += x_speed; physics.y += y_speed; transform.translation = Vec3 { x: physics.x as f32, y: physics.y as f32, z: 0., }; transform.rotation = Quat::from_axis_angle( Vec3 { x: 0., y: 0., z: -1., }, physics.heading, ); }