280 lines
9.1 KiB
Rust
280 lines
9.1 KiB
Rust
use bevy::{prelude::*, render::camera::Camera};
|
|
use bevy_physimple::prelude::*;
|
|
|
|
#[derive(Default)]
|
|
pub struct Player {
|
|
double_jump: bool,
|
|
on_wall: Option<Vec2>,
|
|
on_floor: bool,
|
|
}
|
|
|
|
pub struct Gravity(Vec2);
|
|
|
|
pub struct ModManager;
|
|
impl Plugin for ModManager {
|
|
fn build(&self, app: &mut AppBuilder) {
|
|
app // Basic setup of the app
|
|
.add_plugin(Physics2dPlugin)
|
|
// .add_system(bevy::input::system::exit_on_esc_system.system())
|
|
.add_startup_system(setup.system())
|
|
.add_system(controller_on_stuff.system())
|
|
.add_system(character_system.system())
|
|
.add_system(change_sensor_color.system())
|
|
.add_system(gravity.system())
|
|
.add_system(ray_head.system());
|
|
}
|
|
}
|
|
#[derive(Debug)]
|
|
pub struct PlayerCam;
|
|
|
|
fn setup(mut coms: Commands, mut mats: ResMut<Assets<ColorMaterial>>, a_server: Res<AssetServer>) {
|
|
let wall = mats.add(Color::BLACK.into());
|
|
|
|
// insert a gravity struct
|
|
coms.insert_resource(Gravity(Vec2::new(0.0, -540.0)));
|
|
|
|
// Spawn character
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::splat(28.0)),
|
|
material: mats.add(Color::ALICE_BLUE.into()),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(KinematicBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::splat(28.0))),
|
|
..Default::default()
|
|
})
|
|
.insert(Player::default());
|
|
|
|
// Spawn the damn camera
|
|
// coms.spawn_bundle(OrthographicCameraBundle::new_2d());
|
|
/*coms.spawn_bundle(OrthographicCameraBundle::new_2d())
|
|
.insert(PlayerCam);*/
|
|
|
|
// Controls
|
|
let style = TextStyle {
|
|
font: a_server.load("fonts/Roboto/Roboto-Black.ttf"),
|
|
font_size: 32.0,
|
|
color: Color::ANTIQUE_WHITE,
|
|
};
|
|
let alignment = TextAlignment {
|
|
vertical: VerticalAlign::Bottom,
|
|
horizontal: HorizontalAlign::Left,
|
|
};
|
|
let text = "A/D - Movement\nSpace/W - Jump/Double jump\nS - Stomp(when mid air)";
|
|
coms.spawn_bundle(Text2dBundle {
|
|
text: Text::with_section(text, style, alignment),
|
|
transform: Transform::from_xyz(-270.0, 360.0, 0.0),
|
|
..Default::default()
|
|
});
|
|
|
|
coms.spawn_bundle(OrthographicCameraBundle::new_2d());
|
|
// center floor
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::new(2000.0, 230.0)),
|
|
material: wall.clone(),
|
|
transform: Transform::from_xyz(0.0, -400.0, 0.0),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(StaticBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::new(2000.0, 230.0))),
|
|
..Default::default()
|
|
});
|
|
|
|
// side wall
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::new(40.0, 300.0)),
|
|
material: wall.clone(),
|
|
transform: {
|
|
let mut t = Transform::from_xyz(450.0, 0.0, 0.0);
|
|
t.rotation = Quat::from_rotation_z(-0.1 * 3.14);
|
|
t
|
|
},
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(StaticBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::new(40.0, 300.0))),
|
|
..Default::default()
|
|
});
|
|
|
|
// smaller other side wall
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::new(30.0, 90.0)),
|
|
material: wall.clone(),
|
|
transform: Transform::from_xyz(-150.0, -160.0, 0.0),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(StaticBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::new(30.0, 90.0))),
|
|
..Default::default()
|
|
});
|
|
|
|
// Floating platform
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::new(200.0, 30.0)),
|
|
material: wall.clone(),
|
|
transform: Transform::from_xyz(-150.0, 0.0, 0.0),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(StaticBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::new(200.0, 30.0))),
|
|
..Default::default()
|
|
});
|
|
|
|
// Spawn the sensor
|
|
const SENSOR_SIZE: f32 = 50.0;
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::splat(SENSOR_SIZE)),
|
|
material: mats.add(Color::GOLD.into()),
|
|
transform: Transform::from_xyz(30.0, -150.0, 0.0),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(SensorBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::splat(SENSOR_SIZE))),
|
|
..Default::default()
|
|
});
|
|
|
|
// Spawn another cube which we will try to push or something
|
|
const CUBE_SIZE: f32 = 35.0;
|
|
coms.spawn_bundle(SpriteBundle {
|
|
sprite: Sprite::new(Vec2::splat(CUBE_SIZE)),
|
|
material: mats.add(Color::CRIMSON.into()),
|
|
transform: Transform::from_xyz(100.0, 0.0, 0.0),
|
|
..Default::default()
|
|
})
|
|
.insert_bundle(KinematicBundle {
|
|
shape: CollisionShape::Square(Square::size(Vec2::splat(CUBE_SIZE))),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
fn gravity(time: Res<Time>, grav: Res<Gravity>, mut q: Query<&mut Vel>) {
|
|
// Since the lib itself doesnt take care of gravity(for obv reasons) we need to do it here
|
|
let g = grav.0;
|
|
let t = time.delta_seconds();
|
|
|
|
for mut v in q.iter_mut() {
|
|
v.0 += t * g;
|
|
}
|
|
}
|
|
|
|
fn controller_on_stuff(
|
|
mut query: Query<(Entity, &mut Player)>,
|
|
mut colls: EventReader<CollisionEvent>,
|
|
) {
|
|
// Iterate over the collisions and check if the player is on a wall/floor
|
|
let (e, mut c) = query
|
|
.single_mut()
|
|
.expect("should be only one player :shrug:");
|
|
|
|
// clear the current data on c
|
|
c.on_floor = false;
|
|
c.on_wall = None;
|
|
|
|
for coll in colls.iter().filter(|&c| c.is_b_static) {
|
|
if coll.entity_a == e {
|
|
let n = coll.normal.dot(Vec2::Y);
|
|
|
|
if n > 0.7 {
|
|
c.on_floor = true;
|
|
} else if n.abs() <= 0.7 {
|
|
c.on_wall = Some(coll.normal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn character_system(
|
|
input: Res<Input<KeyCode>>,
|
|
time: Res<Time>,
|
|
gravity: Res<Gravity>,
|
|
mut query: Query<(&mut Player, &mut Vel)>,
|
|
) {
|
|
let gravity = gravity.0;
|
|
|
|
for (mut controller, mut vel) in query.iter_mut() {
|
|
if let Some(normal) = controller.on_wall {
|
|
// If we are colliding with a wall, make sure to stick
|
|
vel.0 -= normal * 0.1;
|
|
// and limit our speed downwards
|
|
if vel.0.y < -1.0 {
|
|
vel.0.y = -1.0;
|
|
}
|
|
}
|
|
// There are 2 places in which we apply a jump, so i made a little colsure for code reusability
|
|
let jump = |body: &Player, vel: &mut Vel| {
|
|
vel.0 = vel.0.slide(gravity.normalize()) - gravity * 0.6;
|
|
let wall = body.on_wall.unwrap_or(Vec2::ZERO) * 250.0;
|
|
vel.0 += wall;
|
|
};
|
|
|
|
let should_jump = input.just_pressed(KeyCode::Space) || input.just_pressed(KeyCode::W);
|
|
if controller.on_floor || controller.on_wall.is_some() {
|
|
controller.double_jump = true;
|
|
if should_jump {
|
|
jump(&controller, &mut vel);
|
|
}
|
|
} else if controller.double_jump && should_jump {
|
|
controller.double_jump = false;
|
|
jump(&controller, &mut vel);
|
|
}
|
|
|
|
// This is for the testing purpose of the continuous collision - aka "The Stomp"
|
|
if input.just_pressed(KeyCode::S) && !controller.on_floor {
|
|
vel.0 = Vec2::new(0.0, -500.0);
|
|
}
|
|
// REMINDER: Dont forget to multiply by `time.delta_seconds()` when messing with movement
|
|
let acc = Vec2::new(1000.0, 0.0) * time.delta_seconds();
|
|
if input.pressed(KeyCode::A) {
|
|
vel.0 -= acc;
|
|
} else if input.pressed(KeyCode::D) {
|
|
vel.0 += acc;
|
|
} else {
|
|
// This is not a good way to do friction
|
|
vel.0.x *= 1.0 - (10.0 * time.delta_seconds());
|
|
}
|
|
// terminal velocity
|
|
const TERMINAL_X: f32 = 500.0;
|
|
if vel.0.x.abs() > TERMINAL_X {
|
|
vel.0.x = TERMINAL_X.copysign(vel.0.x); // you can also do `TERMINAL_X * vel.0.x.signum()`
|
|
}
|
|
}
|
|
}
|
|
|
|
fn change_sensor_color(
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
q: Query<(&Sensor, &Handle<ColorMaterial>)>,
|
|
) {
|
|
// Simply change the color of the sensor if something is inside it
|
|
for (s, h) in q.iter() {
|
|
if let Some(mut m) = materials.get_mut(h) {
|
|
m.color = if s.bodies.len() == 0 {
|
|
Color::GOLD
|
|
} else {
|
|
Color::ALICE_BLUE
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ray_head(
|
|
mut ts: Query<&mut Transform, Without<RayCast>>,
|
|
q: Query<(&RayCast, &Children, &Transform)>,
|
|
) {
|
|
for (r, c, rt) in q.iter() {
|
|
if let Some(c) = c.first() {
|
|
if let Ok(mut t) = ts.get_mut(*c) {
|
|
// We use the offset in the `unwrap_or` because we want to offset the position to be where the ray "ends"
|
|
// while in the `map`(and `pos` by extension) we want the position relative to the transform component
|
|
// since `a.collision_point` is in global space
|
|
|
|
let pos = Vec2::new(rt.translation.x, rt.translation.y);
|
|
t.translation = r
|
|
.collision
|
|
.map(|a| a.collision_point - pos)
|
|
.unwrap_or(r.cast + r.offset)
|
|
.extend(0.0);
|
|
}
|
|
}
|
|
}
|
|
}
|