Initial commit
commit
d5107655a0
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod game;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
game::init();
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue