From ee61b507d291f43c280065306acf2990e0f325cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Thulliez?= <43185161+grzi@users.noreply.github.com> Date: Sun, 15 Aug 2021 02:49:44 +0200 Subject: [PATCH] Feat/blink and layer to z (#168) * feat: Blink animation * chore: Rename layer to z in coords and position --- README.md | 18 +++++----- .../bomberman/character_control_system.rs | 14 ++++---- examples/bomberman/main.rs | 4 +-- examples/mario/main.rs | 4 +-- examples/tetris/layer.rs | 8 ++--- examples/tetris/systems/piece_system.rs | 2 +- examples/world-cup/main.rs | 13 +++++--- src/core/components/animations.rs | 33 +++++++++++++------ src/core/components/maths/coordinates.rs | 10 +++--- src/core/components/maths/transform.rs | 14 ++++---- src/core/components/tiles/tilemap.rs | 6 ++-- src/core/systems/animations_system.rs | 24 ++++++++++++++ src/core/systems/ui_text_system.rs | 2 +- src/rendering/gl_representations.rs | 2 +- src/rendering/scion2d.rs | 8 ++--- src/utils/maths.rs | 14 ++++---- 16 files changed, 107 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 6b80c98..cd55044 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,26 @@ Scion is a 2D game library made in rust. ## Why this project ? -Well, firstly because it' a good way to learn. +Well, firstly because it' a good way to learn. +Then, because I wanted to create something with modest goals, focused on ease of use and a short list of principles that also serves as a guideline. -Then because a lot of projects these days focus on adding a lot of feature pretexting feature parity with big editors or game engines. -Here I focus on the features that I really need for my projects. I won't add things just because they are cool, but because I need them in -a game project - -Scion relies on a short list of principles that also serves as a guideline. +Scion is not 'inspired' by any other engine, it's inspired by the needs from real projects. ### Goals - Strong focus on **2D** only. - **Easy** and **Fun** to use. -- Clean and readable source code. -- Tiled integration -- Maybe one day (Editor) +- Clean and readable source code +- I hope one day (Editor) ### Non goals - Ultra/over optimized code and performances. For this, please try other engines or build your own ! - 3D +## Documentations, Tutorials ? + +Yes, they are planned and being worked on. If you want to see scion in action, check the examples for now. ## Why ECS ? @@ -46,7 +45,6 @@ These are the dependencies this project is relying on. Thanks to these awesome c - legion (Entity component system) - ultraviolet (Maths) - ## Showcase Taquin diff --git a/examples/bomberman/character_control_system.rs b/examples/bomberman/character_control_system.rs index 5e10824..d038b26 100644 --- a/examples/bomberman/character_control_system.rs +++ b/examples/bomberman/character_control_system.rs @@ -26,7 +26,7 @@ pub fn controller( .get(posx + 1).unwrap() == &0 { character.pos_x += 1; - animations.run_animation("MOVE_RIGHT".to_string()); + animations.run_animation("MOVE_RIGHT"); } }); inputs.keyboard_mut().on_key_pressed(KeyCode::Left, || { @@ -37,7 +37,7 @@ pub fn controller( .get(posx - 1).unwrap() == &0 { character.pos_x -= 1; - animations.run_animation("MOVE_LEFT".to_string()); + animations.run_animation("MOVE_LEFT"); } }); inputs.keyboard_mut().on_key_pressed(KeyCode::Up, || { @@ -48,7 +48,7 @@ pub fn controller( .get(posx).unwrap() == &0 { character.pos_y -= 1; - animations.run_animation("MOVE_TOP".to_string()); + animations.run_animation("MOVE_TOP"); } }); inputs.keyboard_mut().on_key_pressed(KeyCode::Down, || { @@ -59,15 +59,15 @@ pub fn controller( .get(posx).unwrap() == &0 { character.pos_y += 1; - animations.run_animation("MOVE_BOTTOM".to_string()); + animations.run_animation("MOVE_BOTTOM"); } }); inputs.keyboard_mut().on_key_pressed(KeyCode::Space, || { let mut animations = - Animations::single("EXPLODE".to_string(), bomb_animations::explode()); - animations.run_animation("EXPLODE".to_string()); + Animations::single("EXPLODE", bomb_animations::explode()); + animations.run_animation("EXPLODE"); cmd.push(( - Transform::from_xy_with_layer( + Transform::from_xyz( (character.pos_x * 64) as f32, (character.pos_y * 64) as f32, level_data.tilemap.len() + 1, diff --git a/examples/bomberman/main.rs b/examples/bomberman/main.rs index d5ba069..5bd3210 100644 --- a/examples/bomberman/main.rs +++ b/examples/bomberman/main.rs @@ -74,7 +74,7 @@ impl SimpleGameLayer for BombermanLayer { Some( *level .tilemap - .get(p.layer()) + .get(p.z()) .unwrap() .values .get(p.y()) @@ -106,7 +106,7 @@ fn create_char( level: &Level, ) -> (Transform, Sprite, AssetRef, Animations, BombermanInfos) { ( - Transform::from_xy_with_layer( + Transform::from_xyz( (level.character_x * 64) as f32, (level.character_y * 64) as f32, level.tilemap.len() + 2, diff --git a/examples/mario/main.rs b/examples/mario/main.rs index 9f6c215..b33afec 100644 --- a/examples/mario/main.rs +++ b/examples/mario/main.rs @@ -146,7 +146,7 @@ fn add_background(world: &mut World) { let background = ( Rectangle::new(2560., 640., None), Material::Texture(app_base_path().join("examples/mario/assets/level.png").get()), - Transform::from_xy_with_layer(0., 0., 1), + Transform::from_xyz(0., 0., 1), ); world.push(background); } @@ -160,7 +160,7 @@ fn add_character(world: &mut World) -> Entity { ColliderType::Square(64), ), Square::new(64., None), - Transform::from_xy_with_layer(256., 320., 2), + Transform::from_xyz(256., 320., 2), Material::Color(Color::new_rgb(100, 120, 23)), )) } diff --git a/examples/tetris/layer.rs b/examples/tetris/layer.rs index 95a7c33..e3c4f9a 100644 --- a/examples/tetris/layer.rs +++ b/examples/tetris/layer.rs @@ -62,14 +62,14 @@ fn add_score_ui(world: &mut World) -> Entity { let txt = UiText::new("SCORE".to_string(), font.clone()); let mut transform = Transform::default(); transform.append_translation(394., 250.); - transform.set_layer(2); + transform.set_z(2); world.push((txt, transform)); let txt = UiText::new("".to_string(), font); let mut transform = Transform::default(); transform.append_translation(394., 290.); - transform.set_layer(2); + transform.set_z(2); world.push((txt, transform)) } @@ -78,7 +78,7 @@ fn add_main_ui_mask(world: &mut World) { let image = UiImage::new(544., 704., path); let mut t = Transform::default(); - t.set_layer(0); + t.set_z(0); world.push((image, t)); } @@ -87,7 +87,7 @@ fn add_ui_top_overflow(world: &mut World) { let image = UiImage::new(324., 32., path); let mut t = Transform::default(); - t.set_layer(2); + t.set_z(2); t.append_translation(32., 0.); world.push((image, t)); } diff --git a/examples/tetris/systems/piece_system.rs b/examples/tetris/systems/piece_system.rs index 3355e33..d616550 100644 --- a/examples/tetris/systems/piece_system.rs +++ b/examples/tetris/systems/piece_system.rs @@ -105,7 +105,7 @@ pub fn initialize_bloc( coord_x * BLOC_SIZE + offset.0 * BLOC_SIZE, coord_y * BLOC_SIZE + offset.1 * BLOC_SIZE, ); - bloc_transform.set_layer(1); + bloc_transform.set_z(1); let tuple = ( bloc_transform, Sprite::new(if is_next_bloc { tetris.next_piece.color } else { tetris.active_piece.color }), diff --git a/examples/world-cup/main.rs b/examples/world-cup/main.rs index 80f9679..105187f 100644 --- a/examples/world-cup/main.rs +++ b/examples/world-cup/main.rs @@ -27,19 +27,18 @@ pub struct WorldCup { impl SimpleGameLayer for WorldCup { fn on_start(&mut self, world: &mut World, _resources: &mut Resources) { let animation = Animation::new( - Duration::from_millis(2000), - vec![AnimationModifier::color(60, Color::new(125, 176, 0, 1.0))], + Duration::from_millis(500), + vec![AnimationModifier::blink(1)], false, ); - let animations = Animations::single("color".to_string(), animation); + let animations = Animations::single("color", animation); self.entity = Some(world.push(( Square::new(500., None), Transform::from_xy(100., 100.), Material::Color(Color::new(0, 0, 255, 1.0)), animations, - Hide, ))); world.push(( @@ -56,7 +55,11 @@ impl SimpleGameLayer for WorldCup { let mut entry = world.entry_mut(*self.entity.as_ref().unwrap()).unwrap(); let animations = entry.get_component_mut::().unwrap(); resources.inputs().keyboard_mut().on_key_pressed(KeyCode::P, || { - animations.run_animation("color".to_string()); + if animations.any_animation_running() { + animations.stop_animation("color",false); + }else{ + animations.loop_animation("color"); + } }); } } diff --git a/src/core/components/animations.rs b/src/core/components/animations.rs index ce69265..9d9a0fb 100644 --- a/src/core/components/animations.rs +++ b/src/core/components/animations.rs @@ -17,17 +17,17 @@ impl Animations { pub fn new(animations: HashMap) -> Self { Animations { animations } } /// Create a new Animations component with a single animation provided - pub fn single(name: String, animation: Animation) -> Self { + pub fn single(name: &str, animation: Animation) -> Self { let mut animations = HashMap::new(); - animations.insert(name, animation); + animations.insert(name.to_string(), animation); Animations { animations } } - fn run(&mut self, animation_name: String, status: AnimationStatus) -> bool { - if self.animations.contains_key(animation_name.as_str()) { + fn run(&mut self, animation_name: &str, status: AnimationStatus) -> bool { + if self.animations.contains_key(animation_name) { let mut animation = self .animations - .get_mut(animation_name.as_str()) + .get_mut(animation_name) .expect("An animation has not been found after the security check"); if AnimationStatus::STOPPED == animation.status { animation.status = status; @@ -41,21 +41,21 @@ impl Animations { } /// Runs the animation `name`. Returns true is the animation has been started, false if it does not exist or was already running - pub fn run_animation(&mut self, animation_name: String) -> bool { + pub fn run_animation(&mut self, animation_name: &str) -> bool { self.run(animation_name, AnimationStatus::RUNNING) } /// Runs the animation `name`. Returns true is the animation has been started, false if it does not exist or was already running - pub fn loop_animation(&mut self, animation_name: String) -> bool { + pub fn loop_animation(&mut self, animation_name: &str) -> bool { self.run(animation_name, AnimationStatus::LOOPING) } /// Stops the animation `name`. Returns true is the animation has been stopped, false if it does not exist or was already stopped - pub fn stop_animation(&mut self, animation_name: String, force: bool) -> bool { - if self.animations.contains_key(animation_name.as_str()) { + pub fn stop_animation(&mut self, animation_name: &str, force: bool) -> bool { + if self.animations.contains_key(animation_name) { let mut animation = self .animations - .get_mut(animation_name.as_str()) + .get_mut(animation_name) .expect("An animation has not been found after the security check"); if animation.status == AnimationStatus::LOOPING || animation.status == AnimationStatus::RUNNING @@ -185,6 +185,7 @@ impl AnimationModifier { ) } + /// Convenience function to create a color animation pub fn color(number_of_keyframes: usize, target_color: Color) -> Self { AnimationModifier::new( number_of_keyframes, @@ -192,6 +193,11 @@ impl AnimationModifier { ) } + /// Convenience function to create a blink animation. + pub fn blink(number_of_blinks: usize) -> Self { + AnimationModifier::new(number_of_blinks * 2, AnimationModifierType::Blink) + } + pub(crate) fn compute_keyframe_modifier_for_animation(&mut self, initial_color: &Color) { self.single_keyframe_modifier = match &self.modifier_type { AnimationModifierType::Color { target } => { @@ -220,6 +226,7 @@ pub enum AnimationModifierType { TransformModifier { vector: Option, scale: Option, rotation: Option }, SpriteModifier { tile_numbers: Vec, end_tile_number: usize }, Color { target: Color }, + Blink } pub(crate) enum ComputedKeyframeModifier { @@ -242,6 +249,9 @@ impl Display for AnimationModifier { AnimationModifierType::Color { .. } => { "Color" } + AnimationModifierType::Blink => { + "Blink" + } } ) } @@ -264,6 +274,9 @@ fn compute_animation_keyframe_modifier(modifier: &mut AnimationModifier) { // We can't compute here because we need the initial color None } + AnimationModifierType::Blink => { + None + } }; } diff --git a/src/core/components/maths/coordinates.rs b/src/core/components/maths/coordinates.rs index 70610c5..61a2a2c 100644 --- a/src/core/components/maths/coordinates.rs +++ b/src/core/components/maths/coordinates.rs @@ -3,23 +3,23 @@ pub struct Coordinates { pub(crate) x: f32, pub(crate) y: f32, - pub(crate) layer: usize, + pub(crate) z: usize, } impl Coordinates { - pub fn new(x: f32, y: f32) -> Self { Self { x, y, layer: 0 } } + pub fn new(x: f32, y: f32) -> Self { Self { x, y, z: 0 } } - pub fn new_with_layer(x: f32, y: f32, layer: usize) -> Self { Self { x, y, layer } } + pub fn new_with_z(x: f32, y: f32, layer: usize) -> Self { Self { x, y, z: layer } } pub fn x(&self) -> f32 { self.x } pub fn y(&self) -> f32 { self.y } - pub fn layer(&self) -> usize { self.layer } + pub fn z(&self) -> usize { self.z } pub fn set_x(&mut self, x: f32) { self.x = x } pub fn set_y(&mut self, y: f32) { self.y = y; } - pub fn set_layer(&mut self, layer: usize) { self.layer = layer; } + pub fn set_z(&mut self, z: usize) { self.z = z; } } diff --git a/src/core/components/maths/transform.rs b/src/core/components/maths/transform.rs index b5b6c0e..061af44 100644 --- a/src/core/components/maths/transform.rs +++ b/src/core/components/maths/transform.rs @@ -52,8 +52,8 @@ impl Transform { pub fn from_xy(x: f32, y: f32) -> Self { Self::new(Coordinates::new(x, y), 1., 0.) } - pub fn from_xy_with_layer(x: f32, y: f32, layer: usize) -> Self { - Self::new(Coordinates::new_with_layer(x, y, layer), 1., 0.) + pub fn from_xyz(x: f32, y: f32, z: usize) -> Self { + Self::new(Coordinates::new_with_z(x, y, z), 1., 0.) } /// Append a translation to this transform's position @@ -105,8 +105,8 @@ impl Transform { /// Change the scale value to a new one. pub fn set_scale(&mut self, scale: f32) { self.scale = scale } - /// Change the layer value in the coordinates. - pub fn set_layer(&mut self, layer: usize) { self.local_translation.layer = layer } + /// Change the z value in the coordinates. + pub fn set_z(&mut self, z: usize) { self.local_translation.z = z } /// Configure the minimum global x position for this transform to be min_x pub fn set_min_x(&mut self, min_x: Option) { @@ -152,7 +152,7 @@ impl Transform { let mut new_global = parent_translation.clone(); new_global.x += self.local_translation.x; new_global.y += self.local_translation.y; - new_global.layer = self.local_translation.layer; + new_global.z = self.local_translation.z; self.global_translation = new_global; self.dirty = true; self.handle_bounds(); @@ -198,8 +198,8 @@ impl TransformBuilder { self.transform.global_translation = translation; self } - pub fn with_translation(mut self, x: f32, y: f32, layer: usize) -> Self { - let translation = Coordinates::new_with_layer(x, y, layer); + pub fn with_translation(mut self, x: f32, y: f32, z: usize) -> Self { + let translation = Coordinates::new_with_z(x, y, z); self.transform.local_translation = translation; self.transform.global_translation = translation; self diff --git a/src/core/components/tiles/tilemap.rs b/src/core/components/tiles/tilemap.rs index 067921f..6eae67a 100644 --- a/src/core/components/tiles/tilemap.rs +++ b/src/core/components/tiles/tilemap.rs @@ -65,8 +65,8 @@ impl Tilemap { for x in 0..infos.dimensions.width() { for y in 0..infos.dimensions.height() { - for layer in 0..infos.dimensions.number_of_layers() { - let position = Position::new(x, y, layer); + for z in 0..infos.dimensions.depth() { + let position = Position::new(x, y, z); let tile_infos = tile_resolver(&position); let entity = world @@ -78,7 +78,7 @@ impl Tilemap { if let Some(animation) = tile_infos.animation { world.entry(entity).unwrap().add_component(Animations::single( - "TileAnimation".to_string(), + "TileAnimation", animation, )); } diff --git a/src/core/systems/animations_system.rs b/src/core/systems/animations_system.rs index 4cec8de..71fab6d 100644 --- a/src/core/systems/animations_system.rs +++ b/src/core/systems/animations_system.rs @@ -13,18 +13,22 @@ use crate::core::{ }, resources::time::{TimerType, Timers}, }; +use legion::systems::CommandBuffer; +use crate::core::components::Hide; /// System responsible of applying modifiers data to the dedicated components /// It will use timers to keep track of the animation and will merge keyframes in case /// of long frames. #[system(for_each)] pub(crate) fn animation_executer( + cmd: &mut CommandBuffer, #[resource] timers: &mut Timers, entity: &Entity, animations: &mut Animations, mut transform: Option<&mut Transform>, mut sprite: Option<&mut Sprite>, mut material: Option<&mut Material>, + hide: Option<&Hide> ) { animations .animations_mut() @@ -73,6 +77,9 @@ pub(crate) fn animation_executer( AnimationModifierType::Color { .. } => { apply_color_modifier(material.as_mut(), modifier, timer_cycle) } + AnimationModifierType::Blink => { + apply_blink_modifier(cmd, entity, modifier, timer_cycle, hide) + } } modifier.current_keyframe += timer_cycle; if modifier.current_keyframe >= modifier.number_of_keyframes { @@ -168,3 +175,20 @@ fn apply_color_modifier( } } } + +fn apply_blink_modifier(cmd: &mut CommandBuffer, + entity: &Entity, + modifier: &mut AnimationModifier, + timer_cycle: usize, + hide: Option<&Hide>) { + + if timer_cycle > 0 { + if modifier.will_be_last_keyframe(timer_cycle) { + cmd.remove_component::(*entity); + } else if hide.is_none(){ + cmd.add_component(*entity, Hide); + } else { + cmd.remove_component::(*entity); + } + } +} \ No newline at end of file diff --git a/src/core/systems/ui_text_system.rs b/src/core/systems/ui_text_system.rs index 719e9e0..bd797e7 100644 --- a/src/core/systems/ui_text_system.rs +++ b/src/core/systems/ui_text_system.rs @@ -61,7 +61,7 @@ pub(crate) fn ui_text_bitmap_update( ]; let mut char_transform = Transform::from_xy(index as f32 * (width + 1.), 0.); - char_transform.set_layer(transform.translation().layer()); + char_transform.set_z(transform.translation().z()); cmd.push(( UiTextImage(UiImage::new_with_uv_map( *width as f32, diff --git a/src/rendering/gl_representations.rs b/src/rendering/gl_representations.rs index 9667d91..ef6aebb 100644 --- a/src/rendering/gl_representations.rs +++ b/src/rendering/gl_representations.rs @@ -158,7 +158,7 @@ impl From> for GlUniform { model_trans.append_translation(Vec3 { x: uniform_data.transform.global_translation.x(), y: uniform_data.transform.global_translation.y(), - z: uniform_data.transform.global_translation.layer() as f32, + z: uniform_data.transform.global_translation.z() as f32, }); if !uniform_data.is_ui_component { model_trans.append_translation(Vec3 { diff --git a/src/rendering/scion2d.rs b/src/rendering/scion2d.rs index b6159bf..1c3ac9b 100644 --- a/src/rendering/scion2d.rs +++ b/src/rendering/scion2d.rs @@ -194,7 +194,7 @@ impl Scion2D { gl_vertex.position.append_position( tile_size as f32 * tile.position.x() as f32, tile_size as f32 * tile.position.y() as f32, - tile.position.layer() as f32 / 100., + tile.position.z() as f32 / 100., ) }); vertexes.append(&mut vec); @@ -354,7 +354,7 @@ impl Scion2D { Material::Tileset(tileset) => Some(tileset.texture.clone()), }; render_infos.push(RenderingInfos { - layer: transform.translation().layer(), + layer: transform.translation().z(), range: component.range(), entity: *entity, texture_path: path, @@ -380,7 +380,7 @@ impl Scion2D { _ => None, }; render_infos.push(RenderingInfos { - layer: transform.translation().layer(), + layer: transform.translation().z(), range: 0..(tiles_nb * Sprite::indices().len()) as u32, entity: *entity, texture_path: path, @@ -399,7 +399,7 @@ impl Scion2D { .iter_mut(world) { render_infos.push(RenderingInfos { - layer: transform.translation().layer(), + layer: transform.translation().z(), range: component.range(), entity: *entity, texture_path: component.get_texture_path(), diff --git a/src/utils/maths.rs b/src/utils/maths.rs index f690f19..2adc6dc 100644 --- a/src/utils/maths.rs +++ b/src/utils/maths.rs @@ -3,34 +3,34 @@ pub struct Position { x: usize, y: usize, - layer: usize, + z: usize, } impl Position { - pub fn new(x: usize, y: usize, layer: usize) -> Self { Self { x, y, layer } } + pub fn new(x: usize, y: usize, z: usize) -> Self { Self { x, y, z } } pub fn x(&self) -> usize { self.x } pub fn y(&self) -> usize { self.y } - pub fn layer(&self) -> usize { self.layer } + pub fn z(&self) -> usize { self.z } } /// The standard way to communicate 3D sizes in `Scion` pub struct Dimensions { width: usize, height: usize, - number_of_layers: usize, + depth: usize, } impl Dimensions { - pub fn new(width: usize, height: usize, number_of_layers: usize) -> Self { - Self { width, height, number_of_layers } + pub fn new(width: usize, height: usize, depth: usize) -> Self { + Self { width, height, depth } } pub fn width(&self) -> usize { self.width } pub fn height(&self) -> usize { self.height } - pub fn number_of_layers(&self) -> usize { self.number_of_layers } + pub fn depth(&self) -> usize { self.depth } }