Skip to content
This repository has been archived by the owner on Oct 24, 2024. It is now read-only.

Commit

Permalink
Daybar UX and emojis (#134)
Browse files Browse the repository at this point in the history
* Winnie goes faster, useful for development

* Introducing emojis

* Refactoring atlas animation generalization to allow for multiple steps

* Inspect label changes description

---------

Co-authored-by: shaperka <[email protected]>
  • Loading branch information
porkbrain and shaperka authored Apr 1, 2024
1 parent 3567ad1 commit 75ba2d7
Show file tree
Hide file tree
Showing 35 changed files with 694 additions and 184 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ common_story = { path = "common/story" }
common_visuals = { path = "common/visuals" }
main_game_lib = { path = "main_game_lib" }

graphviz-rust = "0.7"
graphviz-rust = "0.8"
itertools = "0.12"
lazy_static = "1.4"
logos = "0.14"
Expand Down
2 changes: 1 addition & 1 deletion common/assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! We store e.g. level layouts this way.
pub mod ignore_loader;
mod paths;
pub mod paths;
pub mod ron_loader;
pub mod store;

Expand Down
2 changes: 2 additions & 0 deletions common/assets/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,5 @@ pub mod misc {
pub const LOADING_SCREEN_SPACE_ATLAS: &str =
"misc/loading_screens/space_atlas.png";
}

pub const EMOJI_ATLAS: &str = "misc/emoji_atlas.png";
2 changes: 1 addition & 1 deletion common/loading_screen/src/atlases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl LoadingScreenAtlas {
TextureAtlasLayout::from_grid(tile_size, columns, 1, None, None),
AtlasAnimation {
last: columns - 1,
on_last_frame: AtlasAnimationEnd::Loop,
on_last_frame: AtlasAnimationEnd::LoopIndefinitely,
..default()
},
AtlasAnimationTimer::new_fps(fps),
Expand Down
208 changes: 208 additions & 0 deletions common/story/src/emoji.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! Emoji's are used to express emotions in a visual way.
use bevy::{
math::vec2,
prelude::*,
utils::{Duration, Instant},
};
use common_assets::paths::EMOJI_ATLAS;
use common_visuals::{
AtlasAnimation, AtlasAnimationEnd, AtlasAnimationStep, AtlasAnimationTimer,
};

use crate::Character;

/// Don't replace the current emoji too early, would look weird.
/// Since this is just a visual cue to the player, ignoring is not
/// a big deal.
///
/// If I wanted to be fancy I'd have queued up emojis.
/// But I fancy finishing this game.
const MIN_EMOJI_DURATION: Duration = Duration::from_millis(1000);

/// How large is a single emoji atlas tile.
const EMOJI_SIZE: Vec2 = vec2(24.0, 22.0);

/// System in this set consumes [`DisplayEmojiEvent`]s.
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DisplayEmojiEventConsumer;

/// Send this event to display an emoji.
///
/// This event might end up being ignored if
/// - the same emoji is already displayed
/// - the current emoji has not been displayed for long enough
#[derive(Event)]
pub struct DisplayEmojiEvent {
/// Which emoji to display.
pub emoji: EmojiKind,
/// Each entity can only display one emoji at a time.
/// The emoji will insert itself as a child of this entity unless it
/// already exists.
/// Then it will just update the existing emoji.
pub on_parent: Entity,
/// Who is the parent entity?
/// The entity character mustn't change while the emoji is displayed.
pub offset_for: Character,
}

/// Emojis represent moods or situations that are nice to visually convey to
/// the player.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EmojiKind {
/// Short emoji animation
Tired,
}

enum EmojiFrames {
#[allow(dead_code)]
Empty = 0,

Tired1 = 1,
Tired2 = 2,
Tired3 = 3,
}

#[derive(Component)]
struct Emoji {
kind: EmojiKind,
started_at: Instant,
}

pub(crate) struct Plugin;

impl bevy::app::Plugin for Plugin {
fn build(&self, app: &mut App) {
app.add_event::<DisplayEmojiEvent>().add_systems(
Update,
play_next
.in_set(DisplayEmojiEventConsumer)
.run_if(on_event::<DisplayEmojiEvent>()),
);
}
}

fn play_next(
mut cmd: Commands,
asset_server: Res<AssetServer>,
mut layouts: ResMut<Assets<TextureAtlasLayout>>,
mut events: EventReader<DisplayEmojiEvent>,

mut existing_emoji: Query<(Entity, &Parent, &mut Emoji, &mut TextureAtlas)>,
) {
for event in events.read() {
// this search is O(n) but there never are many emojis
let existing_emoji = existing_emoji
.iter_mut()
.find(|(_, parent, ..)| parent.get() == event.on_parent);

let entity = if let Some((entity, _, mut emoji, mut atlas)) =
existing_emoji
{
if emoji.kind == event.emoji
|| emoji.started_at.elapsed() < MIN_EMOJI_DURATION
{
// let the current emoji play out
continue;
}

// set new emoji
*emoji = Emoji {
kind: event.emoji,
started_at: Instant::now(),
};
atlas.index = event.emoji.initial_frame();

entity
} else {
let entity = cmd
.spawn(Name::new("Emoji"))
.insert(Emoji {
kind: event.emoji,
started_at: Instant::now(),
})
.insert(SpriteBundle {
texture: asset_server.load(EMOJI_ATLAS),
transform: Transform::from_translation(
// the z-index is a dirty hack to make sure the emoji
// is always in front of the character
event.offset_for.emoji_offset().extend(11.0),
),
..default()
})
.insert(TextureAtlas {
layout: layouts.add(TextureAtlasLayout::from_grid(
EMOJI_SIZE,
4,
1,
Some(Vec2::splat(2.0)),
Some(Vec2::splat(1.0)),
)),
index: event.emoji.initial_frame(),
})
.id();
cmd.entity(event.on_parent).add_child(entity);

entity
};

if let Some(first) = event.emoji.animation_first_frame() {
cmd.entity(entity)
.insert(AtlasAnimation {
first,
last: event.emoji.animation_last_frame(),
play: AtlasAnimationStep::Forward,
on_last_frame: AtlasAnimationEnd::DespawnItself,
extra_steps: event.emoji.extra_steps(),
})
.insert(AtlasAnimationTimer::new_fps(event.emoji.fps()));
}
}
}

impl Character {
fn emoji_offset(self) -> Vec2 {
let (size, ..) = self.sprite_atlas().unwrap_or_default();

vec2(0.0, size.y + EMOJI_SIZE.y / 2.0)
}
}

impl EmojiKind {
fn initial_frame(self) -> usize {
match self {
Self::Tired => EmojiFrames::Tired1 as usize,
}
}

/// Only [`Some`] if an animation.
fn animation_first_frame(self) -> Option<usize> {
match self {
Self::Tired => Some(EmojiFrames::Tired2 as usize),
}
}

fn animation_last_frame(self) -> usize {
match self {
Self::Tired => EmojiFrames::Tired3 as usize,
}
}

fn fps(self) -> f32 {
match self {
Self::Tired => 3.0,
}
}

fn extra_steps(self) -> Vec<AtlasAnimationStep> {
match self {
Self::Tired => vec![
AtlasAnimationStep::Backward,
AtlasAnimationStep::Forward,
AtlasAnimationStep::Backward,
AtlasAnimationStep::Forward,
AtlasAnimationStep::Backward,
],
}
}
}
6 changes: 5 additions & 1 deletion common/story/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#![allow(clippy::too_many_arguments)]

pub mod dialog;
pub mod emoji;

use std::time::Duration;

Expand Down Expand Up @@ -75,6 +76,8 @@ pub struct Plugin;

impl bevy::app::Plugin for Plugin {
fn build(&self, app: &mut App) {
app.add_plugins(emoji::Plugin);

app.add_plugins(dialog::fe::portrait::Plugin)
.init_asset_loader::<dialog::loader::Loader>()
.init_asset::<dialog::DialogGraph>();
Expand Down Expand Up @@ -109,7 +112,7 @@ impl Character {
/// How long does it take to move one square.
pub fn default_step_time(self) -> Duration {
match self {
Character::Winnie => Duration::from_millis(35),
Character::Winnie => Duration::from_millis(15),
_ => Duration::from_millis(50),
}
}
Expand Down Expand Up @@ -184,6 +187,7 @@ impl Character {
}

/// Returns arguments to [`TextureAtlasLayout::from_grid`].
#[inline]
fn sprite_atlas(self) -> Option<(Vec2, usize, usize, Vec2)> {
const STANDARD_SIZE: Vec2 = Vec2::new(25.0, 46.0);

Expand Down
45 changes: 25 additions & 20 deletions common/visuals/src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy::prelude::*;
use common_ext::ColorExt;

use crate::{
AtlasAnimation, AtlasAnimationEnd, AtlasAnimationTimer,
AtlasAnimation, AtlasAnimationEnd, AtlasAnimationStep, AtlasAnimationTimer,
BeginAtlasAnimation, BeginAtlasAnimationCond, BeginInterpolationEvent,
ColorInterpolation, Flicker, OnInterpolationFinished,
TranslationInterpolation, UiStyleHeightInterpolation,
Expand All @@ -27,41 +27,46 @@ pub fn advance_atlas_animation(
) {
for (entity, animation, mut timer, mut atlas, mut visibility) in &mut query
{
timer.tick(time.delta());
if timer.just_finished() {
if animation.is_on_last_frame(&atlas) {
timer.inner.tick(time.delta());
if !timer.inner.just_finished() {
continue;
}

match animation.next_step_index_and_frame(&atlas, timer.current_step) {
Some((step_index, frame)) => {
atlas.index = frame;
timer.current_step = step_index;
}
None => {
match &animation.on_last_frame {
AtlasAnimationEnd::RemoveTimerAndHide => {
AtlasAnimationEnd::RemoveTimerAndHideAndReset => {
cmd.entity(entity).remove::<AtlasAnimationTimer>();
*visibility = Visibility::Hidden;
atlas.index = if animation.reversed {
animation.last
} else {
animation.first
atlas.index = match animation.play {
AtlasAnimationStep::Forward => animation.first,
AtlasAnimationStep::Backward => animation.last,
};
}
AtlasAnimationEnd::DespawnItself => {
cmd.entity(entity).despawn();
}
AtlasAnimationEnd::RemoveTimer => {
cmd.entity(entity).remove::<AtlasAnimationTimer>();
}
AtlasAnimationEnd::Custom { with: Some(fun) } => {
fun(entity, &mut atlas, &mut visibility, &mut cmd);
fun(&mut cmd, entity, &mut atlas, &mut visibility);
}
AtlasAnimationEnd::Custom { with: None } => {
// nothing happens
}
AtlasAnimationEnd::Loop => {
atlas.index = if animation.reversed {
animation.last
} else {
animation.first
AtlasAnimationEnd::LoopIndefinitely => {
atlas.index = match animation.play {
AtlasAnimationStep::Forward => animation.first,
AtlasAnimationStep::Backward => animation.last,
};
}
}
} else if animation.reversed {
atlas.index -= 1;
} else {
atlas.index += 1;
};
}
}
}
}
Expand Down
Loading

0 comments on commit 75ba2d7

Please sign in to comment.