diff --git a/examples/sprite_flip.rs b/examples/sprite_flip.rs new file mode 100644 index 0000000..4bce96c --- /dev/null +++ b/examples/sprite_flip.rs @@ -0,0 +1,50 @@ +// In this program, a sprite can be flipped with X and Y keys. + +use bevy::prelude::*; +use seldom_pixel::prelude::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + resolution: Vec2::splat(512.).into(), + ..default() + }), + ..default() + }), + PxPlugin::::new(UVec2::splat(16), "palette/palette_1.palette.png"), + )) + .insert_resource(ClearColor(Color::BLACK)) + .add_systems(Startup, init) + .add_systems(Update, on_key) + .run(); +} + +fn init(assets: Res, mut commands: Commands) { + commands.spawn(Camera2d); + + // Spawn a sprite + commands.spawn(( + PxSprite(assets.load("sprite/mage.px_sprite.png")), + PxPosition(IVec2::splat(8)), + PxFlip::default(), + )); +} + +fn on_key(input: Res>, mut query: Query<&mut PxFlip>) { + if input.just_pressed(KeyCode::KeyX) { + for mut flip in &mut query { + flip.x = !flip.x; + } + } + + if input.just_pressed(KeyCode::KeyY) { + for mut flip in &mut query { + flip.y = !flip.y; + } + } +} + +#[px_layer] +struct Layer; diff --git a/examples/tilemap_flip.rs b/examples/tilemap_flip.rs new file mode 100644 index 0000000..67e09ec --- /dev/null +++ b/examples/tilemap_flip.rs @@ -0,0 +1,63 @@ +// In this program, a tilemap can be flipped with x and y keys. + +use bevy::prelude::*; +use rand::{thread_rng, Rng}; +use seldom_pixel::prelude::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + resolution: Vec2::splat(512.).into(), + ..default() + }), + ..default() + }), + PxPlugin::::new(UVec2::splat(16), "palette/palette_1.palette.png"), + )) + .insert_resource(ClearColor(Color::BLACK)) + .add_systems(Startup, init) + .add_systems(Update, on_key) + .run(); +} + +fn init(assets: Res, mut commands: Commands) { + commands.spawn(Camera2d); + + let mut tiles = PxTiles::new(UVec2::splat(4)); + let mut rng = thread_rng(); + + for x in 0..4 { + for y in 0..4 { + tiles.set( + Some(commands.spawn((PxTile::from(rng.gen_range(0..4)), + PxFlip::default())).id()), + UVec2::new(x, y), + ); + } + } + + // Spawn the map + commands.spawn(PxMap { + tiles, + tileset: assets.load("tileset/tileset.px_tileset.png"), + }); +} + +fn on_key(input: Res>, mut query: Query<&mut PxFlip>) { + if input.just_pressed(KeyCode::KeyX) { + for mut flip in &mut query { + flip.x = !flip.x; + } + } + + if input.just_pressed(KeyCode::KeyY) { + for mut flip in &mut query { + flip.y = !flip.y; + } + } +} + +#[px_layer] +struct Layer; diff --git a/src/map.rs b/src/map.rs index 914a3c8..0396f3f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -301,13 +301,13 @@ fn extract_maps( } } -pub(crate) type TileComponents = (&'static PxTile, Option<&'static PxFilter>); +pub(crate) type TileComponents = (&'static PxTile, Option<&'static PxFilter>, Option<&'static PxFlip>); fn extract_tiles( tiles: Extract>, mut cmd: Commands, ) { - for ((tile, filter), visibility, entity) in &tiles { + for ((tile, filter, flip), visibility, entity) in &tiles { if !visibility.get() { continue; } @@ -320,5 +320,11 @@ fn extract_tiles( } else { entity.remove::(); } + + if let Some(flip) = flip { + entity.insert(flip.clone()); + } else { + entity.remove::(); + } } } diff --git a/src/prelude.rs b/src/prelude.rs index e384062..770b218 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,7 +25,7 @@ pub use crate::{ math::{Diagonal, Orthogonal}, position::{PxAnchor, PxLayer, PxPosition, PxSubPosition, PxVelocity}, screen::ScreenSize, - sprite::{PxSprite, PxSpriteAsset}, + sprite::{PxSprite, PxSpriteAsset, PxFlip}, text::{PxText, PxTypeface}, ui::PxRect, PxPlugin, diff --git a/src/screen.rs b/src/screen.rs index 54bb7ee..370c9db 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -394,17 +394,17 @@ impl ViewNode for PxRenderNode { // } // } - for (sprite, position, anchor, layer, canvas, animation, filter) in + for (sprite, position, anchor, layer, canvas, animation, filter, flip) in self.sprites.iter_manual(world) { if let Some((_, sprites, _, _, _, _, _)) = layer_contents.get_mut(layer) { - sprites.push((sprite, position, anchor, canvas, animation, filter)); + sprites.push((sprite, position, anchor, canvas, animation, filter, flip)); } else { layer_contents.insert( layer.clone(), ( default(), - vec![(sprite, position, anchor, canvas, animation, filter)], + vec![(sprite, position, anchor, canvas, animation, filter, flip)], default(), default(), default(), @@ -578,7 +578,7 @@ impl ViewNode for PxRenderNode { continue; }; - let Ok((&PxTile { texture }, tile_filter)) = + let Ok((&PxTile { texture }, tile_filter, tile_flip)) = self.tiles.get_manual(world, tile) else { continue; @@ -591,7 +591,7 @@ impl ViewNode for PxRenderNode { draw_spatial( tile, - (), + tile_flip.cloned().unwrap_or_default(), &mut layer_image, (**position + pos.as_ivec2() * tileset.tile_size().as_ivec2()).into(), PxAnchor::BottomLeft, @@ -748,14 +748,14 @@ impl ViewNode for PxRenderNode { // ); // } - for (sprite, position, anchor, canvas, animation, filter) in sprites { + for (sprite, position, anchor, canvas, animation, filter, flip) in sprites { let Some(sprite) = sprite_assets.get(&**sprite) else { continue; }; draw_spatial( sprite, - (), + flip.cloned().unwrap_or_default(), &mut layer_image, *position, *anchor, @@ -896,7 +896,7 @@ impl ViewNode for PxRenderNode { draw_spatial( character, - (), + PxFlip::default(), &mut text_image, IVec2::new(character_x as i32, line_y as i32).into(), PxAnchor::BottomLeft, diff --git a/src/sprite.rs b/src/sprite.rs index 229ee83..8002059 100644 --- a/src/sprite.rs +++ b/src/sprite.rs @@ -108,7 +108,7 @@ impl RenderAsset for PxSpriteAsset { } impl Animation for PxSpriteAsset { - type Param = (); + type Param = PxFlip; fn frame_count(&self) -> usize { self.data.area() / self.frame_size @@ -116,7 +116,7 @@ impl Animation for PxSpriteAsset { fn draw( &self, - _: (), + flip: PxFlip, image: &mut PxImageSliceMut, frame: impl Fn(UVec2) -> usize, filter: impl Fn(u8) -> u8, @@ -124,7 +124,7 @@ impl Animation for PxSpriteAsset { let width = self.data.width(); let image_width = image.image_width(); image.for_each_mut(|slice_i, image_i, pixel| { - if let Some(Some(value)) = self.data.get_pixel(IVec2::new( + let mut v = IVec2::new( (slice_i % width) as i32, ((frame(UVec2::new( (image_i % image_width) as u32, @@ -132,7 +132,14 @@ impl Animation for PxSpriteAsset { )) * self.frame_size + slice_i) / width) as i32, - )) { + ); + if flip.x { + v.x = width as i32 - v.x - 1; + } + if flip.y { + v.y = self.data.height() as i32 - v.y - 1; + } + if let Some(Some(value)) = self.data.get_pixel(v) { pixel.set_value(filter(value)); } }); @@ -148,6 +155,15 @@ impl Spatial for PxSpriteAsset { } } +/// Flips the sprite or tile on the x-axis, y-axis, or both. +#[derive(Component, Default, Clone, Copy, Debug)] +pub struct PxFlip { + /// If true, flips the x-axis. + pub x: bool, + /// If true, flips the y-axis. + pub y: bool, +} + /// A sprite #[derive(Component, Deref, DerefMut, Default, Clone, Debug)] #[require(PxPosition, PxAnchor, DefaultLayer, PxCanvas, Visibility)] @@ -400,6 +416,7 @@ pub(crate) type SpriteComponents = ( &'static PxCanvas, Option<&'static PxAnimation>, Option<&'static PxFilter>, + Option<&'static PxFlip>, ); fn extract_sprites( @@ -407,7 +424,7 @@ fn extract_sprites( sprites: Extract, &InheritedVisibility, RenderEntity)>>, mut cmd: Commands, ) { - for ((sprite, &position, &anchor, layer, &canvas, animation, filter), visibility, id) in + for ((sprite, &position, &anchor, layer, &canvas, animation, filter, flip), visibility, id) in &sprites { if !visibility.get() { @@ -428,6 +445,12 @@ fn extract_sprites( } else { entity.remove::(); } + + if let Some(flip) = flip { + entity.insert(flip.clone()); + } else { + entity.remove::(); + } } }