Skip to content

Commit

Permalink
feat: physics collisions with rapier
Browse files Browse the repository at this point in the history
  • Loading branch information
nwesterhausen committed Feb 16, 2024
1 parent 0b1ad53 commit 05baae2
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 85 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/rustdoc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ A listing of the open source libraries and resources used in this project:
- Bevy Libraries (for debugging/development)
- [egui_doc](https://crates.io/crates/egui_dock) (MIT)
- [bevy-inspector-egui](https://crates.io/crates/bevy-inspector-egui) (MIT)
- [bevy_rapier2d](https://crates.io/crates/bevy_rapier2d) (Apache-2.0)
- Other rust dependencies
- [tracing](https://crates.io/crates/tracing) (MIT)
- [embed-resource](https://crates.io/crates/embed-resource) (MIT)
Expand Down
12 changes: 8 additions & 4 deletions game/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use bevy::{
render::{render_resource::WgpuFeatures, settings::WgpuSettings, RenderPlugin},
utils::HashMap,
};
use bevy_rapier2d::prelude::*;
use game_library::{
data_loader::storage::GameData, enums::biome::Biome, state::Game, GeneratedMaps,
MarkersToBiomes, NoisePlugin, SchedulingPlugin,
MarkersToBiomes, NoisePlugin, PhysicsPlugin, SchedulingPlugin,
};
use in_game::InGamePlugin;
use leafwing_input_manager::plugin::InputManagerPlugin;
Expand Down Expand Up @@ -126,6 +127,8 @@ impl Plugin for ElementalistDefaultPlugins {
app.add_plugins(camera::CameraPlugin);
// Add the schedule plugin
app.add_plugins(SchedulingPlugin);
// Add the physics plugin
app.add_plugins(PhysicsPlugin);
}
}

Expand Down Expand Up @@ -248,9 +251,7 @@ fn spawn_random_environment(
if let Some(obj) = obj {
let mut obj_transform =
Transform::from_translation(generated_map.map_to_world((i, j)));
#[allow(clippy::cast_precision_loss)]
let z_val = obj_transform.translation.y / -10.0;
obj_transform.translation.z = z_val;
obj_transform.translation.z = -obj_transform.translation.y;
obj_transform.scale = Vec3::splat(1.0);
commands.spawn((
SpriteSheetBundle {
Expand All @@ -260,6 +261,9 @@ fn spawn_random_environment(
..Default::default()
},
EnvironmentStuff,
RigidBody::Fixed,
// todo: make this reference the actual size of the sprite
Collider::cuboid(6.0, 4.0),
));
}
}
Expand Down
82 changes: 8 additions & 74 deletions game/src/player/avatar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use game_library::{
colors,
data_loader::storage::GameData,
Expand All @@ -7,80 +8,10 @@ use game_library::{
Health, Mana, MovementBundle, SpellChoices, StatBundle, Xp,
};

/// Base stats for the player. These are the stats that the player starts with, and are used to
/// initiate the [`StatBundle`] for the player.
const fn player_base_stats(stat: &StatEnum) -> f32 {
match stat {
StatEnum::MovementSpeed => 100.0,
StatEnum::SpellRange => 50.0,
StatEnum::Health => 10.0,
StatEnum::Mana => 4.0,
StatEnum::ManaRegeneration
| StatEnum::ProjectileSpeed
| StatEnum::ProjectileSize
| StatEnum::ProjectileLifetime
| StatEnum::MagicDamage => 1.0,
StatEnum::HealthRegeneration => 0.05,
StatEnum::DamageReduction
| StatEnum::DamageResistance
| StatEnum::DamageReflection
| StatEnum::DamageAmplification
| StatEnum::CriticalStrikeChance
| StatEnum::CriticalStrikeDamage
| StatEnum::LifeSteal
| StatEnum::ManaSteal
| StatEnum::StunResistance
| StatEnum::DodgeChance
| StatEnum::AttackDamage
| StatEnum::AttackSpeed
| StatEnum::AttackRange
| StatEnum::PhysicalDamageReduction
| StatEnum::PhysicalDamageResistance
| StatEnum::PhysicalDamageReflection
| StatEnum::PhysicalDamageAmplification
| StatEnum::CooldownReduction
| StatEnum::MagicalDamageReduction
| StatEnum::MagicalDamageResistance
| StatEnum::MagicalDamageReflection
| StatEnum::MagicalDamageAmplification => 0.0,
}
}

/// Base health for the player. Specified outside of `base_stats` because it's an integer.
const BASE_HEALTH: u32 = 10;
/// Base mana for the player. Specified outside of `base_stats` because it's an integer.
const BASE_MANA: u32 = 4;

/// Player is a marker component for the player. This is used to identify the player's entity
/// in queries and systems. This should be used instead of using the `PlayerAvatar` component
/// directly, as it allows for more flexibility in the future.
#[derive(Component, Debug, Reflect)]
pub struct Player;

/// `PlayerBundle` is a bundle of components that are used to create the player's entity. This is
/// used to spawn the player's entity in the game. They are a set of components that are integral
/// to the player and we want to ensure that they are always present when the player is spawned.
///
/// These are specifically related to spawning the [`PlayerAvatar`], and are used to track the
/// player's health, mana, stats, and experience points while in a game run. The overall player
/// skills and unlocked spells are tracked in another resource (`todo!()`).
#[derive(Bundle)]
pub struct PlayerBundle {
/// The player's movement bundle.
pub movement: MovementBundle,
/// The player's sprite bundle.
pub sprite: SpriteBundle,
/// The player's health.
pub health: Health,
/// The player's mana.
pub mana: Mana,
/// The player's stats.
pub stats: StatBundle,
/// The player's experience points.
pub xp: Xp,
/// Player marker component.
pub player: Player,
}
use super::{
bundle::{player_base_stats, PlayerBundle, BASE_HEALTH, BASE_MANA},
Player,
};

/// `PlayerAvatar` is a marker component for the player's avatar, i.e. the entity that the player
/// controls. This is used to ensure that only one player avatar is spawned at a time. It also
Expand Down Expand Up @@ -141,6 +72,9 @@ pub fn spawn_player_avatar(
),
xp: Xp::default(),
player: Player,
kinematic_controller: KinematicCharacterController::default(),
collider: Collider::capsule_y(6.0, 4.0),
rigid_body: RigidBody::KinematicVelocityBased,
},
ProgressBarConfig::<Xp>::default()
.with_background_color(colors::BACKGROUND_COLOR_50)
Expand Down
84 changes: 84 additions & 0 deletions game/src/player/bundle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use game_library::{enums::StatEnum, Health, Mana, MovementBundle, StatBundle, Xp};

/// Base stats for the player. These are the stats that the player starts with, and are used to
/// initiate the [`StatBundle`] for the player.
pub(super) const fn player_base_stats(stat: &StatEnum) -> f32 {
match stat {
StatEnum::MovementSpeed => 100.0,
StatEnum::SpellRange => 50.0,
StatEnum::Health => 10.0,
StatEnum::Mana => 4.0,
StatEnum::ManaRegeneration
| StatEnum::ProjectileSpeed
| StatEnum::ProjectileSize
| StatEnum::ProjectileLifetime
| StatEnum::MagicDamage => 1.0,
StatEnum::HealthRegeneration => 0.05,
StatEnum::DamageReduction
| StatEnum::DamageResistance
| StatEnum::DamageReflection
| StatEnum::DamageAmplification
| StatEnum::CriticalStrikeChance
| StatEnum::CriticalStrikeDamage
| StatEnum::LifeSteal
| StatEnum::ManaSteal
| StatEnum::StunResistance
| StatEnum::DodgeChance
| StatEnum::AttackDamage
| StatEnum::AttackSpeed
| StatEnum::AttackRange
| StatEnum::PhysicalDamageReduction
| StatEnum::PhysicalDamageResistance
| StatEnum::PhysicalDamageReflection
| StatEnum::PhysicalDamageAmplification
| StatEnum::CooldownReduction
| StatEnum::MagicalDamageReduction
| StatEnum::MagicalDamageResistance
| StatEnum::MagicalDamageReflection
| StatEnum::MagicalDamageAmplification => 0.0,
}
}

/// Base health for the player. Specified outside of `base_stats` because it's an integer.
pub(super) const BASE_HEALTH: u32 = 10;
/// Base mana for the player. Specified outside of `base_stats` because it's an integer.
pub(super) const BASE_MANA: u32 = 4;

/// Player is a marker component for the player. This is used to identify the player's entity
/// in queries and systems. This should be used instead of using the `PlayerAvatar` component
/// directly, as it allows for more flexibility in the future.
#[derive(Component, Debug, Reflect)]
pub struct Player;

/// `PlayerBundle` is a bundle of components that are used to create the player's entity. This is
/// used to spawn the player's entity in the game. They are a set of components that are integral
/// to the player and we want to ensure that they are always present when the player is spawned.
///
/// These are specifically related to spawning the [`PlayerAvatar`], and are used to track the
/// player's health, mana, stats, and experience points while in a game run. The overall player
/// skills and unlocked spells are tracked in another resource (`todo!()`).
#[derive(Bundle)]
pub struct PlayerBundle {
/// The player's movement bundle.
pub movement: MovementBundle,
/// The player's sprite bundle.
pub sprite: SpriteBundle,
/// The player's health.
pub health: Health,
/// The player's mana.
pub mana: Mana,
/// The player's stats.
pub stats: StatBundle,
/// The player's experience points.
pub xp: Xp,
/// Player marker component.
pub player: Player,
/// Player controller marker component (rapier)
pub kinematic_controller: KinematicCharacterController,
/// Physics collider
pub collider: Collider,
/// Physics rigid body
pub rigid_body: RigidBody,
}
3 changes: 2 additions & 1 deletion game/src/player/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
//! player's avatar.

mod avatar;
mod bundle;
mod menu_control;
mod movement;
mod player_control;
mod player_creation;
mod player_sprite;
mod plugin;

pub use avatar::Player;
pub use bundle::Player;
pub use plugin::PlayerPlugin;
9 changes: 9 additions & 0 deletions game/src/player/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use leafwing_input_manager::action_state::ActionState;

use crate::{events::PlayerAction, player::Player};

use super::avatar::PlayerAvatar;

/// Handle player input for movement
pub fn player_movement_controls(
mut query: Query<&mut Velocity, With<Player>>,
Expand Down Expand Up @@ -41,3 +43,10 @@ pub fn player_movement_controls(
}
}
}

/// Plugin that adjust the player's z-index based on their y position
pub(super) fn update_player_z_index(mut query: Query<&mut Transform, With<PlayerAvatar>>) {
for mut transform in &mut query {
transform.translation.z = -transform.translation.y;
}
}
6 changes: 5 additions & 1 deletion game/src/player/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ impl Plugin for PlayerPlugin {
.add_systems(OnEnter(Game::Playing), avatar::spawn_player_avatar)
.add_systems(
Update,
(movement::player_movement_controls).run_if(in_state(Game::Playing)),
(
movement::player_movement_controls,
movement::update_player_z_index,
)
.run_if(in_state(Game::Playing)),
)
// Remove player when leaving game
.add_systems(OnExit(Game::Playing), despawn_with_tag::<PlayerAvatar>);
Expand Down
16 changes: 11 additions & 5 deletions game/src/resources/movement.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::prelude::*;
use bevy_rapier2d::control::KinematicCharacterController;
use game_library::{
state::{Game, Overlay},
Acceleration, Velocity,
Expand All @@ -24,10 +25,15 @@ fn update_velocity(mut query: Query<(&mut Velocity, &Acceleration)>, time: Res<T
}
}

/// System that updates the position of moving things
fn update_position(mut query: Query<(&mut Transform, &Velocity)>, time: Res<Time>) {
for (mut transform, velocity) in &mut query {
transform.translation.x += velocity.value.x * time.delta_seconds();
transform.translation.y += velocity.value.y * time.delta_seconds();
/// System that updates the position of moving things (via physics)
fn update_position(
mut query: Query<(&mut KinematicCharacterController, &Velocity)>,
time: Res<Time>,
) {
for (mut controller, velocity) in &mut query {
controller.translation = Some(Vec2::new(
velocity.value.x * time.delta_seconds(),
velocity.value.y * time.delta_seconds(),
));
}
}
1 change: 1 addition & 0 deletions game_library/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde_default_utils = "0.2.1"
serde_yaml = "0.9.31"
tracing = "0.1.40"
walkdir = "2.4.0"
bevy_rapier2d = { version = "0.24.0", features = ["parallel"] }

[dependencies.rand]
version = "0.8.5"
Expand Down
2 changes: 2 additions & 0 deletions game_library/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod markers_to_biomes;
mod movement_bundle;
mod noise;
mod particle;
mod physics;
mod realm_data;
mod schedule;
mod shared_traits;
Expand Down Expand Up @@ -71,6 +72,7 @@ pub use markers_to_biomes::MarkersToBiomes;
pub use movement_bundle::MovementBundle;
pub use noise::GeneratedMaps;
pub use noise::NoisePlugin;
pub use physics::PhysicsPlugin;
pub use realm_data::Realm;
pub use schedule::*;
pub use shared_traits::InternalId;
Expand Down
6 changes: 6 additions & 0 deletions game_library/src/physics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Physics plugin and systems

mod plugin;

#[allow(clippy::module_name_repetitions)]
pub use plugin::PhysicsPlugin;
41 changes: 41 additions & 0 deletions game_library/src/physics/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

use crate::state::Game;

/// Pixels per meter
pub const PIXELS_PER_METER: f32 = 16.0;
/// Gravity (in pixels per second squared); No gravity.
pub const GRAVITY: Vec2 = Vec2::ZERO;

/// Elementalist physics plugin
#[allow(clippy::module_name_repetitions)]
pub struct PhysicsPlugin;

impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) {
// Add the Rapier physics plugin
app.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(
PIXELS_PER_METER,
))
.insert_resource(RapierConfiguration {
gravity: GRAVITY,
..default()
});

app.add_systems(Update, display_events.run_if(in_state(Game::Playing)));
}
}

fn display_events(
mut collision_events: EventReader<CollisionEvent>,
mut contact_force_events: EventReader<ContactForceEvent>,
) {
for collision_event in collision_events.read() {
info!("Received collision event: {:?}", collision_event);
}

for contact_force_event in contact_force_events.read() {
info!("Received contact force event: {:?}", contact_force_event);
}
}

0 comments on commit 05baae2

Please sign in to comment.