use bevy::prelude::*;
#[derive(Component, Debug)]
pub enum RigidBody {
Static,
Kinematic,
Dynamic,
}
impl Default for RigidBody {
fn default() -> Self {
RigidBody::Static
}
}
#[derive(Component, Default, Debug)]
pub struct Velocity(Vec2);
impl Velocity {
pub fn add_velocity(&mut self, v: &Vec2) {
self.0 += *v;
}
pub fn set_velocity(&mut self, v: &Vec2) {
self.0 = *v;
}
pub fn set_x(&mut self, x: f32) {
self.0.x = x;
}
pub fn set_y(&mut self, y: f32) {
self.0.y = y;
}
pub fn x(&self) -> f32 {
self.0.x
}
pub fn y(&self) -> f32 {
self.0.y
}
pub fn is_still_x(&self) -> bool {
self.0.x == 0.0
}
pub fn is_still(&self) -> bool {
self.0 == Vec2::ZERO
}
}
#[derive(Bundle, Default)]
pub struct PhysicsBundle {
pub rigidbody: RigidBody,
pub velocity: Velocity,
}
#[derive(Resource)]
pub struct PhysicsConfiguration {
pub paused: bool,
pub show_collision_boxes: bool,
}
#[derive(Resource)]
pub struct Gravity(pub Vec2);
pub fn apply_gravity(
mut query: Query<(&mut Velocity, &RigidBody), With<RigidBody>>,
time: Res<Time>,
gravity: Res<Gravity>,
physics_config: Res<PhysicsConfiguration>,
) {
let g = gravity.0;
let dt = time.delta_seconds();
for (mut v, rigidbody) in query.iter_mut() {
if let RigidBody::Dynamic = rigidbody {
if !physics_config.paused {
v.add_velocity(&(g * dt));
}
}
}
}
#[allow(clippy::type_complexity)]
pub fn move_rigidbodies(
mut query: Query<(&mut Transform, &Velocity), (Changed<Velocity>, With<RigidBody>)>,
time: Res<Time>,
physics_config: Res<PhysicsConfiguration>,
) {
if !physics_config.paused {
let dt = time.delta_seconds();
for (mut transform, v) in query.iter_mut() {
transform.translation += v.0.extend(0.0) * dt;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collision::*;
use crate::tests::create_physics_app;
use bevy::utils::Duration;
#[test]
fn gravity_works() {
let mut app = create_physics_app();
let mut time = Time::default();
time.update();
app.world.insert_resource(time);
let dynamic_id = app
.world
.spawn_empty()
.insert(PhysicsBundle {
rigidbody: RigidBody::Dynamic,
..default()
})
.insert(Transform::from_xyz(0.0, 0.0, 0.0))
.id();
let static_id = app
.world
.spawn_empty()
.insert(PhysicsBundle {
rigidbody: RigidBody::Static,
..default()
})
.insert(Transform::from_xyz(0.0, 0.0, 0.0))
.id();
let mut time = app.world.resource_mut::<Time>();
let last_update = time.last_update().unwrap();
time.update_with_instant(last_update + Duration::from_secs_f32(1.0));
app.update();
assert!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.y
< 0.0
);
assert_eq!(
app.world.get::<Transform>(static_id).unwrap().translation.y,
0.0
);
}
#[test]
fn velocity_works_with_collision() {
let mut app = create_physics_app();
let mut time = Time::default();
time.update();
app.world.insert_resource(time);
let dynamic_id = app
.world
.spawn_empty()
.insert(PhysicsBundle {
rigidbody: RigidBody::Kinematic,
..default()
})
.insert(CollisionBundle {
collision_box: CollisionBox(8.0, 8.0),
collision_layers: CollisionLayers {
layer_id: 0,
collides_with_ids: vec![1],
},
..default()
})
.insert(Transform::from_xyz(0.0, 0.0, 0.0))
.id();
app.world
.spawn_empty()
.insert(PhysicsBundle {
rigidbody: RigidBody::Static,
..default()
})
.insert(CollisionBundle {
collision_box: CollisionBox(8.0, 8.0),
collision_layers: CollisionLayers {
layer_id: 1,
collides_with_ids: vec![0],
},
..default()
})
.insert(Transform::from_xyz(20.0, 0.0, 0.0));
let mut time = app.world.resource_mut::<Time>();
let last_update = time.last_update().unwrap();
time.update_with_instant(last_update + Duration::from_secs_f32(1.1));
app.world
.get_mut::<Velocity>(dynamic_id)
.unwrap()
.set_velocity(&Vec2::new(10.0, 0.0));
app.update();
assert_eq!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.x,
4.0
);
app.update();
assert_eq!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.x,
4.0
);
app.world
.get_mut::<Transform>(dynamic_id)
.unwrap()
.translation
.x = 40.0;
app.world
.get_mut::<Velocity>(dynamic_id)
.unwrap()
.set_velocity(&Vec2::new(-10.0, 0.0));
app.update();
assert_eq!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.x,
36.0
);
app.world
.get_mut::<Transform>(dynamic_id)
.unwrap()
.translation = Vec3::new(10.0, 20.0, 0.0);
app.world
.get_mut::<Velocity>(dynamic_id)
.unwrap()
.set_velocity(&Vec2::new(0.0, -10.0));
app.update();
assert_eq!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.y,
16.0
);
app.world
.get_mut::<Transform>(dynamic_id)
.unwrap()
.translation
.y = -20.0;
app.world
.get_mut::<Velocity>(dynamic_id)
.unwrap()
.set_velocity(&Vec2::new(0.0, 10.0));
app.update();
assert_eq!(
app.world
.get::<Transform>(dynamic_id)
.unwrap()
.translation
.y,
-16.0
);
}
}