diff --git a/README.md b/README.md index 24a0de6..58a330a 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,22 @@ # Bevyjam +## Controls + +* **Move**: arrows +* **Switch character**: Tab +* **Level up**: Enter (when character is white) + ## TODO * name -* scene management * stream audio (with HexoSynthDSP) * color filters * level design -* win -* menu GUI * (?) can jump only from a surface (no mid-air jump) * (?) multiplayer * make WASM build work again (replace hanabi) +* Text is not displayed after menu (on win state and on game over level) +* level reset ## Build diff --git a/src/game.rs b/src/game.rs index b8682a8..bdd90aa 100644 --- a/src/game.rs +++ b/src/game.rs @@ -13,6 +13,9 @@ use bevy_hanabi::*; use bevy_rapier2d::prelude::*; use std::collections::BTreeSet; +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +pub struct LevelId(pub u32); + pub struct GamePlugin; impl Plugin for GamePlugin { @@ -21,24 +24,29 @@ impl Plugin for GamePlugin { .init_resource::() .insert_resource(CurrentLevel(None)) .add_system_set(SystemSet::on_enter(AppState::Game).with_system(setup)) + .add_system_set(SystemSet::on_enter(AppState::Win).with_system(win_setup)) + .add_system_set( + SystemSet::on_exit(AppState::Win).with_system(crate::levels::despawn_level), + ) .add_system_set( SystemSet::on_update(AppState::Game) - .with_system(post_setup_level) + .with_system(crate::levels::post_setup_level) .with_system(keyboard_input_system), ) + .add_system_set(SystemSet::on_update(AppState::Win).with_system(keyboard_input_system)) .add_system_to_stage(CoreStage::PostUpdate, collision_event_system); } } // Events -struct LevelStartupEvent(Entity); +pub struct LevelStartupEvent(pub Entity); // Resources -struct CurrentLevel(Option); +pub struct CurrentLevel(pub Option); -struct CharacterMeshes { +pub struct CharacterMeshes { square: Mesh2dHandle, } @@ -58,20 +66,20 @@ impl FromWorld for CharacterMeshes { // Components -#[derive(Clone, Component, Copy, Eq, Hash, PartialEq)] -struct LevelId(u32); +#[derive(Component)] +pub struct Level; #[derive(Clone, Component, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct CharacterId(u32); +pub struct CharacterId(pub u32); #[derive(Clone, Component, Copy, Eq, Hash, PartialEq)] -struct SelectedCharacterId(Option); +pub struct SelectedCharacterId(pub Option); #[derive(Component)] -struct CharacterIdList(BTreeSet); +pub struct CharacterIdList(pub BTreeSet); #[derive(Clone, Component, PartialEq)] -struct CharacterColor(Color); +pub struct CharacterColor(pub Color); // Systems @@ -80,69 +88,16 @@ fn setup( mut current_level: ResMut, mut level_startup_event: EventWriter, ) { - let level_entity = commands - .spawn() - .insert(LevelId(0)) - .insert(SelectedCharacterId(None)) - .insert(CharacterIdList(BTreeSet::new())) - .id(); - current_level.0 = Some(level_entity); - - commands - .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, -256.0, 0.0))) - .insert(Collider::cuboid(400., 10.)); - - level_startup_event.send(LevelStartupEvent(level_entity)); + let level_id = LevelId(current_level.0.map_or(0, |level_id| level_id.0 + 1)); + crate::levels::setup_level( + &mut commands, + &mut current_level, + &mut level_startup_event, + level_id, + ); } -// This is a bad design, but it's the only way I found -fn post_setup_level( - mut commands: Commands, - character_meshes: Res, - mut effects: ResMut>, - mut materials: ResMut>, - mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, - mut level_startup_event: EventReader, -) { - for LevelStartupEvent(level_entity) in level_startup_event.iter() { - if let Ok((mut selected_character_id, mut character_id_list)) = - level_query.get_mut(*level_entity) - { - spawn_character( - &mut commands, - &character_meshes, - &mut effects, - &mut materials, - &mut selected_character_id, - &mut character_id_list, - Transform::from_xyz(-128., -64., 0.), - Color::RED, - ); - spawn_character( - &mut commands, - &character_meshes, - &mut effects, - &mut materials, - &mut selected_character_id, - &mut character_id_list, - Transform::from_xyz(0., -64., 0.), - Color::GREEN, - ); - spawn_character( - &mut commands, - &character_meshes, - &mut effects, - &mut materials, - &mut selected_character_id, - &mut character_id_list, - Transform::from_xyz(128., -64., 0.), - Color::BLUE, - ); - } - } -} - -fn spawn_character( +pub fn spawn_character( commands: &mut Commands, character_meshes: &Res, effects: &mut ResMut>, @@ -184,6 +139,7 @@ fn spawn_character( transform, ..default() }) + .insert(Level) .insert(character_id) .insert(CharacterColor(color)) .insert(RigidBody::Dynamic) @@ -239,41 +195,48 @@ fn collision_event_system( character_meshes: Res, mut materials: ResMut>, mut effects: ResMut>, - current_level: Res, mut collision_events: EventReader, character_query: Query<(&CharacterId, &CharacterColor, &Transform)>, mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, + mut app_state: ResMut>, ) { - if let Some(level_entity) = current_level.0 { - for collision_event in collision_events.iter() { - if let CollisionEvent::Started(e1, e2, flags) = collision_event { - if flags.is_empty() { - if let ( - Ok((c1_id, c1_color, c1_transform)), - Ok((c2_id, c2_color, _c2_transform)), - ) = (character_query.get(*e1), character_query.get(*e2)) + for collision_event in collision_events.iter() { + if let CollisionEvent::Started(e1, e2, flags) = collision_event { + if flags.is_empty() { + if let (Ok((c1_id, c1_color, c1_transform)), Ok((c2_id, c2_color, _c2_transform))) = + (character_query.get(*e1), character_query.get(*e2)) + { + let (mut selected_character_id, mut character_id_list) = + level_query.single_mut(); + character_id_list.0.remove(c1_id); + character_id_list.0.remove(c2_id); + selected_character_id.0 = None; + + // TODO completely remove particles + commands.entity(*e1).despawn_recursive(); + commands.entity(*e2).despawn_recursive(); + + let new_color = (Vec4::from(c1_color.0) + Vec4::from(c2_color.0)) + .clamp(Vec4::ZERO, Vec4::ONE); + + // If color approximately white + if app_state.current() == &AppState::Game + && 4. - new_color.length_squared() < 0.1 { - let (mut selected_character_id, mut character_id_list) = - level_query.get_mut(level_entity).unwrap(); - character_id_list.0.remove(c1_id); - character_id_list.0.remove(c2_id); - selected_character_id.0 = None; - - // TODO completely remove particles - commands.entity(*e1).despawn_recursive(); - commands.entity(*e2).despawn_recursive(); - - spawn_character( - &mut commands, - &character_meshes, - &mut effects, - &mut materials, - &mut selected_character_id, - &mut character_id_list, - *c1_transform, - (Vec4::from(c1_color.0) + Vec4::from(c2_color.0)).into(), - ); + println!("win"); + app_state.replace(AppState::Win).ok(); } + + spawn_character( + &mut commands, + &character_meshes, + &mut effects, + &mut materials, + &mut selected_character_id, + &mut character_id_list, + *c1_transform, + new_color.into(), + ); } } } @@ -282,7 +245,6 @@ fn collision_event_system( fn keyboard_input_system( keyboard_input: Res>, - current_level: Res, mut characters: Query<( &CharacterId, &mut Velocity, @@ -294,73 +256,92 @@ fn keyboard_input_system( mut effect: Query<&mut ParticleEffect>, dsp_assets: Res, audio: Res