From 438336cc61c0091f134296353e543d14fa3ef879 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sat, 22 Jul 2023 15:47:47 +0100
Subject: [PATCH] Implement basic physics for kinematic bodies

---
 game_core/src/debug/mod.rs        | 20 ++++++++
 game_core/src/entities/mod.rs     |  6 ++-
 game_core/src/entities/physics.rs | 83 +++++++++++++++++++++++++++++++
 game_core/src/entities/player.rs  | 40 ++++++++++-----
 game_core/src/lib.rs              |  1 +
 game_core/src/main.rs             |  1 +
 game_core/src/system/mod.rs       |  2 +
 7 files changed, 140 insertions(+), 13 deletions(-)
 create mode 100644 game_core/src/debug/mod.rs
 create mode 100644 game_core/src/entities/physics.rs

diff --git a/game_core/src/debug/mod.rs b/game_core/src/debug/mod.rs
new file mode 100644
index 0000000..81957b1
--- /dev/null
+++ b/game_core/src/debug/mod.rs
@@ -0,0 +1,20 @@
+mod _plugin {
+	use crate::system::AppState;
+	use bevy::prelude::*;
+	use bevy_rapier2d::prelude::*;
+
+	pub struct DebugPlugin;
+	impl Plugin for DebugPlugin {
+		fn build(&self, app: &mut App) {
+			app.add_systems(OnEnter(AppState::InGame), |mut commands: Commands| {
+				commands.spawn((
+					TransformBundle::from_transform(Transform::from_xyz(0.0, -400.0, 0.0)),
+					RigidBody::Fixed,
+					Collider::cuboid(200.0, 50.0),
+				));
+			});
+		}
+	}
+}
+
+pub use _plugin::DebugPlugin;
diff --git a/game_core/src/entities/mod.rs b/game_core/src/entities/mod.rs
index a25fe93..e48802a 100644
--- a/game_core/src/entities/mod.rs
+++ b/game_core/src/entities/mod.rs
@@ -1,3 +1,4 @@
+mod physics;
 mod player;
 
 mod _plugin {
@@ -7,9 +8,12 @@ mod _plugin {
 	pub struct EntityPluginSet;
 	impl PluginGroup for EntityPluginSet {
 		fn build(self) -> PluginGroupBuilder {
-			PluginGroupBuilder::start::<Self>().add(super::player::PlayerSetupPlugin)
+			PluginGroupBuilder::start::<Self>()
+				.add(super::player::PlayerSetupPlugin)
+				.add(super::physics::PhysicsPlugin)
 		}
 	}
 }
 
 pub use _plugin::EntityPluginSet;
+pub use physics::{Acceleration, PhysicsLimits, PhysicsSet, Velocity, GRAVITY_ACC, GRAVITY_VEC};
diff --git a/game_core/src/entities/physics.rs b/game_core/src/entities/physics.rs
new file mode 100644
index 0000000..c862b46
--- /dev/null
+++ b/game_core/src/entities/physics.rs
@@ -0,0 +1,83 @@
+use crate::system::run_in_game;
+use bevy::prelude::*;
+use bevy_rapier2d::prelude::KinematicCharacterController;
+
+pub const GRAVITY_ACC: f32 = -384.0;
+pub const GRAVITY_VEC: Vec2 = Vec2::new(0.0, GRAVITY_ACC);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, SystemSet)]
+pub struct PhysicsSet;
+
+#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut)]
+pub struct Velocity(Vec2);
+
+#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut)]
+pub struct Acceleration(Vec2);
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct PhysicsLimits {
+	pub velocity: (Option<Vec2>, Option<Vec2>),
+	pub acceleration: (Option<Vec2>, Option<Vec2>),
+}
+
+impl PhysicsLimits {
+	pub fn limit_velocity(&self, velocity: Vec2) -> Vec2 {
+		match self.velocity {
+			(None, None) => velocity,
+			(Some(min), None) => velocity.max(min),
+			(None, Some(max)) => velocity.min(max),
+			(Some(min), Some(max)) => velocity.min(max).max(min),
+		}
+	}
+	pub fn limit_acceleration(&self, acceleration: Vec2) -> Vec2 {
+		match self.acceleration {
+			(None, None) => acceleration,
+			(Some(min), None) => acceleration.max(min),
+			(None, Some(max)) => acceleration.min(max),
+			(Some(min), Some(max)) => acceleration.min(max).max(min),
+		}
+	}
+}
+
+impl Acceleration {
+	pub fn gravity() -> Self {
+		Acceleration(GRAVITY_VEC)
+	}
+}
+
+fn apply_acceleration(
+	time: Res<Time>,
+	mut query: Query<(&mut Velocity, &Acceleration, Option<&PhysicsLimits>)>,
+) {
+	let dt = time.delta_seconds();
+	for (mut velocity, acceleration, limits) in &mut query {
+		**velocity += **acceleration * dt;
+		**velocity = limits
+			.copied()
+			.unwrap_or_default()
+			.limit_velocity(**velocity);
+	}
+}
+
+fn apply_velocity_to_kinematics(
+	time: Res<Time>,
+	mut query: Query<(&mut KinematicCharacterController, &Velocity)>,
+) {
+	let dt = time.delta_seconds();
+	for (mut controller, velocity) in &mut query {
+		controller.translation = Some(**velocity * dt);
+	}
+}
+
+pub struct PhysicsPlugin;
+impl Plugin for PhysicsPlugin {
+	fn build(&self, app: &mut App) {
+		app.add_systems(
+			Update,
+			(apply_acceleration, apply_velocity_to_kinematics)
+				.chain()
+				.in_set(PhysicsSet)
+				.run_if(run_in_game),
+		);
+	}
+}
diff --git a/game_core/src/entities/player.rs b/game_core/src/entities/player.rs
index 0d81e7d..3a32b2d 100644
--- a/game_core/src/entities/player.rs
+++ b/game_core/src/entities/player.rs
@@ -2,25 +2,41 @@ use crate::assets::AssetHandles;
 use crate::system::AppState;
 use bevy::prelude::*;
 
+use crate::entities::{Acceleration, PhysicsLimits, Velocity, GRAVITY_ACC};
 use bevy_rapier2d::prelude::*;
 
+#[derive(Component, Clone, Copy, Debug)]
+pub struct Player;
+
 pub fn spawn_player(mut commands: Commands, assets: Res<AssetHandles>) {
-	commands
-		.spawn((SpriteSheetBundle {
+	commands.spawn((
+		Player,
+		SpriteSheetBundle {
 			sprite: TextureAtlasSprite::new(3),
 			texture_atlas: assets.atlas("beige_blob"),
 			..Default::default()
-		},))
-		.with_children(|builder| {
-			builder.spawn((
-				TransformBundle::from_transform(Transform::from_xyz(0.0, -32.0, 0.0)),
+		},
+		RigidBody::KinematicPositionBased,
+		KinematicCharacterController {
+			custom_shape: Some((
 				Collider::capsule(Vec2::new(0.0, 8.0), Vec2::new(0.0, -48.0), 48.0),
-				KinematicCharacterController {
-					offset: CharacterLength::Absolute(0.01),
-					..Default::default()
-				},
-			));
-		});
+				Vec2::new(0.0, -32.0),
+				0.0,
+			)),
+			snap_to_ground: Some(CharacterLength::Absolute(0.5)),
+			offset: CharacterLength::Absolute(0.03),
+			..Default::default()
+		},
+		Acceleration::gravity(),
+		Velocity::default(),
+		PhysicsLimits {
+			velocity: (
+				Some(Vec2::new(f32::NEG_INFINITY, GRAVITY_ACC)),
+				Some(Vec2::new(f32::INFINITY, (-GRAVITY_ACC) * 2.0)),
+			),
+			acceleration: Default::default(),
+		},
+	));
 }
 
 pub struct PlayerSetupPlugin;
diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs
index 371b2e7..24db231 100644
--- a/game_core/src/lib.rs
+++ b/game_core/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod assets;
+pub mod debug;
 pub mod entities;
 pub mod system;
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index ef41de5..acc48ff 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -5,6 +5,7 @@ fn main() {
 		.add_plugins(game_core::assets::AssetLoadingPlugin)
 		.add_plugins(game_core::system::SystemPluginSet)
 		.add_plugins(game_core::entities::EntityPluginSet)
+		.add_plugins(game_core::debug::DebugPlugin)
 		.add_plugins(bevy_rapier2d::plugin::RapierPhysicsPlugin::<()>::pixels_per_meter(128.0))
 		.add_plugins(micro_banimate::BanimatePluginGroup)
 		.add_plugins(micro_musicbox::CombinedAudioPlugins::<
diff --git a/game_core/src/system/mod.rs b/game_core/src/system/mod.rs
index f8823a1..0cf6bb6 100644
--- a/game_core/src/system/mod.rs
+++ b/game_core/src/system/mod.rs
@@ -17,6 +17,7 @@ mod _plugin {
 		pub struct PhysDebugPlugin;
 		impl Plugin for PhysDebugPlugin {
 			fn build(&self, app: &mut App) {
+				info!("Including Debug Physics Plugin");
 				app.add_plugins(bevy_rapier2d::render::RapierDebugRenderPlugin {
 					mode: DebugRenderMode::all(),
 					style: DebugRenderStyle::default(),
@@ -36,6 +37,7 @@ mod _plugin {
 
 			#[cfg(feature = "phys-debug")]
 			{
+				info!("Rendering physics debug information");
 				plugins.add(phys_debug::PhysDebugPlugin)
 			}
 
-- 
GitLab