From f3265b994deab7f9ef09760a3552c0073aa17425 Mon Sep 17 00:00:00 2001 From: eri Date: Sun, 10 Dec 2023 22:42:51 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20multiple=20improvemens=20and=20fixes=20?= =?UTF-8?q?=F0=9F=8D=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- assets/sprites/coin.png | Bin 0 -> 3716 bytes assets/sprites/river.png | Bin 0 -> 2618 bytes src/game.rs | 4 +- src/hud.rs | 108 ++++++++++++++++++++++++---------- src/load.rs | 6 ++ src/spirits.rs | 122 ++++++++++++++++++++++----------------- src/tilemap.rs | 4 +- 8 files changed, 158 insertions(+), 92 deletions(-) create mode 100644 assets/sprites/coin.png create mode 100644 assets/sprites/river.png diff --git a/README.md b/README.md index 8069fd3..b651c8d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ a game for the bevy jam 4 - [x] lose timer and visual feedback - [ ] important tweaks (dom maƱ) - - [ ] add sprites + - [x] add sprites - [x] zoom out screen - [x] limited path tiles - [ ] end screen (win/lose) @@ -33,7 +33,7 @@ a game for the bevy jam 4 - [ ] other river types - [ ] other spirit behaviour - [ ] bridges - - [ ] overlay ui (bridge selection, n paths...) + - [x] overlay ui - [ ] better path drawing - [ ] presentation (dom tar) @@ -44,7 +44,7 @@ a game for the bevy jam 4 - [ ] would be nice (???) - [ ] alternate paths - [ ] improve lose timer - - [ ] animations + - [x] animations - [ ] spirit dialogues - [ ] sound and music - [ ] tweening and animation diff --git a/assets/sprites/coin.png b/assets/sprites/coin.png new file mode 100644 index 0000000000000000000000000000000000000000..74471f024f5cce438753045a689c72198d9e0e2e GIT binary patch literal 3716 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 literal 0 HcmV?d00001 diff --git a/src/game.rs b/src/game.rs index ac67f92..d542a8b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -18,7 +18,7 @@ const START_SCORES: [u32; 20] = [ 5000, ]; -const END_SCORES: [u32; 4] = [0, 60, 350, 3000]; +const END_SCORES: [u32; 6] = [0, 60, 120, 350, 1000, 3000]; pub struct CharonPlugin; @@ -127,7 +127,7 @@ fn spawn_start_end( }; // Grow level size every 2 starts (only if we are not at the max size) - if is_start && (*start_spawned + 2) % 3 == 0 && level_size.0.x < MAP_SIZE.x { + if is_start && (*start_spawned + 3) % 4 == 0 && level_size.0.x < MAP_SIZE.x { level_size.0.x += 2; level_size.0.y += 2; if let Ok(mut cam) = cam.get_single_mut() { diff --git a/src/hud.rs b/src/hud.rs index 5bb265c..2d9f1ec 100644 --- a/src/hud.rs +++ b/src/hud.rs @@ -35,41 +35,85 @@ fn init_hud(mut cmd: Commands, assets: Res, mut node: Query 0", - TextStyle { - font: assets.font.clone(), - font_size: 24.0, - color: Color::WHITE, + parent + .spawn(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + left: Val::Px(5.0), + top: Val::Px(5.0), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(4.), + ..default() }, - ) - .with_style(Style { - position_type: PositionType::Absolute, - right: Val::Px(5.0), - top: Val::Px(5.0), ..default() - }), - ScoreText, - )); + }) + .with_children(|tiles| { + tiles.spawn(ImageBundle { + image: UiImage { + texture: assets.river_icon.clone(), + ..default() + }, + style: Style { + width: Val::Px(24.), + ..default() + }, + ..default() + }); - parent.spawn(( - TextBundle::from_section( - "[] 0", - TextStyle { - font: assets.font.clone(), - font_size: 24.0, - color: Color::WHITE, + tiles.spawn(( + TextBundle::from_section( + "0", + TextStyle { + font: assets.font.clone(), + font_size: 24.0, + color: Color::WHITE, + }, + ), + TilesText, + )); + }); + + parent + .spawn(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + right: Val::Px(5.0), + top: Val::Px(5.0), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(4.), + ..default() }, - ) - .with_style(Style { - position_type: PositionType::Absolute, - left: Val::Px(5.0), - top: Val::Px(5.0), ..default() - }), - TilesText, - )); + }) + .with_children(|score| { + score.spawn(( + TextBundle::from_section( + "0", + TextStyle { + font: assets.font.clone(), + font_size: 24.0, + color: Color::WHITE, + }, + ), + ScoreText, + )); + + score.spawn(ImageBundle { + image: UiImage { + texture: assets.coin_icon.clone(), + ..default() + }, + style: Style { + width: Val::Px(24.), + ..default() + }, + ..default() + }); + }); }); } } @@ -82,10 +126,10 @@ fn update_hud( mut tiles_text: Query<&mut Text, (With, Without)>, ) { for mut text in score_text.iter_mut() { - text.sections[0].value = format!("<> {}", score.score); + text.sections[0].value = format!("{}", score.score); } for mut text in tiles_text.iter_mut() { - text.sections[0].value = format!("[] {}", tiles.0); + text.sections[0].value = format!("{}", tiles.0); } } diff --git a/src/load.rs b/src/load.rs index ec7735f..adc43bc 100644 --- a/src/load.rs +++ b/src/load.rs @@ -49,6 +49,12 @@ pub struct GameAssets { #[asset(path = "icons/bevy.png")] pub bevy_icon: Handle, + #[asset(path = "sprites/coin.png")] + pub coin_icon: Handle, + + #[asset(path = "sprites/river.png")] + pub river_icon: Handle, + #[asset(path = "fonts/sans.ttf")] pub font: Handle, } diff --git a/src/spirits.rs b/src/spirits.rs index 22c4b1e..6b364ed 100644 --- a/src/spirits.rs +++ b/src/spirits.rs @@ -39,6 +39,7 @@ impl Plugin for SpiritPlugin { next_tile_spirit, spirit_collision, move_spirit, + animate_spirit, integrate, ) .run_if(in_state(GameState::Play)), @@ -59,7 +60,7 @@ pub struct EndTimer(Timer); impl Default for EndTimer { fn default() -> Self { - Self(Timer::from_seconds(0.3, TimerMode::Repeating)) + Self(Timer::from_seconds(0.25, TimerMode::Repeating)) } } @@ -76,6 +77,7 @@ pub struct Spirit { next_pos: Vec2, selected_end: Option, vel: Vec2, + animate_timer: Timer, } impl Spirit { @@ -88,6 +90,7 @@ impl Spirit { next_pos: curr_pos, selected_end: None, vel: Vec2::ZERO, + animate_timer: Timer::from_seconds(0.5, TimerMode::Repeating), } } } @@ -133,7 +136,7 @@ fn spawn_spirit( // Spawn the entity at the start of the path cmd.spawn(( SpriteSheetBundle { - sprite: TextureAtlasSprite::new(0), + sprite: TextureAtlasSprite::new((rand::random::() % 3) * 2), texture_atlas: spirit_assets.stix.clone(), transform: Transform::from_translation(pos.extend(5.)), ..default() @@ -163,49 +166,47 @@ fn check_lose_count( tilemap: Query<(&TilemapLayer, &TilemapGridSize, &TilemapType, &Transform)>, ) { for (pos, mut start) in start.iter_mut() { - if start.lose_counter > 1. { - let lose_text = start.lose_text; - - if lose_text.is_none() { - for (layer, grid_size, map_type, trans) in tilemap.iter() { - match layer { - TilemapLayer::RiverStix => {} - _ => continue, - } + let lose_text = start.lose_text; - let pos = tile_to_pos(pos, grid_size, map_type, trans); - start.lose_text = Some( - cmd.spawn(( - Text2dBundle { - text: Text::from_section( - "", - TextStyle { - font: assets.font.clone(), - font_size: 48., - color: Color::rgb(0.9, 1.0, 0.6), - }, - ), - transform: Transform::from_translation(pos.extend(10.)), - ..default() - }, - LoseText, - )) - .id(), - ); + if lose_text.is_none() { + for (layer, grid_size, map_type, trans) in tilemap.iter() { + match layer { + TilemapLayer::RiverStix => {} + _ => continue, } - continue; - }; - if let Ok(mut text) = text.get_mut(lose_text.unwrap()) { - let remainder = (LOSE_COUNT - start.lose_counter) / 2. + 1.; - text.sections[0].value = if remainder <= 2. { - "!!!".to_string() - } else if remainder > 15. { - "".to_string() - } else { - remainder.round().to_string() - }; + let pos = tile_to_pos(pos, grid_size, map_type, trans); + start.lose_text = Some( + cmd.spawn(( + Text2dBundle { + text: Text::from_section( + "", + TextStyle { + font: assets.font.clone(), + font_size: 48., + color: Color::rgb(0.9, 0.4, 0.6), + }, + ), + transform: Transform::from_translation(pos.extend(10.)), + ..default() + }, + LoseText, + )) + .id(), + ); } + continue; + }; + + if let Ok(mut text) = text.get_mut(lose_text.unwrap()) { + let remainder = (LOSE_COUNT - start.lose_counter) / 2. - 3.; + text.sections[0].value = if remainder <= 0. { + "!!!".to_string() + } else if remainder > 10. { + "".to_string() + } else { + remainder.round().to_string() + }; } if start.lose_counter >= LOSE_COUNT { state.set(GameState::End); @@ -249,17 +250,7 @@ fn next_tile_spirit( } // TODO: Move this so it updates for other spirits - // Update counts - if let Some(entity) = storage.get(&tile_pos) { - if let Ok((_, mut path)) = paths.get_mut(entity) { - path.count += 1; - } - } - if let Some(entity) = storage.get(&spirit.curr_tile) { - if let Ok((_, mut path)) = paths.get_mut(entity) { - path.count = path.count.saturating_sub(1); - } - } + spirit.curr_tile = tile_pos; // If it arrived at the next tile (or if there is no next tile) @@ -385,6 +376,18 @@ fn next_tile_spirit( spirit.next_pos = tile_to_pos(&spirit.next_tile.unwrap(), grid_size, map_type, map_trans); spirit.selected_end = next.unwrap().2; + + // Update counts + if let Some(entity) = storage.get(&spirit.next_tile.unwrap()) { + if let Ok((_, mut path)) = paths.get_mut(entity) { + path.count += 1; + } + } + if let Some(entity) = storage.get(&spirit.prev_tile.unwrap()) { + if let Ok((_, mut path)) = paths.get_mut(entity) { + path.count = path.count.saturating_sub(1); + } + } } } } @@ -406,7 +409,7 @@ fn clear_end_count( for (entity, spirit) in spirits.iter() { if spirit.curr_tile == *end_pos { cmd.get_entity(entity).unwrap().despawn_recursive(); - end.count -= 1; + end.count = end.count.saturating_sub(1); score.score += 1; break; } @@ -451,3 +454,16 @@ fn integrate(mut spirits: Query<(&Spirit, &mut Transform)>, time: Res