Files
sailing/src/main.rs
2025-11-28 16:25:37 -05:00

167 lines
3.9 KiB
Rust

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<ClearColor>, mut commands: Commands) {
*clear_color = ClearColor(Color::linear_rgb(0.1, 0.4, 0.7));
commands.spawn(Camera2d);
}
fn sprite_setup(assets: Res<AssetServer>, 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<ButtonInput<KeyCode>>) {
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,
);
}