Initial commit

fix/collision-refresh
hheik 2022-10-19 20:27:25 +03:00
commit d5107655a0
11 changed files with 4293 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

3826
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "kuilu"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = { version = "0.8.1", features = ["dynamic"] }
bevy-inspector-egui = "0.13.0"
bevy_rapier2d = "0.17.0"
serde = "1.0.145"
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3

39
src/game.rs Normal file
View File

@ -0,0 +1,39 @@
use bevy::prelude::*;
use bevy_inspector_egui::*;
use bevy_rapier2d::prelude::*;
use self::{camera::GameCameraPlugin, kinematic::KinematicPlugin, player::PlayerPlugin};
pub mod camera;
pub mod kinematic;
pub mod player;
pub fn init() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.add_plugin(RapierDebugRenderPlugin::default())
.add_plugin(WorldInspectorPlugin::new())
.add_plugin(KinematicPlugin)
.add_plugin(GameCameraPlugin)
.add_plugin(PlayerPlugin)
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands) {
// Static ground
commands
.spawn()
.insert(Name::new("Ground"))
.insert(Collider::cuboid(400.0, 25.0))
.insert_bundle(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.25, 0.25, 0.75),
custom_size: Some(Vec2::new(800.0, 50.0)),
..default()
},
transform: Transform::from_xyz(0.0, -100.0, 0.0),
..default()
});
}

83
src/game/camera.rs Normal file
View File

@ -0,0 +1,83 @@
use bevy::{prelude::*, render::camera::ScalingMode};
use crate::util::{move_towards_vec3, vec3_lerp};
pub struct GameCameraPlugin;
impl Plugin for GameCameraPlugin {
fn build(&self, app: &mut App) {
app.register_type::<CameraFollow>()
.add_startup_system(camera_setup)
.add_system(camera_system);
}
}
#[derive(Clone, Copy)]
pub enum FollowMovement {
Instant,
Linear(f32),
Smooth(f32),
}
impl Default for FollowMovement {
fn default() -> Self {
Self::Instant
}
}
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
pub struct CameraFollow {
pub priority: i32,
#[reflect(ignore)]
pub movement: FollowMovement,
}
fn camera_setup(mut commands: Commands) {
commands
.spawn()
.insert(Name::new("Camera"))
.insert_bundle(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::FixedHorizontal(320.0),
..default()
},
..default()
});
}
fn camera_system(
time: Res<Time>,
mut camera_query: Query<&mut Transform, With<Camera2d>>,
follow_query: Query<(&Transform, &CameraFollow), Without<Camera2d>>,
) {
let (target, follow) = match follow_query
.iter()
.max_by_key(|(_transform, follow)| follow.priority)
{
Some(followed) => followed,
None => return,
};
for mut camera_transform in camera_query.iter_mut() {
match follow.movement {
FollowMovement::Instant => {
camera_transform.translation = target.translation * Vec3::new(0.0, 1.0, 1.0)
}
FollowMovement::Linear(speed) => {
camera_transform.translation = move_towards_vec3(
camera_transform.translation,
target.translation * Vec3::new(0.0, 1.0, 1.0),
speed * time.delta_seconds(),
)
}
FollowMovement::Smooth(speed) => {
camera_transform.translation = vec3_lerp(
camera_transform.translation,
target.translation * Vec3::new(0.0, 1.0, 1.0),
speed * time.delta_seconds(),
)
}
}
}
}

139
src/game/kinematic.rs Normal file
View File

@ -0,0 +1,139 @@
use std::f32::consts::PI;
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use crate::util::*;
pub struct KinematicPlugin;
impl Plugin for KinematicPlugin {
fn build(&self, app: &mut App) {
app.register_type::<KinematicProperties>()
.register_type::<KinematicInput>()
.add_system(kinematic_movement);
}
}
#[derive(Bundle)]
pub struct KinematicBundle {
pub rigidbody: RigidBody,
pub velocity: Velocity,
pub gravity_scale: GravityScale,
pub collider: Collider,
pub locked_axes: LockedAxes,
pub events: ActiveEvents,
pub collisions: ActiveCollisionTypes,
pub properties: KinematicProperties,
#[bundle]
pub transform: TransformBundle,
}
impl Default for KinematicBundle {
fn default() -> Self {
KinematicBundle {
rigidbody: RigidBody::Dynamic,
gravity_scale: GravityScale(4.0),
locked_axes: LockedAxes::ROTATION_LOCKED,
events: ActiveEvents::COLLISION_EVENTS,
collisions: ActiveCollisionTypes::all(),
collider: Collider::default(),
properties: KinematicProperties::default(),
transform: TransformBundle::default(),
velocity: Velocity::default(),
}
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct KinematicProperties {
pub ground_speed: f32,
pub ground_acceleration: f32,
pub ground_friction: f32,
pub air_speed: f32,
pub air_acceleration: f32,
pub air_friction: f32,
}
impl Default for KinematicProperties {
fn default() -> Self {
Self {
ground_speed: 100.0,
ground_acceleration: 20.0,
ground_friction: 30.0,
air_speed: 100.0,
air_acceleration: 10.0,
air_friction: 10.0,
}
}
}
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
pub struct KinematicInput {
pub movement: Vec2,
}
fn kinematic_movement(
time: Res<Time>,
mut query: Query<(
&mut Velocity,
&KinematicProperties,
Option<&KinematicInput>,
Option<&GravityScale>,
)>,
) {
for (mut velocity, props, input, gravity) in query.iter_mut() {
let default = &KinematicInput::default();
let input = input.unwrap_or(default);
let has_gravity = if let Some(gravity) = gravity {
gravity.0.abs() < f32::EPSILON
} else {
false
};
let on_ground = if has_gravity { true } else { false };
let (speed, acceleration, friction) = if on_ground {
(
props.ground_speed,
props.ground_acceleration,
props.ground_friction,
)
} else {
(props.air_speed, props.air_acceleration, props.air_friction)
};
const GRAVITY_DIR: Vec2 = Vec2::NEG_Y;
let current_velocity = velocity.linvel;
let target_velocity =
input.movement * speed + current_velocity.project_onto_normalized(GRAVITY_DIR);
let angle_lerp = if current_velocity.length_squared() > 0.01 {
let result = inverse_lerp(
0.0,
PI,
current_velocity
.angle_between(target_velocity - current_velocity)
.abs(),
);
if result.is_nan() {
0.0
} else {
result
}
} else {
0.0
};
let delta_interpolation = angle_lerp.clamp(0.0, 1.0);
let velocity_change_speed = lerp(acceleration, friction, delta_interpolation) * speed;
velocity.linvel = move_towards_vec2(
current_velocity,
target_velocity,
velocity_change_speed * time.delta_seconds(),
);
}
}

86
src/game/player.rs Normal file
View File

@ -0,0 +1,86 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use super::{
camera::{CameraFollow, FollowMovement},
kinematic::*,
};
pub struct PlayerPlugin;
impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.register_type::<PlayerInput>()
.add_startup_system(player_spawn)
.add_system(player_system);
}
}
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
pub struct PlayerInput;
#[derive(Default, Bundle)]
pub struct PlayerBundle {
pub control: PlayerInput,
#[bundle]
pub kinematic: KinematicBundle,
}
pub fn player_system(
input: Res<Input<KeyCode>>,
mut query: Query<(&mut KinematicInput, &Transform), With<PlayerInput>>,
) {
let (mut kinematic_input, transform) = match query.get_single_mut() {
Ok(single) => single,
Err(_) => return,
};
let movement = Vec2 {
x: input_to_axis(input.pressed(KeyCode::A), input.pressed(KeyCode::D)),
// y: input_to_axis(input.pressed(KeyCode::S), input.pressed(KeyCode::W)),
y: 0.0,
};
kinematic_input.movement = movement;
}
fn input_to_axis(negative: bool, positive: bool) -> f32 {
if negative == positive {
return 0.0;
}
if negative {
-1.0
} else {
1.0
}
}
pub fn player_spawn(mut commands: Commands) {
let kinematic = KinematicBundle {
collider: Collider::round_cuboid(8.0, 16.0, 2.0),
transform: TransformBundle::default(),
..default()
};
commands
.spawn()
.insert(Name::new("Player"))
.insert_bundle(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.75, 0.25, 0.25),
custom_size: Some(Vec2 { x: 16.0, y: 32.0 }),
..default()
},
..default()
})
.insert_bundle(PlayerBundle {
kinematic,
..default()
})
.insert(KinematicInput::default())
.insert(CameraFollow {
priority: 0,
movement: FollowMovement::Smooth(7.0),
});
}

6
src/main.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod game;
pub mod util;
fn main() {
game::init();
}

0
src/mst.rs Normal file
View File

51
src/mst/world.rs Normal file
View File

@ -0,0 +1,51 @@
use box2d_rs::{
b2_body::BodyPtr,
b2_math::B2vec2,
b2_world::{B2world, B2worldPtr},
b2rs_common::UserDataType,
};
use unsafe_send_sync::UnsafeSendSync;
pub type UnsafeBox2D = UnsafeSendSync<Box2D>;
pub type UnsafeBody = UnsafeSendSync<BodyPtr<UserData>>;
#[derive(Clone, Copy, Default)]
pub struct UserData;
impl UserDataType for UserData {
type Body = Option<u32>;
type Fixture = u32;
type Joint = ();
}
pub struct Box2D {
pub gravity: B2vec2,
pub world_ptr: B2worldPtr<UserData>,
}
impl Box2D {
pub const METERS_TO_TEXELS: f32 = 4.0;
pub const TEXELS_TO_METERS: f32 = 1.0 / Self::METERS_TO_TEXELS;
pub const INIT_POS: B2vec2 = B2vec2 {
x: -1000.0,
y: -1000.0,
};
fn new() -> Box2D {
let gravity: B2vec2 = B2vec2 { x: 0.0, y: 100.0 };
// let gravity: B2vec2 = B2vec2 { x: 0.0, y: 1.0 };
// let gravity: B2vec2 = B2vec2 { x: 0.0, y: 0.0 };
let world_ptr: B2worldPtr<UserData> = B2world::new(gravity);
Box2D { gravity, world_ptr }
}
pub fn new_unsafe() -> UnsafeBox2D {
UnsafeBox2D::new(Self::new())
}
}
impl Default for Box2D {
fn default() -> Self {
Self::new()
}
}

42
src/util.rs Normal file
View File

@ -0,0 +1,42 @@
use bevy::prelude::*;
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
a * (1.0 - t) + b * t
}
pub fn inverse_lerp(a: f32, b: f32, value: f32) -> f32 {
(value - a) / (b - a)
}
pub fn vec2_lerp(a: Vec2, b: Vec2, t: f32) -> Vec2 {
Vec2 {
x: lerp(a.x, b.x, t),
y: lerp(a.y, b.y, t),
}
}
pub fn vec3_lerp(a: Vec3, b: Vec3, t: f32) -> Vec3 {
Vec3 {
x: lerp(a.x, b.x, t),
y: lerp(a.y, b.y, t),
z: lerp(a.z, b.z, t),
}
}
pub fn move_towards_vec2(from: Vec2, to: Vec2, amount: f32) -> Vec2 {
let diff = to - from;
let length = diff.length();
if length <= f32::EPSILON {
return from;
}
from + diff.normalize() * length.min(amount)
}
pub fn move_towards_vec3(from: Vec3, to: Vec3, amount: f32) -> Vec3 {
let diff = to - from;
let length = diff.length();
if length <= f32::EPSILON {
return from;
}
from + diff.normalize() * length.min(amount)
}