Skip to content

Commit

Permalink
Merge pull request #62 from 64kramsystem/limit_players_distance
Browse files Browse the repository at this point in the history
Proper camera logic, and new player movement limitations
  • Loading branch information
odecay authored Jul 11, 2022
2 parents 3e00997 + 3a1f62b commit 38331b0
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 37 deletions.
3 changes: 3 additions & 0 deletions assets/default.game.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
start_level: beach/beach.level.yaml
camera_height: 448
# Distance of the rightmost player from the center of the camera, after which the camera moves.
# For simplicity, this value can also be used for movement limitation calculations.
camera_move_right_boundary: 150.

main_menu:
title_font:
Expand Down
23 changes: 16 additions & 7 deletions src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use bevy::{
use bevy_parallax::ParallaxMoveEvent;
use leafwing_input_manager::prelude::ActionState;

use crate::{consts, input::CameraAction, Player};
use crate::{consts, input::CameraAction, metadata::GameMeta, Player};

#[cfg_attr(feature = "debug", derive(bevy_inspector_egui::Inspectable))]
#[derive(Component)]
Expand Down Expand Up @@ -66,22 +66,31 @@ pub fn helper_camera_controller(
}
}

/// Moves the camera according to the RIGHT_BOUNDARY_DISTANCE. Note that this does not enforce
/// limitations of any kind - that's up to the players movement logic (e.g. max distance).
pub fn camera_follow_player(
player_query: Query<&Transform, With<Player>>,
mut camera_query: Query<(&mut Transform, &Panning), (With<Camera>, Without<Player>)>,
mut move_event_writer: EventWriter<ParallaxMoveEvent>,
game_meta: Res<GameMeta>,
) {
// TODO: Follow both players, not just the first one
if let Some(player) = player_query.iter().next() {
let (mut camera, panning) = camera_query.single_mut();
let max_player_x = player_query
.iter()
.map(|transform| transform.translation.x)
.max_by(|ax, bx| ax.total_cmp(bx))
.unwrap();

let diff = player.translation.x - (camera.translation.x - panning.offset.x);
let (mut camera, panning) = camera_query.single_mut();

camera.translation.x = player.translation.x + panning.offset.x;
let max_player_x_diff =
max_player_x - camera.translation.x - game_meta.camera_move_right_boundary;

if max_player_x_diff > 0. {
// The x axis is handled by the parallax plugin.
camera.translation.y = consts::GROUND_Y + panning.offset.y;

move_event_writer.send(ParallaxMoveEvent {
camera_move_speed: diff * consts::CAMERA_SPEED,
camera_move_speed: max_player_x_diff * consts::CAMERA_SPEED,
});
}
}
3 changes: 3 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ pub const PLAYER_SPRITE_HEIGHT: f32 = 80.;
pub const PLAYER_HITBOX_HEIGHT: f32 = 50.;

pub const PLAYER_HEIGHT: f32 = PLAYER_SPRITE_HEIGHT - 50.;
// Distance from the player, after which the player movement boundary is moved forward.
//
pub const LEFT_BOUNDARY_MAX_DISTANCE: f32 = 380.;

pub const GROUND_Y: f32 = -120.;
pub const GROUND_HEIGHT: f32 = 150.;
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ fn main() {
.add_plugin(ParallaxPlugin)
.add_plugin(UIPlugin)
.insert_resource(ParallaxResource::default())
.insert_resource(LeftMovementBoundary::default())
.add_system(game_init::load_game.run_in_state(GameState::LoadingGame))
.add_system(load_level.run_in_state(GameState::LoadingLevel))
.add_system_set(
Expand Down Expand Up @@ -270,6 +271,7 @@ fn main() {
.with_system(move_in_arc_system)
.with_system(rotate_system)
.with_system(camera_follow_player)
.with_system(update_left_movement_boundary)
.into(),
)
.add_system_to_stage(CoreStage::Last, despawn_entities);
Expand Down
1 change: 1 addition & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct GameMeta {
pub main_menu: MainMenuMeta,
pub ui_theme: UIThemeMeta,
pub camera_height: u32,
pub camera_move_right_boundary: f32,

pub default_input_maps: InputMapsMeta,
pub translations: TranslationsMeta,
Expand Down
146 changes: 116 additions & 30 deletions src/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ use bevy::{
core::{Time, Timer},
math::{Quat, Vec2, Vec3Swizzles},
prelude::{
Commands, Component, Deref, DerefMut, Entity, EventWriter, Query, Res, Transform, With,
Commands, Component, Deref, DerefMut, Entity, EventWriter, Query, Res, ResMut, Transform,
With,
},
};
use leafwing_input_manager::prelude::ActionState;

use crate::{
animation::Facing, consts, input::PlayerAction, item::ThrowItemEvent, state::State,
animation::Facing,
consts::{self, LEFT_BOUNDARY_MAX_DISTANCE},
input::PlayerAction,
item::ThrowItemEvent,
metadata::GameMeta,
state::State,
ArrivedEvent, DespawnMarker, Player, Stats,
};

#[cfg_attr(feature = "debug", derive(bevy_inspector_egui::Inspectable))]
#[derive(Component, Deref, DerefMut)]
pub struct MoveInDirection(pub Vec2);

// (Moving) bondary before which, the players can't go back.
#[derive(Component)]
pub struct LeftMovementBoundary(f32);

impl Default for LeftMovementBoundary {
fn default() -> Self {
Self(-LEFT_BOUNDARY_MAX_DISTANCE)
}
}

#[derive(Component)]
pub struct Knockback {
pub direction: Vec2,
Expand Down Expand Up @@ -50,45 +66,99 @@ pub fn player_controller(
With<Player>,
>,
time: Res<Time>,
game_meta: Res<GameMeta>,
left_movement_boundary: Res<LeftMovementBoundary>,
) {
for (mut state, stats, mut transform, facing_option, input) in query.iter_mut() {
if *state != State::Idle && *state != State::Running {
break;
}
let players_x = query
.iter()
.map(|(_, _, transform, _, _)| transform.translation.x)
.collect::<Vec<_>>();

let mut dir = Vec2::ZERO;
// Compute the new direction vectors; can be None if the state is not (idle or running).
//
let mut player_dirs = query
.iter()
.map(|(state, stats, transform, _, input)| {
if *state != State::Idle && *state != State::Running {
None
} else {
let mut dir = Vec2::ZERO;

if input.pressed(PlayerAction::Move) {
dir = input.action_axis_pair(PlayerAction::Move).unwrap().xy();
}
if input.pressed(PlayerAction::Move) {
dir = input.action_axis_pair(PlayerAction::Move).unwrap().xy();
}

// Apply speed
dir = dir * stats.movement_speed * time.delta_seconds();
// Apply speed
dir = dir * stats.movement_speed * time.delta_seconds();

//Restrict player to the ground
let new_y = transform.translation.y + dir.y + consts::GROUND_OFFSET;
let new_x = transform.translation.x + dir.x;

if new_y >= consts::MAX_Y || new_y <= consts::MIN_Y {
dir.y = 0.;
}
// The dir.x condition allows some flexibility (e.g. in case of knockback), given
// the current state of development. To be removed once the movement logic is
// stabilized.
//
if dir.x < 0. && new_x < left_movement_boundary.0 {
dir.x = 0.;
}

//Restrict player to the ground
let new_y = transform.translation.y + dir.y + consts::GROUND_OFFSET;

if new_y >= consts::MAX_Y || new_y <= consts::MIN_Y {
dir.y = 0.;
}

//Move the player
Some(dir)
}
})
.collect::<Vec<_>>();

//Move the player
transform.translation.x += dir.x;
transform.translation.y += dir.y;
if player_dirs.len() > 1 {
let max_players_x_distance =
LEFT_BOUNDARY_MAX_DISTANCE + game_meta.camera_move_right_boundary;

//Set the player state and direction
if let Some(mut facing) = facing_option {
if dir.x < 0. {
facing.set(Facing::Left);
} else if dir.x > 0. {
facing.set(Facing::Right);
let new_players_x = players_x
.iter()
.zip(player_dirs.iter())
.map(|(x, dir)| x + dir.unwrap_or(Vec2::ZERO).x)
.collect::<Vec<_>>();

let min_player_x = new_players_x
.iter()
.min_by(|ax, bx| ax.total_cmp(bx))
.unwrap();

for (player_dir, player_x) in player_dirs.iter_mut().zip(new_players_x.iter()) {
if let Some(player_dir) = player_dir.as_mut() {
if *player_x > min_player_x + max_players_x_distance {
*player_dir = Vec2::ZERO;
}
}
}
}

if dir == Vec2::ZERO {
state.set(State::Idle);
} else {
state.set(State::Running);
for ((mut state, _, mut transform, facing_option, _), dir) in
query.iter_mut().zip(player_dirs.iter())
{
if let Some(dir) = dir {
transform.translation.x += dir.x;
transform.translation.y += dir.y;

//Set the player state and direction
if let Some(mut facing) = facing_option {
if dir.x < 0. {
facing.set(Facing::Left);
} else if dir.x > 0. {
facing.set(Facing::Right);
}
}

if dir == &Vec2::ZERO {
state.set(State::Idle);
} else {
state.set(State::Running);
}
}
}
}
Expand Down Expand Up @@ -236,3 +306,19 @@ pub fn move_to_target(
}
}
}

pub fn update_left_movement_boundary(
query: Query<&Transform, With<Player>>,
mut boundary: ResMut<LeftMovementBoundary>,
game_meta: Res<GameMeta>,
) {
let max_player_x = query
.iter()
.map(|transform| transform.translation.x)
.max_by(|ax, bx| ax.total_cmp(bx))
.unwrap();

boundary.0 = boundary
.0
.max(max_player_x - game_meta.camera_move_right_boundary - LEFT_BOUNDARY_MAX_DISTANCE);
}

0 comments on commit 38331b0

Please sign in to comment.