Skip to content

Commit

Permalink
Add a Pushback level, for usage when developing #12
Browse files Browse the repository at this point in the history
  • Loading branch information
idanarye committed Aug 13, 2024
2 parents b73a7e8 + 6b07a97 commit 43766e7
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 36 deletions.
13 changes: 9 additions & 4 deletions demos/src/bin/platformer_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ fn main() {
>::default());
app.add_systems(Startup, setup_camera_and_lights);
app.add_plugins({
LevelSwitchingPlugin::new(app_setup_configuration.level_to_load.as_ref()).with(
"Default",
tnua_demos_crate::levels_setup::for_3d_platformer::setup_level,
)
LevelSwitchingPlugin::new(app_setup_configuration.level_to_load.as_ref())
.with(
"Default",
tnua_demos_crate::levels_setup::for_3d_platformer::setup_level,
)
.with(
"Pushback",
tnua_demos_crate::levels_setup::pushback_3d::setup_level,
)
});
app.add_systems(Startup, setup_player);
app.add_systems(
Expand Down
88 changes: 88 additions & 0 deletions demos/src/level_mechanics/cannon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use bevy::{ecs::system::EntityCommands, prelude::*};

use crate::levels_setup::{IsPlayer, LevelObject};

#[derive(Component)]
pub struct Cannon {
pub timer: Timer,
pub cmd: Box<dyn Send + Sync + Fn(&mut EntityCommands)>,
}

pub struct CannonPlugin;

impl Plugin for CannonPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (shoot, handle_collision));
}
}

fn shoot(
time: Res<Time>,
mut query: Query<(&mut Cannon, &GlobalTransform, Option<&Name>)>,
mut commands: Commands,
) {
for (mut cannon, cannon_transform, cannon_name) in query.iter_mut() {
if cannon.timer.tick(time.delta()).just_finished() {
let mut cmd = commands.spawn(LevelObject);
if let Some(cannon_name) = cannon_name.as_ref() {
cmd.insert(Name::new(format!("{cannon_name} projectile")));
}
#[cfg(feature = "rapier3d")]
cmd.insert(bevy_rapier3d::geometry::ActiveEvents::COLLISION_EVENTS);
(cannon.cmd)(&mut cmd);
cmd.insert(TransformBundle::from_transform(
Transform::from_translation(cannon_transform.translation()),
));
}
}
}

#[derive(Component)]
#[allow(clippy::type_complexity)]
pub struct CannonBullet {
effect: Box<dyn Send + Sync + Fn(&mut EntityCommands)>,
}

impl CannonBullet {
pub fn new_with_effect(effect: impl 'static + Send + Sync + Fn(&mut EntityCommands)) -> Self {
Self {
effect: Box::new(effect),
}
}
}

fn handle_collision(
#[cfg(feature = "avian3d")] mut avian_reader: EventReader<avian3d::prelude::CollisionStarted>,
#[cfg(feature = "rapier3d")] mut rapier_reader: EventReader<
bevy_rapier3d::prelude::CollisionEvent,
>,
bullets_query: Query<&CannonBullet>,
player_query: Query<(), With<IsPlayer>>,
mut commands: Commands,
) {
let events = std::iter::empty::<(Entity, Entity)>();
#[cfg(feature = "avian3d")]
let events = events.chain(avian_reader.read().map(|event| (event.0, event.1)));
#[cfg(feature = "rapier3d")]
let events = events.chain(rapier_reader.read().filter_map(|event| {
use bevy_rapier3d::pipeline::CollisionEvent;
if let CollisionEvent::Started(e1, e2, _) = event {
Some((*e1, *e2))
} else {
None
}
}));

let events = events.flat_map(|(e1, e2)| [(e1, e2), (e2, e1)]);

for (bullet_entity, player_entity) in events {
let Ok(bullet) = bullets_query.get(bullet_entity) else {
continue;
};
if !player_query.contains(player_entity) {
continue;
}
(bullet.effect)(&mut commands.entity(player_entity));
commands.entity(bullet_entity).despawn_recursive();
}
}
9 changes: 9 additions & 0 deletions demos/src/level_mechanics/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
mod cannon;
mod moving_platform;
mod push_effect;
mod time_to_despawn;

use bevy::prelude::*;

pub use cannon::{Cannon, CannonBullet};
pub use moving_platform::MovingPlatform;
pub use push_effect::PushEffect;
pub use time_to_despawn::TimeToDespawn;

pub struct LevelMechanicsPlugin;

impl Plugin for LevelMechanicsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(moving_platform::MovingPlatformPlugin);
app.add_plugins(cannon::CannonPlugin);
app.add_plugins(push_effect::PushEffectPlugin);
app.add_plugins(time_to_despawn::TimeToDespawnPlugin);
}
}
69 changes: 69 additions & 0 deletions demos/src/level_mechanics/push_effect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use bevy::{ecs::query::QueryData, prelude::*};
use bevy_tnua::math::Vector3;

pub struct PushEffectPlugin;

impl Plugin for PushEffectPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, apply_push_effect);
}
}

#[derive(Component)]
pub enum PushEffect {
Impulse(Vector3),
}

#[derive(QueryData)]
#[query_data(mutable)]
struct VelocityQuery {
#[cfg(feature = "rapier2d")]
rapier2d_velocity: Option<&'static mut bevy_rapier2d::prelude::Velocity>,

#[cfg(feature = "rapier3d")]
rapier3d_velocity: Option<&'static mut bevy_rapier3d::prelude::Velocity>,

#[cfg(feature = "avian2d")]
avian2d_linear_velocity: Option<&'static mut avian2d::prelude::LinearVelocity>,

#[cfg(feature = "avian3d")]
avian3d_linear_velocity: Option<&'static mut avian3d::prelude::LinearVelocity>,
}

impl VelocityQueryItem<'_> {
fn apply_impulse(&mut self, impulse: Vector3) {
#[cfg(feature = "rapier2d")]
if let Some(velocity) = self.rapier2d_velocity.as_mut() {
velocity.linvel += impulse.truncate();
}

#[cfg(feature = "rapier3d")]
if let Some(velocity) = self.rapier3d_velocity.as_mut() {
velocity.linvel += impulse;
}

#[cfg(feature = "avian2d")]
if let Some(velocity) = self.avian2d_linear_velocity.as_mut() {
velocity.0 += impulse.truncate();
}

#[cfg(feature = "avian3d")]
if let Some(velocity) = self.avian3d_linear_velocity.as_mut() {
velocity.0 += impulse;
}
}
}

fn apply_push_effect(
mut query: Query<(Entity, &PushEffect, VelocityQuery)>,
mut commands: Commands,
) {
for (entity, push_effect, mut velocity) in query.iter_mut() {
match push_effect {
PushEffect::Impulse(impulse) => {
velocity.apply_impulse(*impulse);
commands.entity(entity).remove::<PushEffect>();
}
}
}
}
30 changes: 30 additions & 0 deletions demos/src/level_mechanics/time_to_despawn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use bevy::prelude::*;

pub struct TimeToDespawnPlugin;

impl Plugin for TimeToDespawnPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, handle_despawn);
}
}

#[derive(Component)]
pub struct TimeToDespawn(Timer);

impl TimeToDespawn {
pub fn from_seconds(duration: f32) -> Self {
Self(Timer::from_seconds(duration, TimerMode::Once))
}
}

fn handle_despawn(
time: Res<Time>,
mut query: Query<(Entity, &mut TimeToDespawn)>,
mut commands: Commands,
) {
for (entity, mut ttd) in query.iter_mut() {
if ttd.0.tick(time.delta()).just_finished() {
commands.entity(entity).despawn_recursive();
}
}
}
106 changes: 74 additions & 32 deletions demos/src/levels_setup/helper/helper3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,33 +106,41 @@ pub struct LevelSetupHelper3dWithMaterial<'a, 'w, 's> {
}

impl LevelSetupHelper3dWithMaterial<'_, '_, '_> {
pub fn spawn_cuboid(
pub fn spawn_mesh_without_physics(
&mut self,
name: impl ToString,
transform: Transform,
size: Vector3,
mesh: impl Into<Mesh>,
) -> EntityCommands {
let mesh = self.parent.meshes.add(Cuboid::from_size(size.f32()));
let mesh = self.parent.meshes.add(mesh);
let mut cmd = self.parent.spawn_named(name);

cmd.insert(PbrBundle {
mesh,
material: self.material.clone(),
transform,
..Default::default()
});
cmd
}

#[cfg(feature = "rapier3d")]
cmd.insert(rapier::Collider::cuboid(
0.5 * size.x.f32(),
0.5 * size.y.f32(),
0.5 * size.z.f32(),
pub fn spawn_cuboid(
&mut self,
name: impl ToString,
transform: Transform,
size: Vector3,
) -> EntityCommands {
let mut cmd =
self.spawn_mesh_without_physics(name, transform, Cuboid::from_size(size.f32()));

cmd.insert((
#[cfg(feature = "rapier3d")]
rapier::Collider::cuboid(0.5 * size.x.f32(), 0.5 * size.y.f32(), 0.5 * size.z.f32()),
#[cfg(feature = "avian3d")]
(
avian::RigidBody::Static,
avian::Collider::cuboid(size.x, size.y, size.z),
),
));
#[cfg(feature = "avian3d")]
{
cmd.insert(avian::RigidBody::Static);
cmd.insert(avian::Collider::cuboid(size.x, size.y, size.z));
}

cmd
}
Expand All @@ -144,34 +152,35 @@ impl LevelSetupHelper3dWithMaterial<'_, '_, '_> {
radius: Float,
half_height: Float,
) -> EntityCommands {
let mesh = self.parent.meshes.add(Cylinder {
radius: radius.f32(),
half_height: half_height.f32(),
});
let mut cmd = self.parent.spawn_named(name);

cmd.insert(PbrBundle {
mesh,
material: self.material.clone(),
let mut cmd = self.spawn_mesh_without_physics(
name,
transform,
..Default::default()
});
Cylinder {
radius: radius.f32(),
half_height: half_height.f32(),
},
);

#[cfg(feature = "rapier3d")]
cmd.insert(rapier::Collider::cylinder(half_height, radius));
#[cfg(feature = "avian3d")]
{
cmd.insert(avian::RigidBody::Static);
cmd.insert(avian::Collider::cylinder(radius, 2.0 * half_height));
}
cmd.insert((
#[cfg(feature = "rapier3d")]
rapier::Collider::cylinder(half_height, radius),
#[cfg(feature = "avian3d")]
(
avian::RigidBody::Static,
avian::Collider::cylinder(radius, 2.0 * half_height),
),
));

cmd
}
}

pub trait LevelSetupHelper3dEntityCommandsExtension {
fn make_kinematic(&mut self) -> &mut Self;
fn make_kinematic_with_linear_velocity(&mut self, velocity: Vector3) -> &mut Self;
fn make_kinematic_with_angular_velocity(&mut self, angvel: Vector3) -> &mut Self;
fn add_ball_collider(&mut self, radius: Float) -> &mut Self;
fn make_sensor(&mut self) -> &mut Self;
}

impl LevelSetupHelper3dEntityCommandsExtension for EntityCommands<'_> {
Expand All @@ -187,6 +196,21 @@ impl LevelSetupHelper3dEntityCommandsExtension for EntityCommands<'_> {
))
}

fn make_kinematic_with_linear_velocity(
&mut self,
#[allow(unused)] velocity: Vector3,
) -> &mut Self {
self.insert((
#[cfg(feature = "avian3d")]
(avian::LinearVelocity(velocity), avian::RigidBody::Kinematic),
#[cfg(feature = "rapier3d")]
(
rapier::Velocity::linear(velocity),
rapier::RigidBody::KinematicVelocityBased,
),
))
}

fn make_kinematic_with_angular_velocity(
&mut self,
#[allow(unused)] angvel: Vector3,
Expand All @@ -201,4 +225,22 @@ impl LevelSetupHelper3dEntityCommandsExtension for EntityCommands<'_> {
),
))
}

fn add_ball_collider(&mut self, #[allow(unused)] radius: Float) -> &mut Self {
self.insert((
#[cfg(feature = "avian3d")]
avian::Collider::sphere(radius),
#[cfg(feature = "rapier3d")]
rapier::Collider::ball(radius),
))
}

fn make_sensor(&mut self) -> &mut Self {
self.insert((
#[cfg(feature = "avian3d")]
avian::Sensor,
#[cfg(feature = "rapier3d")]
rapier::Sensor,
))
}
}
1 change: 1 addition & 0 deletions demos/src/levels_setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod for_2d_platformer;
pub mod for_3d_platformer;
mod helper;
pub mod level_switching;
pub mod pushback_3d;

pub use level_switching::{IsPlayer, LevelObject, PositionPlayer};
Loading

0 comments on commit 43766e7

Please sign in to comment.