diff --git a/assets/config/config.phys b/assets/config/config.phys index 727ab38ab46f77c6aca99964dee91de9d5f02e80..97800d765c671c6563e96fb53d92958cff0dfd9a 100644 --- a/assets/config/config.phys +++ b/assets/config/config.phys @@ -1,5 +1,5 @@ { - "gravity": -123.0, + "gravity": -200.0, "jump": 200.0, "speed": 150.0 } \ No newline at end of file diff --git a/assets/sprites/world.png b/assets/sprites/world.png index e917b8fabbd77c5668553d489c87770dcda13677..b97f306515e0258af40be3e36abf7cfc93702107 100644 --- a/assets/sprites/world.png +++ b/assets/sprites/world.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb63be0e3ef42965bba1364620643497fa267fa18894debb85a74c8f3faa59b5 -size 43342 +oid sha256:619c5382db0bb237eab80f42d6c45818f888b9c33305e5b8bf013b6b01350d7c +size 43302 diff --git a/assets/sprites/world_ext.png b/assets/sprites/world_ext.png new file mode 100644 index 0000000000000000000000000000000000000000..6cd47d8ccc15fef47d16d71dd93833691bbf9f34 --- /dev/null +++ b/assets/sprites/world_ext.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3d81edc05aed20cf92d0fcee4d04d98c0fe620cf10a662adfa2090dbfe7ea5c +size 140389 diff --git a/assets/world.ldtk b/assets/world.ldtk index 7baf762e183bfc8806dc7bd2428d640e554a0726..1891c61064ae7bfe734e87543a69584de62dae59 100644 --- a/assets/world.ldtk +++ b/assets/world.ldtk @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:768f14ee00b4997d06c01dca23d1d74620bd0a7b5d1e210a189c22b72d53a6d8 +oid sha256:ef25cb1aa8d9a56f5979927b6fd97c3bd20359644648a4fd097b6dce38d3072f size 93007 diff --git a/game_core/src/assets/loader.rs b/game_core/src/assets/loader.rs index 7ad3ff274a92f9c9a03540edde7d0b083f507c51..d562f58c673a044835c406fa414318c48d17893f 100644 --- a/game_core/src/assets/loader.rs +++ b/game_core/src/assets/loader.rs @@ -64,7 +64,7 @@ impl<'w, 's> AssetTypeLoader<'w, 's> { assets: &[FixedAssetNameMapping], ) -> Vec<Handle<Image>> { self.load_list(assets, |loader, path, key| { - let handle: Handle<Image> = loader.asset_server.load(&path); + let handle: Handle<Image> = loader.asset_server.load(path); loader .handles @@ -76,8 +76,8 @@ impl<'w, 's> AssetTypeLoader<'w, 's> { Vec2::new(config.tile_width as f32, config.tile_height as f32), config.columns, config.rows, - None, - None, + config.padding.map(|v| Vec2::new(v as f32, v as f32)), + config.offset.map(|v| Vec2::new(v as f32, v as f32)), ); let atlas_handle = loader.atlas.add(atlas); diff --git a/game_core/src/assets/resources.rs b/game_core/src/assets/resources.rs index 61b629b55325b4c30c196f48d42a221b4f930119..83105bbc7b86fa6ebb9377d060cb4c15452d71f5 100644 --- a/game_core/src/assets/resources.rs +++ b/game_core/src/assets/resources.rs @@ -10,6 +10,8 @@ pub struct SpriteSheetConfig { pub tile_height: usize, pub columns: usize, pub rows: usize, + pub padding: Option<usize>, + pub offset: Option<usize>, } impl SpriteSheetConfig { @@ -19,6 +21,25 @@ impl SpriteSheetConfig { tile_height: tile_wh, columns, rows, + padding: None, + offset: None, + } + } + + pub fn extruded_squares( + tile_wh: usize, + columns: usize, + rows: usize, + padding: Option<usize>, + offset: Option<usize>, + ) -> Self { + Self { + tile_width: tile_wh, + tile_height: tile_wh, + columns, + rows, + padding, + offset, } } @@ -28,6 +49,26 @@ impl SpriteSheetConfig { tile_height, columns, rows, + padding: None, + offset: None, + } + } + + pub fn extruded_rectangles( + tile_width: usize, + tile_height: usize, + columns: usize, + rows: usize, + padding: Option<usize>, + offset: Option<usize>, + ) -> Self { + Self { + tile_width, + tile_height, + columns, + rows, + padding, + offset, } } } diff --git a/game_core/src/assets/startup.rs b/game_core/src/assets/startup.rs index 3b1e45cfc5dd1025cb555f08f9416e7777404777..bb72806d67d840bb6024e0c9a8842ae98c20f58b 100644 --- a/game_core/src/assets/startup.rs +++ b/game_core/src/assets/startup.rs @@ -14,14 +14,16 @@ pub fn start_preload_resources( pub fn start_load_resources(mut loader: AssetTypeLoader) { loader.load_spritesheet( - &SpriteSheetConfig::squares(18, 32, 64), - &[("sprites/world.png", "world")], + &SpriteSheetConfig::extruded_squares(18, 32, 64, Some(2), Some(1)), + &[("sprites/world_ext.png", "world")], ); loader.load_spritesheet( &SpriteSheetConfig::squares(24, 9, 3), &[("sprites/characters.png", "characters")], ); + + loader.load_ldtk(&[("world.ldtk", "world")]); } pub fn check_load_resources(loader: AssetTypeLoader, mut next_state: ResMut<NextState<AppState>>) { diff --git a/game_core/src/entities/mod.rs b/game_core/src/entities/mod.rs index cea322b5d81b9ca3962ace899db460027d5de2d2..4d3f9f25bb2eebcba6f3c8687997c816012c8157 100644 --- a/game_core/src/entities/mod.rs +++ b/game_core/src/entities/mod.rs @@ -19,4 +19,8 @@ mod _plugin { pub use _plugin::EntityPluginSet; pub use graphics::{SimpleAnimation, SimpleAnimationBundle}; -pub use physics::{Acceleration, PhysicsLimits, PhysicsSet, Velocity, GRAVITY_ACC, GRAVITY_VEC}; +pub use physics::{ + Acceleration, PhysicsLimits, PhysicsSet, PlatformerPhysicsHooks, Velocity, GRAVITY_ACC, + GRAVITY_VEC, +}; +pub use player::Player; diff --git a/game_core/src/entities/physics.rs b/game_core/src/entities/physics.rs index 57392d7ae1d2f4e7eb6318fc2a5e2be2e558a54b..fb9d92ce653df4a5acdba6b5c4e1f8e389d1d204 100644 --- a/game_core/src/entities/physics.rs +++ b/game_core/src/entities/physics.rs @@ -1,8 +1,15 @@ use crate::assets::FetchPhysicsConfig; +use crate::entities::Player; use crate::system::run_in_game; use bevy::ecs::query::Has; +use bevy::ecs::system::SystemParam; use bevy::prelude::*; -use bevy_rapier2d::prelude::KinematicCharacterController; +use bevy_rapier2d::geometry::SolverFlags; +use bevy_rapier2d::pipeline::{ContactModificationContextView, PairFilterContextView}; +use bevy_rapier2d::prelude::{ + BevyPhysicsHooks, KinematicCharacterController, KinematicCharacterControllerOutput, +}; +use std::time::Duration; pub const GRAVITY_ACC: f32 = -150.0; pub const GRAVITY_VEC: Vec2 = Vec2::new(0.0, GRAVITY_ACC); @@ -53,6 +60,40 @@ impl Acceleration { } } +#[derive(Copy, Clone, Debug, Component)] +pub struct CoyoteTime { + elapsed: Duration, +} + +impl CoyoteTime { + const LIMIT: Duration = Duration::from_millis(100); + pub fn new() -> CoyoteTime { + Self { + elapsed: Duration::ZERO, + } + } + pub fn tick(&mut self, amount: Duration) -> bool { + self.elapsed = self.elapsed.saturating_add(amount); + self.elapsed >= Self::LIMIT + } +} + +fn tick_coyote_time( + time: Res<Time>, + mut commands: Commands, + mut coyote_query: Query<(Entity, &mut CoyoteTime)>, +) { + let time = time.delta(); + for (entity, mut coyote_time) in &mut coyote_query { + if coyote_time.tick(time) { + commands.entity(entity).despawn_recursive(); + } + } +} + +#[derive(Component)] +pub struct Grounded; + fn apply_acceleration( time: Res<Time>, mut query: Query<( @@ -87,6 +128,51 @@ fn apply_velocity_to_kinematics( } } +fn filter_collisions(mut query: Query<&mut KinematicCharacterControllerOutput>) { + for mut controller in &mut query { + for coll in &controller.collisions { + // coll. + } + } +} + +#[derive(SystemParam)] +pub struct PlatformerPhysicsHooks<'w, 's> { + player_query: Query< + 'w, + 's, + ( + &'static Transform, + &'static KinematicCharacterControllerOutput, + ), + With<Player>, + >, +} +impl<'w, 's> PlatformerPhysicsHooks<'w, 's> { + pub fn fetch_player( + &self, + entity: Entity, + ) -> Option<(&Transform, &KinematicCharacterControllerOutput)> { + self.player_query.get(entity).ok() + } +} + +impl<'w, 's> BevyPhysicsHooks for PlatformerPhysicsHooks<'w, 's> { + fn filter_contact_pair(&self, _context: PairFilterContextView) -> Option<SolverFlags> { + info!("DAMN"); + None + } + + fn filter_intersection_pair(&self, _context: PairFilterContextView) -> bool { + info!("DAMN"); + true + } + + fn modify_solver_contacts(&self, _context: ContactModificationContextView) { + info!("MOIDIFY"); + } +} + pub struct PhysicsPlugin; impl Plugin for PhysicsPlugin { fn build(&self, app: &mut App) { diff --git a/game_core/src/entities/player.rs b/game_core/src/entities/player.rs index 5da2d04a9449af818bf66232b1172f80d04e3dfc..6889edd9b49e2c63f9e45d3c3fbffdb1fbfc661d 100644 --- a/game_core/src/entities/player.rs +++ b/game_core/src/entities/player.rs @@ -22,6 +22,7 @@ pub fn spawn_player(mut commands: Commands, assets: Res<AssetHandles>) { ..Default::default() }, RigidBody::KinematicPositionBased, + ActiveEvents::all(), Collider::cuboid(8.0, 12.0), KinematicCharacterController { snap_to_ground: Some(CharacterLength::Absolute(0.5)), @@ -62,9 +63,11 @@ pub fn handle_input( .map(|phys_config| phys_config.speed) .unwrap_or_default(); + let jump = phys_config.get().map(|p| p.jump).unwrap_or_default(); + for (mut velocity, controller) in &mut query { let delta_y = if controller.grounded && input.is_jump_just_pressed() { - Some(40.0) + Some(jump) } else { None }; diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs index 24db2312511538727a30c6c224c78a429b18b228..ade7124bb368a1404a1dc8b31aaa69febf587790 100644 --- a/game_core/src/lib.rs +++ b/game_core/src/lib.rs @@ -2,3 +2,4 @@ pub mod assets; pub mod debug; pub mod entities; pub mod system; +pub mod world; diff --git a/game_core/src/main.rs b/game_core/src/main.rs index 8f74f60da0aa1258df574bef8e62874c760b251c..76aa876226cc948a705e39bbee1cbb882e35b03f 100644 --- a/game_core/src/main.rs +++ b/game_core/src/main.rs @@ -1,16 +1,23 @@ use bevy::prelude::App; +use game_core::entities::PlatformerPhysicsHooks; +use micro_ldtk::set_ldtk_tile_scale_u32; fn main() { + set_ldtk_tile_scale_u32(18); + App::new() .add_plugins(game_core::system::SystemPluginSet) .add_plugins(game_core::assets::AssetLoadingPlugin) .add_plugins(game_core::assets::ConfigsPlugin) - .add_plugins(game_core::entities::EntityPluginSet) - .add_plugins(game_core::debug::DebugPlugin) - .add_plugins(bevy_rapier2d::plugin::RapierPhysicsPlugin::<()>::pixels_per_meter(18.0)) + .add_plugins(bevy_rapier2d::plugin::RapierPhysicsPlugin::< + PlatformerPhysicsHooks, + >::pixels_per_meter(18.0)) .add_plugins(micro_musicbox::CombinedAudioPlugins::< game_core::assets::AssetHandles, >::new()) .add_plugins(micro_ldtk::MicroLDTKPlugin) + .add_plugins(game_core::entities::EntityPluginSet) + .add_plugins(game_core::world::WorldPlugin) + .add_plugins(game_core::debug::DebugPlugin) .run(); } diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..06bdc9b8aaf2f4b89dc5dcaf06ed79f9f7d2430c --- /dev/null +++ b/game_core/src/world/mod.rs @@ -0,0 +1,24 @@ +mod spawning; + +mod _plugin { + use crate::system::AppState; + use crate::world::spawning::has_level_changed; + use bevy::app::{App, PreUpdate}; + use bevy::prelude::*; + + pub struct WorldPlugin; + impl Plugin for WorldPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + OnEnter(AppState::InGame), + super::spawning::set_initial_level, + ) + .add_systems( + PreUpdate, + super::spawning::spawn_initial_level.run_if(has_level_changed), + ); + } + } +} + +pub use _plugin::WorldPlugin; diff --git a/game_core/src/world/spawning.rs b/game_core/src/world/spawning.rs new file mode 100644 index 0000000000000000000000000000000000000000..5bf9d32468f8ae0983af2e97b32c9e6eb2db8c9b --- /dev/null +++ b/game_core/src/world/spawning.rs @@ -0,0 +1,78 @@ +use crate::assets::AssetHandles; +use crate::entities::Player; +use bevy::log::{error, info}; +use bevy::prelude::{ + ClearColor, Color, Commands, DetectChanges, Query, Res, ResMut, Transform, TransformBundle, + With, +}; +use bevy::sprite::{SpriteSheetBundle, TextureAtlasSprite}; +use bevy_rapier2d::prelude::{Collider, RigidBody}; +use micro_ldtk::{entity_centre, get_ldtk_tile_scale, ActiveLevel, LevelIndex, MapQuery}; + +pub fn set_initial_level(mut commands: Commands) { + commands.insert_resource(ActiveLevel::new("w_0_0")); +} + +pub fn has_level_changed(active: Option<Res<ActiveLevel>>) -> bool { + active.map(|a| a.is_changed()).unwrap_or(false) +} + +pub fn spawn_initial_level( + mut commands: Commands, + mut query: MapQuery, + assets: Res<AssetHandles>, + mut player_q: Query<&mut Transform, With<Player>>, + l: Res<LevelIndex>, +) { + let level = match query.get_active_level() { + Some(level) => level, + None => { + error!("Didn't get the right level!"); + return; + } + }; + + commands.insert_resource(ClearColor( + Color::hex(level.level_ref().bg_color.as_str()).unwrap_or_default(), + )); + + let height = level.height(); + + MapQuery::for_each_layer_of(level, |layer| { + layer.for_each_tile(|x, y, data| { + commands.spawn(SpriteSheetBundle { + texture_atlas: assets.atlas("world"), + sprite: TextureAtlasSprite::new(data.tile.t as usize), + transform: Transform::from_xyz( + x as f32 * get_ldtk_tile_scale(), + height - y as f32 * get_ldtk_tile_scale(), + 50.0, + ), + ..Default::default() + }); + }); + }); + + for ent in MapQuery::get_filtered_entities_of(level, "collider") { + let (px, py) = (ent.pivot[0] - 0.5, ent.pivot[1] - 0.5); + let offset_x = ent.width as f32 * (px as f32 * -1.0); + let offset_y = ent.height as f32 * (py as f32 * -1.0); + + let x = ent.px[0] as f32 + offset_x - get_ldtk_tile_scale() / 2.0; + let y = level.height() - (ent.px[1] as f32 + offset_y) + get_ldtk_tile_scale() / 2.0; + + commands.spawn(( + Collider::cuboid(ent.width as f32 / 2.0, ent.height as f32 / 2.0), + RigidBody::Fixed, + TransformBundle::from_transform(Transform::from_xyz(x, y, 25.0)), + )); + } + + for ent in MapQuery::get_filtered_entities_of(level, "spawn_point") { + let area = entity_centre(level.height_i(), ent); + for mut p in &mut player_q { + p.translation.x = area.0; + p.translation.y = area.1; + } + } +}