Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Physics Based Dragon Movement #70

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/game/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ pub use constants::*;
pub use enemy::Enemy;
pub use fire_breath::SpawnFireBreathEvent;
pub use level::{BorderTile, Tile};
pub use player::Player;
pub use player::{Player, PlayerMovementEvent};
pub use plugin::GamePlugin;
80 changes: 78 additions & 2 deletions src/game/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
animation::{AnimationIndices, AnimationTimer},
camera::{RenderLayer, YSorted},
physics::Speed,
AppState,
playing, AppState,
};

use super::{
Expand All @@ -18,7 +18,11 @@ pub(super) struct PlayerPlugin;

impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.add_event::<PlayerMovementEvent>();

app.add_systems(OnEnter(AppState::InGame), spawn_player);

app.add_systems(FixedUpdate, handle_player_movement_events.run_if(playing()));
}
}

Expand All @@ -31,8 +35,11 @@ pub struct PlayerBundle {
pub fire_breath_resource: ResourcePool<Fire>,
pub hitpoints: ResourcePool<Health>,
pub score: Score,
pub speed: Speed,
pub damping: Damping,
pub external_impulse: ExternalImpulse,
pub marker: Player,
pub rigid_body: RigidBody,
pub speed: Speed,
pub render_layers: RenderLayers,
pub spritesheet: SpriteSheetBundle,
}
Expand All @@ -59,6 +66,9 @@ fn spawn_player(mut commands: Commands, asset_server: Res<AssetServer>) {
marker: Player,
render_layers: RenderLayers::layer(RenderLayer::Sky.into()),
speed: Speed(10.),
damping: Damping::default(),
external_impulse: ExternalImpulse::default(),
rigid_body: RigidBody::Dynamic,
spritesheet: SpriteSheetBundle {
atlas: TextureAtlas {
layout: texture_atlas_layout_handle,
Expand All @@ -72,3 +82,69 @@ fn spawn_player(mut commands: Commands, asset_server: Res<AssetServer>) {

player_entity_commands.insert((InGameEntity, YSorted));
}

#[derive(Event)]
pub enum PlayerMovementEvent {
Accelerate { target: Vec2 },
Brake,
}

impl PlayerMovementEvent {
pub fn accelerate(target: Vec2) -> Self {
Self::Accelerate { target }
}

pub fn brake() -> Self {
Self::Brake
}
}

fn handle_player_movement_events(
mut player_movement_event_reader: EventReader<PlayerMovementEvent>,
mut query: Query<(&Transform, &mut ExternalImpulse, &mut Damping), With<Player>>,
) {
let (transform, mut external_impulse, mut damping) = query.single_mut();

for event in player_movement_event_reader.read() {
match event {
&PlayerMovementEvent::Accelerate { target } => {
let player_position = transform.translation.truncate();
let target_to_player_vector = target - player_position;

if target_to_player_vector == Vec2::ZERO {
continue;
}

let target_distance_to_player = target.distance(player_position);
let velocity_scalar = target_distance_to_player.min(300.) / 300.;
let direction = transform.rotation.mul_vec3(Vec3::Y).truncate();
let angle_with_cursor =
direction.angle_between(target_to_player_vector.normalize());
let is_facing_forward = (-0.4..0.4).contains(&angle_with_cursor);
let is_in_cruise_mode = (-0.2..0.2).contains(&angle_with_cursor);
let angle = target_to_player_vector.angle_between(direction);

external_impulse.impulse = direction * velocity_scalar * 125.;

if is_facing_forward {
damping.angular_damping = 3.;
external_impulse.torque_impulse = 0.;
external_impulse.impulse *= 2.;
if is_in_cruise_mode {
damping.angular_damping = 5.;
}
} else {
damping.angular_damping = 0.5;
external_impulse.torque_impulse = -angle * 2.;
damping.linear_damping = 1.5;
}
}
PlayerMovementEvent::Brake => {
*damping = Damping {
angular_damping: 25.,
linear_damping: 25.,
};
}
}
}
}
53 changes: 9 additions & 44 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::{f32::consts::FRAC_PI_2, time::Duration};

use bevy::{ecs::system::SystemParam, prelude::*, window::PrimaryWindow};
use bevy_rapier2d::prelude::Collider;

use crate::{
animation::AnimationTimer, camera::MainCamera, game::Player, game::SpawnFireBreathEvent,
physics::Speed, playing, AppState,
camera::MainCamera, game::Player, game::PlayerMovementEvent, game::SpawnFireBreathEvent,
playing, AppState,
};

pub struct InputPlugin;
Expand All @@ -16,7 +13,7 @@ impl Plugin for InputPlugin {
PreUpdate,
(
clear_input.run_if(state_changed::<AppState>),
(mouse_input, player_movement).run_if(playing()),
mouse_input.run_if(playing()),
)
.chain(),
);
Expand Down Expand Up @@ -54,11 +51,17 @@ struct FireBreathSfx;
fn mouse_input(
mut commands: Commands,
mut spawn_fire_breath_event_writer: EventWriter<SpawnFireBreathEvent>,
mut player_movement_event_writer: EventWriter<PlayerMovementEvent>,
cursor_world_position_checker: CursorWorldPositionChecker,
query: Query<&Transform, With<Player>>,
sfx_query: Query<Entity, With<FireBreathSfx>>,
asset_server: Res<AssetServer>,
mouse_input: ResMut<ButtonInput<MouseButton>>,
) {
if let Some(cursor_position) = cursor_world_position_checker.cursor_world_position() {
player_movement_event_writer.send(PlayerMovementEvent::accelerate(cursor_position));
}

if mouse_input.just_pressed(MouseButton::Left) {
commands.spawn((
AudioBundle {
Expand Down Expand Up @@ -103,44 +106,6 @@ fn mouse_input(
}
}

fn player_movement(
mut query: Query<(&mut Transform, &Speed, &mut AnimationTimer, &Collider), With<Player>>,
cursor_world_position_checker: CursorWorldPositionChecker,
) {
if let Some(cursor_position) = cursor_world_position_checker.cursor_world_position() {
let (mut player_transform, player_speed, mut player_animation_timer, player_collider) =
query.single_mut();
let player_position = player_transform.translation.truncate();
let cursor_to_player_vector = cursor_position - player_position;

if cursor_to_player_vector != Vec2::ZERO {
let cursor_distance_to_player = cursor_position.distance(player_position);
let velocity_rate = cursor_distance_to_player.min(300.) / 300.;
let direction = cursor_to_player_vector.normalize();

if cursor_distance_to_player > player_collider.as_cuboid().unwrap().half_extents().y {
player_transform.translation.x += direction.x * player_speed.0 * velocity_rate;
player_transform.translation.y += direction.y * player_speed.0 * velocity_rate;
player_animation_timer.set_duration(Duration::from_secs_f32(
0.2 * player_speed.0 * 0.25 * velocity_rate,
));
} else {
player_animation_timer.set_duration(Duration::from_secs_f32(0.2));
}

if direction != Vec2::ZERO {
let angle = (direction).angle_between(Vec2::X);

if angle.is_finite() {
// FIXME: Rotate the image sprite to always face right?
// FRAC_PI_2 is subtracted to offset the 90 degree rotation from the X axis the sprite has.
player_transform.rotation = Quat::from_rotation_z(-angle - FRAC_PI_2);
}
}
}
}
}

fn clear_input(
mut keyboard_input: ResMut<ButtonInput<KeyCode>>,
mut mouse_input: ResMut<ButtonInput<MouseButton>>,
Expand Down