Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sync sprite color and attachments. #738

Merged
merged 3 commits into from Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
722 changes: 297 additions & 425 deletions Cargo.lock

Large diffs are not rendered by default.

36 changes: 33 additions & 3 deletions core/src/attachment.rs
Expand Up @@ -28,15 +28,19 @@ pub struct Attachment {
pub offset: Vec3,
/// Synchronize [`AtlasSprite`] animation with entity animation
pub sync_animation: bool,
/// Synchronize [`Sprite`] color with entity color
pub sync_color: bool,
}

/// System to update the transforms of entities with the [`Attachment`] component.
pub fn update_attachments(
time: Res<Time>,
entities: Res<Entities>,
mut transforms: CompMut<Transform>,
mut sprites: CompMut<Sprite>,
attachments: Comp<Attachment>,
invincibles: Comp<Invincibility>,
mut transforms: CompMut<Transform>,
mut atlas_sprites: CompMut<AtlasSprite>,
mut sprites: CompMut<Sprite>,
) {
for (ent, attachment) in entities.iter_with(&attachments) {
let Some(attached_transform) = transforms.get(attachment.entity).copied() else {
Expand Down Expand Up @@ -71,6 +75,7 @@ pub fn update_attachments(
}
}

// Sync animation of attached entity
if attachment.sync_animation {
if let Some((index, attach_atlas)) = atlas_sprites
.get(attachment.entity)
Expand All @@ -81,6 +86,28 @@ pub fn update_attachments(
}
}

// Sync color of attached entity
if attachment.sync_color {
let mut sync_sprite_colors = |alpha| {
if let Some(entity_sprite) = atlas_sprites.get_mut(ent) {
entity_sprite.color.set_a(alpha);
}

if let Some(attachment_sprite) = atlas_sprites.get_mut(attachment.entity) {
attachment_sprite.color.set_a(alpha);
}
};

match invincibles.get(attachment.entity) {
None => sync_sprite_colors(1.0),
Some(_) => sync_sprite_colors(sine_between(
*INVINCIBILITY_ALPHA_RANGE.start(),
*INVINCIBILITY_ALPHA_RANGE.end(),
(time.elapsed().as_millis() / 150) as f32,
)),
}
}

transform.translation += offset;
}
}
Expand All @@ -100,6 +127,8 @@ pub struct PlayerBodyAttachment {
/// Whether or not to automatically play the same animation bank animation as the sprite that it
/// is attached to.
pub sync_animation: bool,
/// Whether or not to automatically sync the color of the attached entity with the player's
pub sync_color: bool,
}

#[derive(Clone, Copy, TypeUlid)]
Expand Down Expand Up @@ -136,8 +165,9 @@ fn update_player_body_attachments(
ent,
Attachment {
entity: player_ent,
offset: current_body_offset.extend(0.0) + body_attachment.offset,
sync_color: body_attachment.sync_color,
sync_animation: body_attachment.sync_animation,
offset: current_body_offset.extend(0.0) + body_attachment.offset,
},
);
}
Expand Down
15 changes: 5 additions & 10 deletions core/src/debug.rs
Expand Up @@ -113,13 +113,11 @@ fn debug_render_colliders(
);

// TODO: Provide a way to change the collider colors
// An orange-y color
const DEFAULT_COLOR: [f32; 4] = [205.0 / 255.0, 94.0 / 255.0, 15.0 / 255.0, 1.0];

paths.insert(
debug_context.path_entity,
Path2d {
color: DEFAULT_COLOR,
// An orange-y color
color: Color::from([205.0 / 255.0, 94.0 / 255.0, 15.0 / 255.0, 1.0]),
points,
line_breaks,
..default()
Expand All @@ -144,10 +142,8 @@ fn debug_render_damage_regions(
// debug lines to keep it upright.
let angle = Vec2::from_angle(-rotation);

// Red color
const COLOR: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
Path2d {
color: COLOR,
color: Color::RED,
points: vec![
angle.rotate(rect.top_left()),
angle.rotate(rect.top_right()),
Expand Down Expand Up @@ -188,10 +184,9 @@ fn debug_render_emote_regions(
// debug lines to keep it upright.
let angle = Vec2::from_angle(-rotation);

// Green color
const COLOR: [f32; 4] = [39.0 / 255.0, 191.0 / 255.0, 68.0 / 255.0, 1.0];
Path2d {
color: COLOR,
// Green color
color: Color::from([39.0 / 255.0, 191.0 / 255.0, 68.0 / 255.0, 1.0]),
points: vec![
angle.rotate(rect.top_left()),
angle.rotate(rect.top_right()),
Expand Down
3 changes: 2 additions & 1 deletion core/src/elements/kick_bomb.rs
Expand Up @@ -211,8 +211,9 @@ fn update_lit_kick_bombs(
entity,
PlayerBodyAttachment {
player,
offset: grab_offset.extend(1.0),
sync_color: false,
sync_animation: false,
offset: grab_offset.extend(1.0),
},
);
}
Expand Down
1 change: 1 addition & 0 deletions core/src/elements/stomp_boots.rs
Expand Up @@ -137,6 +137,7 @@ fn update(
let attachment_ent = entities.create();
let attachment = Attachment {
entity: player,
sync_color: false,
offset: Vec3::ZERO,
sync_animation: true,
};
Expand Down
1 change: 1 addition & 0 deletions core/src/globals.rs
@@ -0,0 +1 @@
pub const INVINCIBILITY_ALPHA_RANGE: std::ops::RangeInclusive<f32> = 0.25..=1.0;
3 changes: 2 additions & 1 deletion core/src/item.rs
Expand Up @@ -129,8 +129,9 @@ pub fn grab_items(
entity,
PlayerBodyAttachment {
player,
offset: grab_offset.extend(PlayerLayers::FIN_Z_OFFSET / 2.0),
sync_animation,
sync_color: false,
offset: grab_offset.extend(PlayerLayers::FIN_Z_OFFSET / 2.0),
},
);
}
Expand Down
3 changes: 2 additions & 1 deletion core/src/lib.rs
Expand Up @@ -28,17 +28,18 @@ pub mod damage;
pub mod debug;
pub mod editor;
pub mod elements;
pub mod globals;
pub mod input;
pub mod item;
pub mod lifetime;
pub mod map;
pub mod math;
pub mod metadata;
pub mod physics;
pub mod player;
pub mod random;
pub mod session;
pub mod testing;
pub mod utils;

/// The target fixed frames-per-second that the game sumulation runs at.
pub const FPS: f32 = 60.0;
Expand Down
4 changes: 2 additions & 2 deletions core/src/lifetime.rs
Expand Up @@ -69,12 +69,12 @@ fn invincibility(
entities: ResMut<Entities>,
mut invincibles: CompMut<Invincibility>,
) {
for (entity, invincible) in &mut entities.iter_with(&mut invincibles) {
for (player_ent, invincible) in &mut entities.iter_with(&mut invincibles) {
invincible.0.tick(time.delta());

if invincible.0.finished() {
commands.add(move |mut invincibles: CompMut<Invincibility>| {
invincibles.remove(entity);
invincibles.remove(player_ent);
});
}
}
Expand Down
30 changes: 13 additions & 17 deletions core/src/metadata/common.rs
@@ -1,15 +1,10 @@
use super::*;
use serde::{Deserialize, Deserializer};
use serde::{ser::SerializeStruct, Deserialize, Deserializer};

#[derive(Clone, Copy, Debug)]
pub struct ColorMeta(pub [f32; 4]);
impl bones_bevy_asset::BonesBevyAssetLoad for ColorMeta {}
#[derive(Clone, Copy, Debug, Default)]
pub struct ColorMeta(pub Color);

impl Default for ColorMeta {
fn default() -> Self {
Self([0.0, 0.0, 0.0, 1.0])
}
}
impl bones_bevy_asset::BonesBevyAssetLoad for ColorMeta {}

impl<'de> Deserialize<'de> for ColorMeta {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
Expand All @@ -25,13 +20,14 @@ impl Serialize for ColorMeta {
where
S: serde::Serializer,
{
let [r, g, b, a] = [
(self.0[0] * 255.0) as u8,
(self.0[1] * 255.0) as u8,
(self.0[2] * 255.0) as u8,
(self.0[3] * 255.0) as u8,
];
serializer.serialize_str(&format!("rgba({r}, {g}, {b}, {a})"))
let [r, g, b, a] = self.0.as_rgba_f32();

let mut color = serializer.serialize_struct("Color", 4)?;
color.serialize_field("r", &r)?;
color.serialize_field("g", &g)?;
color.serialize_field("b", &b)?;
color.serialize_field("a", &a)?;
color.end()
}
}

Expand All @@ -51,7 +47,7 @@ impl<'de> serde::de::Visitor<'de> for ColorVisitor {
csscolorparser::parse(v)
.map(|x| {
let [r, g, b, a] = x.to_array();
[r as f32, g as f32, b as f32, a as f32]
[r as f32, g as f32, b as f32, a as f32].into()
})
.map_err(|e| E::custom(e))?,
))
Expand Down
25 changes: 15 additions & 10 deletions core/src/player.rs
Expand Up @@ -7,6 +7,13 @@ use bones_lib::animation::AnimationBankSprite;
pub use state::*;
use turborand::GenCore;

const PLAYER_COLORS: [Color; 4] = [
Color::RED,
Color::GREEN,
Color::BLUE,
Color::rgb(1.0, 0.0, 1.0),
];

pub fn install(session: &mut GameSession) {
state::install(session);

Expand Down Expand Up @@ -325,12 +332,7 @@ fn player_ai_system(
.map(|x| x.0.as_vec2() * map.tile_size + map.tile_size / 2.0)
.collect(),
thickness: 2.0,
color: [
[1.0, 0.0, 0.0, 1.0],
[0.0, 1.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[1.0, 0.0, 1.0, 1.0],
][player_idx.0],
color: PLAYER_COLORS[player_idx.0],
..default()
},
);
Expand Down Expand Up @@ -374,7 +376,7 @@ fn player_ai_system(
Path2d {
points: vec![pos, pos + vec2(0.0, 4.0)],
thickness: 8.0,
color: [1.0, 0.0, 0.0, 1.0],
color: Color::RED,
..default()
},
);
Expand Down Expand Up @@ -487,9 +489,10 @@ fn hydrate_players(
player_body_attachments.insert(
fin_entity,
PlayerBodyAttachment {
sync_color: true,
sync_animation: false,
player: player_entity,
offset: meta.layers.fin.offset.extend(PlayerLayers::FIN_Z_OFFSET),
sync_animation: false,
},
);

Expand All @@ -513,9 +516,10 @@ fn hydrate_players(
player_body_attachments.insert(
face_entity,
PlayerBodyAttachment {
sync_color: true,
sync_animation: false,
player: player_entity,
offset: meta.layers.face.offset.extend(PlayerLayers::FACE_Z_OFFSET),
sync_animation: false,
},
);

Expand Down Expand Up @@ -587,9 +591,10 @@ fn hydrate_players(
attachments.insert(
sword_ent,
PlayerBodyAttachment {
sync_color: false,
sync_animation: false,
player: player_entity,
offset: grab_offset.extend(1.0),
sync_animation: false,
},
);
}
Expand Down
6 changes: 3 additions & 3 deletions core/src/prelude.rs
@@ -1,8 +1,8 @@
pub use {
crate::{
attachment::*, bullet::*, camera::*, damage::*, debug::*, debug::*, elements::*, input::*,
item::*, item::*, lifetime::*, map::*, math::*, metadata::*, physics::*, player::*,
session::*, MAX_PLAYERS,
attachment::*, bullet::*, camera::*, damage::*, debug::*, debug::*, elements::*,
globals::*, input::*, item::*, item::*, lifetime::*, map::*, metadata::*, physics::*,
player::*, session::*, utils::*, MAX_PLAYERS,
},
bones_bevy_asset::{BevyAssets, BonesBevyAsset, BonesBevyAssetLoad},
bones_lib::prelude::*,
Expand Down
6 changes: 6 additions & 0 deletions core/src/utils.rs
@@ -0,0 +1,6 @@
mod easing;
pub use easing::*;
mod math;
pub use math::*;
mod rect;
pub use rect::*;
68 changes: 68 additions & 0 deletions core/src/utils/easing.rs
@@ -0,0 +1,68 @@
use std::f32::consts::PI;

/// Simple easing calculator
pub struct Ease {
pub ease_in: bool,
pub ease_out: bool,
pub function: EaseFunction,
pub progress: f32,
}

pub enum EaseFunction {
Quadratic,
Cubic,
Sinusoidial,
}

impl Default for Ease {
fn default() -> Self {
Self {
ease_in: true,
ease_out: true,
function: EaseFunction::Quadratic,
progress: 0.0,
}
}
}

impl Ease {
pub fn output(&self) -> f32 {
let mut k = self.progress;

// Reference for easing functions:
// https://echarts.apache.org/examples/en/editor.html?c=line-easing&lang=ts
//
// TODO: Add tests to make sure easings are correct
match (&self.function, self.ease_in, self.ease_out) {
(EaseFunction::Quadratic, true, true) => {
k *= 2.0;
if k < 1.0 {
0.5 * k * k
} else {
k -= 1.0;
-0.5 * (k * (k - 2.0) - 1.0)
}
}
(EaseFunction::Quadratic, true, false) => k * k,
(EaseFunction::Quadratic, false, true) => k * (2.0 - k),
(EaseFunction::Cubic, true, true) => {
k *= 2.0;
if k < 1.0 {
0.5 * k * k * k
} else {
k -= 2.0;
0.5 * (k * k * k + 2.0)
}
}
(EaseFunction::Cubic, true, false) => k * k * k,
(EaseFunction::Cubic, false, true) => {
k -= 1.0;
k * k * k + 1.0
}
(EaseFunction::Sinusoidial, true, true) => 0.5 * (1.0 - f32::cos(PI * k)),
(EaseFunction::Sinusoidial, true, false) => 1.0 - f32::cos((k * PI) / 2.0),
(EaseFunction::Sinusoidial, false, true) => f32::sin((k * PI) / 2.0),
(_, false, false) => k,
}
}
}