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