diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index 6e227d1d4..0dc1b181f 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -377,15 +377,18 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream { #(#include_paths)* - const PALETTES: &[Palette16] = &[ + #[link_section = ".sprites.palettes"] + static PALETTES: &[Palette16] = &[ #(#palette_data),* ]; - pub const SPRITES: &[Sprite] = &[ + #[link_section = ".sprites.sprites"] + static SPRITES: &[Sprite] = &[ #(#sprites),* ]; - const TAGS: &TagMap = &TagMap::new( + #[link_section = ".sprites.tags"] + static TAGS: &TagMap = &TagMap::new( &[ #(#tags),* ] diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 9d56ce686..dc4b6dae6 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -4,14 +4,14 @@ use agb::{ display::tiled::{TileFormat, TileSet, TileSetting, TiledMap}, display::{ - object::{OamManaged, Object, Size, Sprite}, + object::{OamDisplay, ObjectUnmanaged, Size, Sprite, SpriteLoader, SpriteVram}, palette16::Palette16, tiled::RegularBackgroundSize, HEIGHT, WIDTH, }, input::Button, }; -use core::convert::TryInto; +use agb_fixnum::{num, Num, Vector2D}; #[derive(PartialEq, Eq)] enum State { @@ -20,15 +20,23 @@ enum State { Flapping, } -struct Character<'a> { - object: Object<'a>, - position: Vector2D, - velocity: Vector2D, +struct Character { + sprite: SpriteVram, + position: Vector2D>, + velocity: Vector2D>, + flipped: bool, } -struct Vector2D { - x: i32, - y: i32, +impl OamDisplay for &Character { + fn set_in( + self, + oam: &mut agb::display::object::OamIterator, + ) -> agb::display::object::OamDisplayResult { + ObjectUnmanaged::new(self.sprite.clone()) + .set_position((self.position + (num!(0.5), num!(0.5)).into()).floor() - (4, 4).into()) + .set_hflip(self.flipped) + .set_in(oam) + } } fn tile_is_collidable(tile: u16) -> bool { @@ -36,11 +44,26 @@ fn tile_is_collidable(tile: u16) -> bool { masked == 0 || masked == 4 } +fn position_is_colliding(tiles: &[[u16; 32]; 32], position: Vector2D>) -> bool { + fn inner_check(tiles: &[[u16; 32]; 32], position: Vector2D>) -> Option { + let position = position.floor() / 8; + Some(tile_is_collidable( + *tiles.get(position.y as usize)?.get(position.x as usize)?, + )) + } + + inner_check(tiles, position).unwrap_or(true) +} + fn frame_ranger(count: u32, start: u32, end: u32, delay: u32) -> usize { (((count / delay) % (end + 1 - start)) + start) as usize } #[agb::entry] +fn entry(gba: agb::Gba) -> ! { + main(gba); +} + fn main(mut gba: agb::Gba) -> ! { let map_as_grid: &[[u16; 32]; 32] = unsafe { (&MAP_MAP as *const [u16; 1024] as *const [[u16; 32]; 32]) @@ -74,46 +97,39 @@ fn main(mut gba: agb::Gba) -> ! { background.show(); background.commit(&mut vram); - let object = gba.display.object.get_managed(); + let (mut oam, mut sprite_loader) = gba.display.object.get(); - let sprite = object.sprite(&CHICKEN_SPRITES[0]); + let sprite = sprite_loader.get_vram_sprite(&CHICKEN_SPRITES[0]); let mut chicken = Character { - object: object.object(sprite), + sprite, position: Vector2D { - x: (6 * 8) << 8, - y: ((7 * 8) - 4) << 8, + x: (6 * 8).into(), + y: ((7 * 8) - 4).into(), }, - velocity: Vector2D { x: 0, y: 0 }, + velocity: Vector2D { + x: 0.into(), + y: 0.into(), + }, + flipped: false, }; - chicken - .object - .set_x((chicken.position.x >> 8).try_into().unwrap()); - chicken - .object - .set_y((chicken.position.y >> 8).try_into().unwrap()); - chicken.object.show(); - - object.commit(); - - let acceleration = 1 << 4; - let gravity = 1 << 4; + let acceleration = num!(0.0625); + let gravity = num!(0.0625); let flapping_gravity = gravity / 3; - let jump_velocity = 1 << 9; + let jump_velocity = num!(2.); let mut frame_count = 0; let mut frames_off_ground = 0; - let terminal_velocity = (1 << 8) / 2; + let terminal_velocity = num!(0.5); loop { - vblank.wait_for_vblank(); frame_count += 1; input.update(); // Horizontal movement - chicken.velocity.x += (input.x_tri() as i32) * acceleration; - chicken.velocity.x = 61 * chicken.velocity.x / 64; + chicken.velocity.x += acceleration * (input.x_tri() as i32); + chicken.velocity.x = (chicken.velocity.x * 61) / 64; // Update position based on collision detection let state = handle_collision( @@ -137,103 +153,89 @@ fn main(mut gba: agb::Gba) -> ! { } restrict_to_screen(&mut chicken); - update_chicken_object(&mut chicken, &object, state, frame_count); - - object.commit(); + update_chicken_object(&mut chicken, &mut sprite_loader, state, frame_count); + vblank.wait_for_vblank(); + chicken.set_in(&mut oam.iter()); } } fn update_chicken_object( - chicken: &'_ mut Character<'_>, - gfx: &OamManaged, + chicken: &mut Character, + sprite_loader: &mut SpriteLoader, state: State, frame_count: u32, ) { - if chicken.velocity.x > 1 { - chicken.object.set_hflip(false); - } else if chicken.velocity.x < -1 { - chicken.object.set_hflip(true); + match chicken.velocity.x.to_raw().signum() { + 1 => chicken.flipped = false, + -1 => chicken.flipped = true, + _ => {} } + match state { State::Ground => { - if chicken.velocity.x.abs() > 1 << 4 { - chicken - .object - .set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)])); + if chicken.velocity.x.abs() > num!(0.0625) { + chicken.sprite = sprite_loader + .get_vram_sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]); } else { - chicken.object.set_sprite(gfx.sprite(&CHICKEN_SPRITES[0])); + chicken.sprite = sprite_loader.get_vram_sprite(&CHICKEN_SPRITES[0]); } } State::Upwards => {} State::Flapping => { - chicken - .object - .set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)])); + chicken.sprite = + sprite_loader.get_vram_sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]); } } - - let x: u16 = (chicken.position.x >> 8).try_into().unwrap(); - let y: u16 = (chicken.position.y >> 8).try_into().unwrap(); - - chicken.object.set_x(x - 4); - chicken.object.set_y(y - 4); } fn restrict_to_screen(chicken: &mut Character) { - if chicken.position.x > (WIDTH - 8 + 4) << 8 { - chicken.velocity.x = 0; - chicken.position.x = (WIDTH - 8 + 4) << 8; - } else if chicken.position.x < 4 << 8 { - chicken.velocity.x = 0; - chicken.position.x = 4 << 8; + if chicken.position.x > (WIDTH - 8 + 4).into() { + chicken.velocity.x = 0.into(); + chicken.position.x = (WIDTH - 8 + 4).into(); + } else if chicken.position.x < 4.into() { + chicken.velocity.x = 0.into(); + chicken.position.x = 4.into(); } - if chicken.position.y > (HEIGHT - 8 + 4) << 8 { - chicken.velocity.y = 0; - chicken.position.y = (HEIGHT - 8 + 4) << 8; - } else if chicken.position.y < 4 << 8 { - chicken.velocity.y = 0; - chicken.position.y = 4 << 8; + if chicken.position.y > (HEIGHT - 8 + 4).into() { + chicken.velocity.y = 0.into(); + chicken.position.y = (HEIGHT - 8 + 4).into(); + } else if chicken.position.y < 4.into() { + chicken.velocity.y = 0.into(); + chicken.position.y = 4.into(); } } fn handle_collision( chicken: &mut Character, map_as_grid: &[[u16; 32]; 32], - gravity: i32, - flapping_gravity: i32, - terminal_velocity: i32, + gravity: Num, + flapping_gravity: Num, + terminal_velocity: Num, ) -> State { - let mut new_chicken_x = chicken.position.x + chicken.velocity.x; - let mut new_chicken_y = chicken.position.y + chicken.velocity.y; - - let tile_x = ((new_chicken_x >> 8) / 8) as usize; - let tile_y = ((new_chicken_y >> 8) / 8) as usize; - - let left = (((new_chicken_x >> 8) - 4) / 8) as usize; - let right = (((new_chicken_x >> 8) + 4) / 8) as usize; - let top = (((new_chicken_y >> 8) - 4) / 8) as usize; - let bottom = (((new_chicken_y >> 8) + 4) / 8) as usize; - - if chicken.velocity.x < 0 && tile_is_collidable(map_as_grid[tile_y][left]) { - new_chicken_x = (((left + 1) * 8 + 4) << 8) as i32; - chicken.velocity.x = 0; - } else if chicken.velocity.x > 0 && tile_is_collidable(map_as_grid[tile_y][right]) { - new_chicken_x = ((right * 8 - 4) << 8) as i32; - chicken.velocity.x = 0; + let mut new_chicken_positon = chicken.position + chicken.velocity; + + if (chicken.velocity.x < 0.into() + && position_is_colliding(map_as_grid, new_chicken_positon - (4, 0).into())) + || (chicken.velocity.x > 0.into() + && position_is_colliding(map_as_grid, new_chicken_positon + (4, 0).into())) + { + new_chicken_positon.x = (new_chicken_positon.x.floor() / 8 * 8 + 4).into(); + chicken.velocity.x = 0.into(); } - if chicken.velocity.y < 0 && tile_is_collidable(map_as_grid[top][tile_x]) { - new_chicken_y = ((((top + 1) * 8 + 4) << 8) + 4) as i32; - chicken.velocity.y = 0; - } else if chicken.velocity.y > 0 && tile_is_collidable(map_as_grid[bottom][tile_x]) { - new_chicken_y = ((bottom * 8 - 4) << 8) as i32; - chicken.velocity.y = 0; + if (chicken.velocity.y < 0.into() + && position_is_colliding(map_as_grid, new_chicken_positon - (0, 4).into())) + || (chicken.velocity.y > 0.into() + && position_is_colliding(map_as_grid, new_chicken_positon + (0, 4).into())) + { + new_chicken_positon.y = (new_chicken_positon.y.floor() / 8 * 8 + 4).into(); + chicken.velocity.y = 0.into(); } let mut air_animation = State::Ground; - if !tile_is_collidable(map_as_grid[bottom][tile_x]) { - if chicken.velocity.y < 0 { + if !position_is_colliding(map_as_grid, new_chicken_positon + (0, 4).into()) { + if chicken.velocity.y < 0.into() { air_animation = State::Upwards; chicken.velocity.y += gravity; } else { @@ -245,8 +247,7 @@ fn handle_collision( } } - chicken.position.x = new_chicken_x; - chicken.position.y = new_chicken_y; + chicken.position = new_chicken_positon; air_animation } diff --git a/agb/examples/object_text_render.rs b/agb/examples/object_text_render.rs index d53237249..58ec533e1 100644 --- a/agb/examples/object_text_render.rs +++ b/agb/examples/object_text_render.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - object::{ChangeColour, ObjectTextRender, PaletteVram, Size, TextAlignment}, + object::{ChangeColour, OamDisplay, ObjectTextRender, PaletteVram, Size, TextAlignment}, palette16::Palette16, Font, HEIGHT, WIDTH, }, @@ -22,7 +22,7 @@ fn entry(gba: agb::Gba) -> ! { } fn main(mut gba: agb::Gba) -> ! { - let (mut unmanaged, _sprites) = gba.display.object.get_unmanaged(); + let (mut unmanaged, _sprites) = gba.display.object.get(); loop { let mut palette = [0x0; 16]; @@ -74,7 +74,7 @@ fn main(mut gba: agb::Gba) -> ! { vblank.wait_for_vblank(); input.update(); let oam = &mut unmanaged.iter(); - wr.commit(oam); + wr.set_at(oam, (0, HEIGHT - 40).into()); let start = timer.value(); if frame % 4 == 0 { @@ -84,7 +84,7 @@ fn main(mut gba: agb::Gba) -> ! { line_done = false; wr.pop_line(); } - wr.update((0, HEIGHT - 40).into()); + wr.update(); let end = timer.value(); frame += 1; diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index 161e4ed86..7ac4b4a83 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -3,127 +3,113 @@ extern crate alloc; -use agb::display::{ - affine::AffineMatrix, - object::{self, Graphics, OamManaged, Sprite, TagMap}, -}; use agb::fixnum::num; +use agb::input::Button; +use agb::{ + display::{ + affine::AffineMatrix, + object::{ + self, AffineMode, Graphics, OamUnmanaged, ObjectUnmanaged, Sprite, SpriteLoader, TagMap, + }, + }, + input::ButtonController, + interrupt::VBlank, +}; use agb_fixnum::Num; use alloc::vec::Vec; -const GRAPHICS: &Graphics = agb::include_aseprite!( +static GRAPHICS: &Graphics = agb::include_aseprite!( "examples/gfx/objects.aseprite", "examples/gfx/boss.aseprite", "examples/gfx/wide.aseprite", "examples/gfx/tall.aseprite" ); -const SPRITES: &[Sprite] = GRAPHICS.sprites(); -const TAG_MAP: &TagMap = GRAPHICS.tags(); - -fn all_sprites(gfx: &OamManaged, rotation_speed: Num) { - let mut input = agb::input::ButtonController::new(); - let mut objs = Vec::new(); - - let mut rotation: Num = num!(0.); - +static SPRITES: &[Sprite] = GRAPHICS.sprites(); +static TAG_MAP: &TagMap = GRAPHICS.tags(); + +fn all_sprites( + sprite_loader: &mut SpriteLoader, + rotation: Num, + offset: usize, +) -> Vec { let rotation_matrix = AffineMatrix::from_rotation(rotation); let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping()); - for y in 0..9 { - for x in 0..14 { - let mut obj = gfx.object_sprite(&SPRITES[0]); - obj.set_affine_matrix(matrix.clone()); - obj.show_affine(object::AffineMode::Affine); - obj.set_position((x * 16 + 8, y * 16 + 8).into()); - objs.push(obj); - } - } - - let mut count = 0; - let mut image = 0; - - let vblank = agb::interrupt::VBlank::get(); - - loop { - vblank.wait_for_vblank(); - input.update(); - - if input.is_just_pressed(agb::input::Button::A) { - break; - } - - rotation += rotation_speed; - let rotation_matrix = AffineMatrix::from_rotation(rotation); - - let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping()); - - for obj in objs.iter_mut() { - obj.set_affine_matrix(matrix.clone()); - } - - count += 1; - - if count % 5 == 0 { - image += 1; - image %= SPRITES.len(); - for (i, obj) in objs.iter_mut().enumerate() { - let this_image = (image + i) % SPRITES.len(); - obj.set_sprite(gfx.sprite(&SPRITES[this_image])); - } - } - gfx.commit(); - } + (0..(9 * 14)) + .map(|idx| { + let x = idx % 14; + let y = idx / 14; + + (idx, x as i32, y as i32) + }) + .map(move |(idx, x, y)| { + let sprite_vram = + sprite_loader.get_vram_sprite(&SPRITES[(idx + offset) % SPRITES.len()]); + ObjectUnmanaged::new(sprite_vram) + .set_affine_matrix(matrix.clone()) + .show_affine(AffineMode::Affine) + .set_position((x * 16 + 8, y * 16 + 8).into()) + }) + .collect() } -fn all_tags(gfx: &OamManaged) { - let mut input = agb::input::ButtonController::new(); - let mut objs = Vec::new(); - - for (i, v) in TAG_MAP.values().enumerate() { - let x = (i % 7) as i32; - let y = (i / 7) as i32; - let sprite = v.sprite(0); - let (size_x, size_y) = sprite.size().to_width_height(); - let (size_x, size_y) = (size_x as i32, size_y as i32); - let mut obj = gfx.object_sprite(sprite); - obj.show(); - obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()); - objs.push((obj, v)); - } - - let mut count = 0; - let mut image = 0; +fn all_tags(sprite_loader: &mut SpriteLoader, image: usize) -> Vec { + TAG_MAP + .values() + .enumerate() + .map(move |(i, v)| { + let x = (i % 7) as i32; + let y = (i / 7) as i32; + let sprite = v.animation_sprite(image); + let (size_x, size_y) = sprite.size().to_width_height(); + let (size_x, size_y) = (size_x as i32, size_y as i32); + let sprite_vram = sprite_loader.get_vram_sprite(sprite); + ObjectUnmanaged::new(sprite_vram) + .set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()) + }) + .collect() +} - let vblank = agb::interrupt::VBlank::get(); +fn run_with_sprite_generating_fn( + oam: &mut OamUnmanaged, + sprite_loader: &mut SpriteLoader, + mut f: F, +) where + F: FnMut(&mut SpriteLoader) -> Vec, +{ + let mut button = ButtonController::new(); + let vblank = VBlank::get(); loop { + let sprites = f(sprite_loader); vblank.wait_for_vblank(); - - input.update(); - - if input.is_just_pressed(agb::input::Button::A) { + button.update(); + if button.is_just_pressed(Button::A) { break; } - - count += 1; - - if count % 5 == 0 { - image += 1; - for (obj, tag) in objs.iter_mut() { - obj.set_sprite(gfx.sprite(tag.animation_sprite(image))); - } - gfx.commit(); - } + oam.iter().set(sprites); } } #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let gfx = gba.display.object.get_managed(); + let (mut oam_manager, mut sprite_loader) = gba.display.object.get(); loop { - all_tags(&gfx); - all_sprites(&gfx, num!(0.)); - all_sprites(&gfx, num!(0.01)); + let mut count = 0; + run_with_sprite_generating_fn(&mut oam_manager, &mut sprite_loader, |sprite_loader| { + count += 1; + all_tags(sprite_loader, count / 5) + }); + run_with_sprite_generating_fn(&mut oam_manager, &mut sprite_loader, |sprite_loader| { + count += 1; + all_sprites(sprite_loader, num!(0.), count / 5) + }); + let mut rotation = num!(0.); + run_with_sprite_generating_fn(&mut oam_manager, &mut sprite_loader, |sprite_loader| { + count += 1; + rotation += num!(0.01); + all_sprites(sprite_loader, rotation, count / 5) + }); } } diff --git a/agb/gba.ld b/agb/gba.ld index 525260d9a..5b95adca2 100644 --- a/agb/gba.ld +++ b/agb/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/agb/gba_mb.ld b/agb/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/agb/gba_mb.ld +++ b/agb/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/agb/src/arena.rs b/agb/src/arena.rs deleted file mode 100644 index 976f44bb5..000000000 --- a/agb/src/arena.rs +++ /dev/null @@ -1,77 +0,0 @@ -use core::{alloc::Allocator, mem::ManuallyDrop}; - -use alloc::{alloc::Global, vec::Vec}; - -union ArenaItem { - free: Option, - occupied: ManuallyDrop, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct ArenaKey(usize); - -pub struct Arena { - tip: Option, - data: Vec, A>, - inserted: usize, -} - -impl Arena { - pub const fn new() -> Self { - Self::new_in(Global) - } -} - -impl Arena { - pub const fn new_in(alloc: A) -> Self { - Self { - tip: None, - data: Vec::new_in(alloc), - inserted: 0, - } - } - - pub unsafe fn insert(&mut self, value: T) -> ArenaKey { - self.inserted += 1; - match self.tip { - Some(tip) => { - self.tip = self.data[tip.0].free; - self.data[tip.0].occupied = ManuallyDrop::new(value); - tip - } - None => { - self.data.push(ArenaItem { - occupied: ManuallyDrop::new(value), - }); - ArenaKey(self.data.len() - 1) - } - } - } - - pub unsafe fn remove(&mut self, key: ArenaKey) { - self.inserted = self - .inserted - .checked_sub(1) - .expect("removed more items than exist in here!"); - - unsafe { - core::mem::ManuallyDrop::::drop(&mut self.data[key.0].occupied); - } - - self.data[key.0].free = self.tip; - self.tip = Some(key); - } - - pub unsafe fn get(&self, key: ArenaKey) -> &T { - &self.data[key.0].occupied - } -} - -impl Drop for Arena { - fn drop(&mut self) { - assert_eq!( - self.inserted, 0, - "must remove all elements from arena before dropping it!" - ); - } -} diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 76d9e5a88..314cdb652 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -7,7 +7,7 @@ use video::Video; use self::{ blend::Blend, - object::{initilise_oam, OamManaged, OamUnmanaged, SpriteLoader}, + object::{initilise_oam, OamUnmanaged, SpriteLoader}, window::Windows, }; @@ -84,22 +84,10 @@ pub struct Display { pub struct ObjectDistribution; impl ObjectDistribution { - pub fn get_unmanaged(&mut self) -> (OamUnmanaged<'_>, SpriteLoader) { + pub fn get(&mut self) -> (OamUnmanaged<'_>, SpriteLoader) { unsafe { initilise_oam() }; (OamUnmanaged::new(), SpriteLoader::new()) } - - pub fn get_managed(&mut self) -> OamManaged<'_> { - unsafe { initilise_oam() }; - OamManaged::new() - } - - /// The old name for [`get_managed`][ObjectDistribution::get_managed] kept around for easier migration. - /// This will be removed in a future release. - #[deprecated = "use get_managed to get the managed oam instead"] - pub fn get(&mut self) -> OamManaged<'_> { - self.get_managed() - } } #[non_exhaustive] diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 85df2a554..2556304f8 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -10,7 +10,6 @@ mod affine; mod font; -mod managed; mod sprites; mod unmanaged; @@ -20,8 +19,9 @@ pub use sprites::{ }; pub use affine::AffineMatrixInstance; -pub use managed::{OamManaged, Object}; -pub use unmanaged::{AffineMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; +pub use unmanaged::{ + AffineMode, OamDisplay, OamDisplayResult, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged, +}; pub use font::{ChangeColour, ObjectTextRender, TextAlignment}; @@ -29,11 +29,6 @@ use super::DISPLAY_CONTROL; const OBJECT_ATTRIBUTE_MEMORY: *mut u16 = 0x0700_0000 as *mut u16; -#[deprecated = "use OamManaged directly instead"] -/// The old name for [`OamManaged`] kept around for easier migration. -/// This will be removed in a future release. -pub type ObjectController<'a> = OamManaged<'a>; - pub(super) unsafe fn initilise_oam() { for i in 0..128 { let ptr = (OBJECT_ATTRIBUTE_MEMORY).add(i * 4); diff --git a/agb/src/display/object/font.rs b/agb/src/display/object/font.rs index a9d05da57..db14b8fc0 100644 --- a/agb/src/display/object/font.rs +++ b/agb/src/display/object/font.rs @@ -10,7 +10,7 @@ use self::{ renderer::{Configuration, WordRender}, }; -use super::{OamIterator, ObjectUnmanaged, PaletteVram, Size, SpriteVram}; +use super::{OamDisplay, OamIterator, ObjectUnmanaged, PaletteVram, Size, SpriteVram}; mod preprocess; mod renderer; @@ -214,11 +214,11 @@ impl BufferedRender<'_> { /// /// use core::fmt::Write; /// -/// const EXAMPLE_FONT: Font = agb::include_font!("examples/font/yoster.ttf", 12); +/// static EXAMPLE_FONT: Font = agb::include_font!("examples/font/yoster.ttf", 12); /// /// #[agb::entry] /// fn main(gba: &mut agb::Gba) -> ! { -/// let (mut unmanaged, _) = gba.display.object.get_unmanaged(); +/// let (mut unmanaged, _) = gba.display.object.get(); /// let vblank = agb::interrupt::VBlank::get(); /// /// let mut palette = [0x0; 16]; @@ -259,7 +259,6 @@ impl<'font> ObjectTextRender<'font> { positions: VecDeque::new(), line_capacity: VecDeque::new(), objects: Vec::new(), - objects_are_at_origin: (0, 0).into(), area: (0, 0).into(), }, } @@ -276,14 +275,13 @@ impl Write for ObjectTextRender<'_> { } } -impl ObjectTextRender<'_> { - /// Commits work already done to screen. You can commit to multiple places in the same frame. - pub fn commit(&mut self, oam: &mut OamIterator) { - for (object, slot) in self.layout.objects.iter().zip(oam) { - slot.set(object); - } +impl<'a> OamDisplay for &ObjectTextRender<'a> { + fn set_in(self, oam: &mut OamIterator) -> super::OamDisplayResult { + self.layout.objects.iter().set_in(oam) } +} +impl ObjectTextRender<'_> { /// Force a relayout, must be called after writing. pub fn layout( &mut self, @@ -331,7 +329,7 @@ impl ObjectTextRender<'_> { /// Updates the internal state of the number of letters to write and popped /// line. Should be called in the same frame as and after /// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line]. - pub fn update(&mut self, position: Vector2D) { + pub fn update(&mut self) { if !self.buffer.buffered_chars.is_empty() && self.buffer.letters.letters.len() <= self.number_of_objects + 5 { @@ -339,7 +337,6 @@ impl ObjectTextRender<'_> { } self.layout.update_objects_to_display_at_position( - position, self.buffer.letters.letters.iter(), self.number_of_objects, ); @@ -409,23 +406,17 @@ struct LayoutCache { positions: VecDeque>, line_capacity: VecDeque, objects: Vec, - objects_are_at_origin: Vector2D, area: Vector2D, } impl LayoutCache { fn update_objects_to_display_at_position<'a>( &mut self, - position: Vector2D, letters: impl Iterator, number_of_objects: usize, ) { - let already_done = if position == self.objects_are_at_origin { - self.objects.len() - } else { - self.objects.clear(); - 0 - }; + let already_done = self.objects.len(); + self.objects.extend( self.positions .iter() @@ -433,14 +424,11 @@ impl LayoutCache { .take(number_of_objects) .skip(already_done) .map(|(offset, letter)| { - let position = offset.change_base() + position; - let mut object = ObjectUnmanaged::new(letter.clone()); - object.show().set_position(position); - object + let position = offset.change_base(); + ObjectUnmanaged::new(letter.clone()).set_position(position) }), ); self.objects.truncate(number_of_objects); - self.objects_are_at_origin = position; } fn create_positions( diff --git a/agb/src/display/object/managed.rs b/agb/src/display/object/managed.rs deleted file mode 100644 index b55c1806c..000000000 --- a/agb/src/display/object/managed.rs +++ /dev/null @@ -1,511 +0,0 @@ -use core::cell::{Cell, UnsafeCell}; - -use agb_fixnum::Vector2D; - -use crate::{ - arena::{Arena, ArenaKey}, - display::Priority, -}; - -use super::{ - AffineMatrixInstance, AffineMode, OamUnmanaged, ObjectUnmanaged, Sprite, SpriteLoader, - SpriteVram, -}; - -type ObjectKey = ArenaKey; - -#[derive(Clone, Copy)] -struct Ordering { - next: Option, - previous: Option, -} - -struct ObjectItem { - object: UnsafeCell, - z_order: Cell, - z_index: Cell, -} - -struct Store { - store: UnsafeCell>, - first_z: Cell>, -} - -struct StoreIterator<'store> { - store: &'store Arena, - current: Option, -} - -impl<'store> Iterator for StoreIterator<'store> { - type Item = &'store ObjectItem; - - fn next(&mut self) -> Option { - let to_output = unsafe { self.store.get(self.current?) }; - self.current = to_output.z_order.get().next; - Some(to_output) - } -} - -impl Store { - /// SAFETY: while this exists, no other store related operations should be - /// performed. Notably this means you shouldn't drop the ObjectItem as this - /// implementation will touch this. - unsafe fn iter(&self) -> StoreIterator { - StoreIterator { - store: unsafe { &*self.store.get() }, - current: self.first_z.get(), - } - } - - #[cfg(test)] - fn is_all_ordered_right(&self) -> bool { - let mut previous_z = i32::MIN; - let mut current_index = self.first_z.get(); - - while let Some(ci) = current_index { - let obj = self.get_object(ci); - let this_z = obj.z_index.get(); - if this_z < previous_z { - return false; - } - previous_z = this_z; - current_index = obj.z_order.get().next; - } - - true - } - - fn insert_object(&self, object: ObjectUnmanaged) -> Object { - let object_item = ObjectItem { - object: UnsafeCell::new(object), - z_order: Cell::new(Ordering { - next: None, - previous: None, - }), - z_index: Cell::new(0), - }; - let idx = { - let data = unsafe { &mut *self.store.get() }; - unsafe { data.insert(object_item) } - }; - - if let Some(first) = self.first_z.get() { - let mut this_index = first; - while self.get_object(this_index).z_index.get() < 0 { - if let Some(idx) = self.get_object(this_index).z_order.get().next { - this_index = idx; - } else { - break; - } - } - if self.get_object(this_index).z_index.get() < 0 { - add_after_element(self, idx, this_index); - } else { - add_before_element(self, idx, this_index); - } - } else { - self.first_z.set(Some(idx)); - } - - Object { - me: idx, - store: self, - } - } - - fn remove_object(&self, object: ObjectKey) { - remove_from_linked_list(self, object); - - let data = unsafe { &mut *self.store.get() }; - unsafe { data.remove(object) }; - } - - fn get_object(&self, key: ObjectKey) -> &ObjectItem { - unsafe { (*self.store.get()).get(key) } - } -} - -/// OAM that manages z ordering and commit all visible objects in one call. This -/// is simpler to use than the [`OamUnmanaged`], but is less performant -/// depending on how objects are stored. -/// -/// Use this if: -/// * You don't want to handle z ordering. -/// * You don't want to deal with the complexity of committing all objects during vblank. -/// -/// Otherwise I'd recommend using [`OamUnmanaged`]. -pub struct OamManaged<'gba> { - object_store: Store, - sprite_loader: UnsafeCell, - unmanaged: UnsafeCell>, -} - -impl OamManaged<'_> { - pub(crate) fn new() -> Self { - Self { - object_store: Store { - store: UnsafeCell::new(Arena::new()), - first_z: Cell::new(None), - }, - sprite_loader: UnsafeCell::new(SpriteLoader::new()), - unmanaged: UnsafeCell::new(OamUnmanaged::new()), - } - } - - /// SAFETY: - /// Do not reenter or recurse or otherwise use sprite loader cell during this. - unsafe fn do_work_with_sprite_loader(&self, c: C) -> T - where - C: Fn(&mut SpriteLoader) -> T, - { - let sprite_loader = unsafe { &mut *self.sprite_loader.get() }; - - c(sprite_loader) - } - - /// Commits all the visible objects. Call during vblank to make changes made - /// to objects visible. - pub fn commit(&self) { - // safety: commit is not reentrant - let unmanaged = unsafe { &mut *self.unmanaged.get() }; - - for (object, slot) in unsafe { self.object_store.iter() } - .map(|item| unsafe { &*item.object.get() }) - .filter(|object| object.is_visible()) - .zip(unmanaged.iter()) - { - slot.set(object); - } - - // safety: not reentrant - unsafe { - self.do_work_with_sprite_loader(SpriteLoader::garbage_collect); - } - } - - /// Creates an object from the sprite in vram. - pub fn object(&self, sprite: SpriteVram) -> Object<'_> { - self.object_store - .insert_object(ObjectUnmanaged::new(sprite)) - } - - /// Creates a sprite in vram from a static sprite from [`include_aseprite`][crate::include_aseprite]. - pub fn sprite(&self, sprite: &'static Sprite) -> SpriteVram { - // safety: not reentrant - unsafe { - self.do_work_with_sprite_loader(|sprite_loader| sprite_loader.get_vram_sprite(sprite)) - } - } - - /// Creates a sprite in vram and uses it to make an object from a static sprite from [`include_aseprite`][crate::include_aseprite]. - pub fn object_sprite(&self, sprite: &'static Sprite) -> Object<'_> { - self.object(self.sprite(sprite)) - } -} - -/// A managed object used with the [`OamManaged`] interface. -pub struct Object<'controller> { - me: ObjectKey, - store: &'controller Store, -} - -impl Drop for Object<'_> { - fn drop(&mut self) { - self.store.remove_object(self.me); - } -} - -fn remove_from_linked_list(store: &Store, to_remove: ObjectKey) { - let my_current_neighbours = store.get_object(to_remove).z_order.get(); - - if let Some(previous) = my_current_neighbours.previous { - let stored_part = &store.get_object(previous).z_order; - let mut neighbour_left = stored_part.get(); - neighbour_left.next = my_current_neighbours.next; - stored_part.set(neighbour_left); - } else { - store.first_z.set(my_current_neighbours.next); - } - - if let Some(next) = my_current_neighbours.next { - let stored_part = &store.get_object(next).z_order; - let mut neighbour_right = stored_part.get(); - neighbour_right.previous = my_current_neighbours.previous; - stored_part.set(neighbour_right); - } - - store.get_object(to_remove).z_order.set(Ordering { - next: None, - previous: None, - }); -} - -fn add_before_element(store: &Store, elem: ObjectKey, before_this: ObjectKey) { - assert_ne!(elem, before_this); - - let this_element_store = &store.get_object(elem).z_order; - let mut this_element = this_element_store.get(); - - let before_store = &store.get_object(before_this).z_order; - let mut before = before_store.get(); - - if let Some(previous) = before.previous { - let neighbour_left_store = &store.get_object(previous).z_order; - let mut neighbour_left = neighbour_left_store.get(); - neighbour_left.next = Some(elem); - neighbour_left_store.set(neighbour_left); - } else { - store.first_z.set(Some(elem)); - } - this_element.next = Some(before_this); - this_element.previous = before.previous; - - before.previous = Some(elem); - - this_element_store.set(this_element); - before_store.set(before); -} - -fn add_after_element(store: &Store, elem: ObjectKey, after_this: ObjectKey) { - assert_ne!(elem, after_this); - - let this_element_store = &store.get_object(elem).z_order; - let mut this_element = this_element_store.get(); - - let after_store = &store.get_object(after_this).z_order; - let mut after = after_store.get(); - - if let Some(next) = after.next { - let neighbour_left_store = &store.get_object(next).z_order; - let mut neighbour_right = neighbour_left_store.get(); - neighbour_right.previous = Some(elem); - neighbour_left_store.set(neighbour_right); - } - - this_element.previous = Some(after_this); - this_element.next = after.next; - - after.next = Some(elem); - - this_element_store.set(this_element); - after_store.set(after); -} - -fn move_before(store: &Store, source: ObjectKey, before_this: ObjectKey) { - assert_ne!(source, before_this); - - remove_from_linked_list(store, source); - add_before_element(store, source, before_this); -} - -fn move_after(store: &Store, source: ObjectKey, after_this: ObjectKey) { - assert_ne!(source, after_this); - - remove_from_linked_list(store, source); - add_after_element(store, source, after_this); -} - -impl Object<'_> { - /// Sets the z position of an object. This is not a GBA concept. It causes - /// the order of rendering to be different, thus changing whether objects - /// are rendered above eachother. - /// - /// Negative z is more towards the outside and positive z is further into - /// the screen => an object with a more *negative* z is drawn on top of an - /// object with a more *positive* z. - pub fn set_z(&mut self, z_index: i32) -> &mut Self { - let my_object = &self.store.get_object(self.me); - - let order = z_index.cmp(&my_object.z_index.get()); - - match order { - core::cmp::Ordering::Equal => {} - core::cmp::Ordering::Less => { - let mut previous_index = self.me; - let mut current_index = self.me; - while self.store.get_object(current_index).z_index.get() > z_index { - previous_index = current_index; - let previous = self.store.get_object(current_index).z_order.get().previous; - if let Some(previous) = previous { - current_index = previous; - } else { - break; - } - } - if previous_index != self.me { - move_before(self.store, self.me, previous_index); - } - } - core::cmp::Ordering::Greater => { - let mut previous_index = self.me; - let mut current_index = self.me; - while self.store.get_object(current_index).z_index.get() < z_index { - previous_index = current_index; - let next = self.store.get_object(current_index).z_order.get().next; - if let Some(next) = next { - current_index = next; - } else { - break; - } - } - if previous_index != self.me { - move_after(self.store, self.me, previous_index); - } - } - } - - my_object.z_index.set(z_index); - - self - } - - /// Safety: - /// Only have *ONE* of these at a time, do not call any functions that modify the slot map while having this. - unsafe fn object(&mut self) -> &mut ObjectUnmanaged { - unsafe { &mut *self.store.get_object(self.me).object.get() } - } - - /// Safety: - /// Don't have a mutable one of these while having one of these, do not call any functions that modify the slot map while having this. - unsafe fn object_shared(&self) -> &ObjectUnmanaged { - unsafe { &*self.store.get_object(self.me).object.get() } - } - - #[must_use] - /// Checks whether the object is not marked as hidden. Note that it could be - /// off screen or completely transparent and still claimed to be visible. - pub fn is_visible(&self) -> bool { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object_shared() }.is_visible() - } - - /// Display the sprite in Normal mode. - pub fn show(&mut self) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().show() }; - - self - } - - /// Display the sprite in Affine mode. - pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().show_affine(affine_mode) }; - - self - } - - /// Sets the horizontal flip, note that this only has a visible affect in Normal mode. - pub fn set_hflip(&mut self, flip: bool) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_hflip(flip) }; - - self - } - - /// Sets the vertical flip, note that this only has a visible affect in Normal mode. - pub fn set_vflip(&mut self, flip: bool) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_vflip(flip) }; - - self - } - - /// Sets the priority of the object relative to the backgrounds priority. - pub fn set_priority(&mut self, priority: Priority) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_priority(priority) }; - - self - } - - /// Changes the sprite mode to be hidden, can be changed to Normal or Affine - /// modes using [`show`][Object::show] and - /// [`show_affine`][Object::show_affine] respectively. - pub fn hide(&mut self) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().hide() }; - - self - } - - /// Sets the x position of the object. - pub fn set_x(&mut self, x: u16) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_x(x) }; - - self - } - - /// Sets the y position of the object. - pub fn set_y(&mut self, y: u16) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_y(y) }; - - self - } - - /// Sets the position of the object. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_position(position) }; - - self - } - - /// Sets the affine matrix. This only has an affect in Affine mode. - pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_affine_matrix(affine_matrix) }; - - self - } - - /// Sets the current sprite for the object. - pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { - // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_sprite(sprite) }; - - self - } -} - -#[cfg(test)] -mod tests { - use alloc::vec::Vec; - - use crate::{display::object::Graphics, include_aseprite}; - - use super::*; - - const TEST_SPRITES: &Graphics = include_aseprite!("examples/gfx/tall.aseprite"); - - const TEST_SPRITE: &Sprite = &TEST_SPRITES.sprites()[0]; - - #[test_case] - fn test_always_ordered(gba: &mut crate::Gba) { - let managed = gba.display.object.get_managed(); - - let sprite = managed.sprite(TEST_SPRITE); - - let mut objects = Vec::new(); - for _ in 0..200 { - let obj = managed.object(sprite.clone()); - objects.push(obj); - } - - for modification_number in 0..10_000 { - let index_to_modify = (crate::rng::gen() as usize) % objects.len(); - let modify_to = crate::rng::gen(); - objects[index_to_modify].set_z(modify_to); - - assert!( - managed.object_store.is_all_ordered_right(), - "objects are unordered after {} modifications. Modified {} to {}.", - modification_number + 1, - index_to_modify, - modify_to - ); - } - } -} diff --git a/agb/src/display/object/sprites/sprite.rs b/agb/src/display/object/sprites/sprite.rs index 0857e2831..84a2013d0 100644 --- a/agb/src/display/object/sprites/sprite.rs +++ b/agb/src/display/object/sprites/sprite.rs @@ -1,4 +1,4 @@ -use core::{alloc::Layout, slice}; +use core::alloc::Layout; use crate::display::palette16::Palette16; @@ -65,7 +65,8 @@ macro_rules! align_bytes { pub bytes: Bytes, } - const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { + #[link_section = ".sprites.sprite_data"] + static ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { _align: [], bytes: *$data, }; @@ -82,7 +83,7 @@ macro_rules! align_bytes { /// # #![no_std] /// # #![no_main] /// # use agb::{display::object::Graphics, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( +/// static GRAPHICS: &Graphics = include_aseprite!( /// "examples/gfx/boss.aseprite", /// "examples/gfx/objects.aseprite" /// ); @@ -140,12 +141,12 @@ impl Graphics { /// # #![no_std] /// # #![no_main] /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( +/// static GRAPHICS: &Graphics = include_aseprite!( /// "examples/gfx/boss.aseprite", /// "examples/gfx/objects.aseprite" /// ); /// -/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); +/// static EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); /// ``` /// This being the whole animation associated with the walk sequence of the emu. /// See [Tag] for details on how to use this. @@ -202,12 +203,12 @@ impl TagMap { /// # #![no_std] /// # #![no_main] /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( + /// static GRAPHICS: &Graphics = include_aseprite!( /// "examples/gfx/boss.aseprite", /// "examples/gfx/objects.aseprite" /// ); /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); + /// static EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); /// ``` /// /// See [Tag] for more details. @@ -246,8 +247,7 @@ impl Direction { /// A sequence of sprites from aseprite. pub struct Tag { - sprites: *const Sprite, - len: usize, + sprites: &'static [Sprite], direction: Direction, } @@ -255,16 +255,13 @@ impl Tag { /// The individual sprites that make up the animation themselves. #[must_use] pub fn sprites(&self) -> &'static [Sprite] { - unsafe { slice::from_raw_parts(self.sprites, self.len) } + self.sprites } /// A single sprite referred to by index in the animation sequence. #[must_use] pub const fn sprite(&self, idx: usize) -> &'static Sprite { - if idx >= self.len { - panic!("out of bounds access to sprite"); - } - unsafe { &*self.sprites.add(idx) } + &self.sprites[idx] } /// A sprite that follows the animation sequence. For instance, in aseprite @@ -278,10 +275,10 @@ impl Tag { #[inline] #[must_use] pub fn animation_sprite(&self, idx: usize) -> &'static Sprite { - let len_sub_1 = self.len - 1; + let len_sub_1 = self.sprites.len() - 1; match self.direction { - Direction::Forward => self.sprite(idx % self.len), - Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)), + Direction::Forward => self.sprite(idx % self.sprites.len()), + Direction::Backward => self.sprite(len_sub_1 - (idx % self.sprites.len())), Direction::PingPong => self.sprite( (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize) .unsigned_abs(), @@ -297,8 +294,7 @@ impl Tag { assert!(from <= to); assert!(to < sprites.len()); Self { - sprites: &sprites[from] as *const Sprite, - len: to - from + 1, + sprites: sprites.split_at(from).1.split_at(to - from + 1).0, direction: Direction::from_usize(direction), } } diff --git a/agb/src/display/object/sprites/sprite_allocator.rs b/agb/src/display/object/sprites/sprite_allocator.rs index 8050347b8..86c22022b 100644 --- a/agb/src/display/object/sprites/sprite_allocator.rs +++ b/agb/src/display/object/sprites/sprite_allocator.rs @@ -1,9 +1,6 @@ use core::{alloc::Allocator, ptr::NonNull}; -use alloc::{ - boxed::Box, - rc::{Rc, Weak}, -}; +use alloc::{boxed::Box, rc::Rc}; use crate::{ agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd, impl_zst_allocator}, @@ -48,7 +45,9 @@ struct SpriteId(usize); impl SpriteId { fn from_static_sprite(sprite: &'static Sprite) -> SpriteId { - SpriteId(sprite as *const _ as usize) + let location = sprite as *const _ as usize; + + SpriteId(location) } } @@ -65,8 +64,8 @@ impl PaletteId { /// This holds loading of static sprites and palettes. pub struct SpriteLoader { - static_palette_map: HashMap>, - static_sprite_map: HashMap>, + static_palette_map: HashMap>, + static_sprite_map: HashMap>, } #[derive(Clone, Copy, Debug)] @@ -199,38 +198,6 @@ impl SpriteVram { } impl SpriteLoader { - fn create_sprite_no_insert( - palette_map: &mut HashMap>, - sprite: &'static Sprite, - ) -> Result<(Weak, SpriteVram), LoaderError> { - let palette = Self::try_get_vram_palette_asoc(palette_map, sprite.palette)?; - - let sprite = SpriteVram::new(sprite.data, sprite.size, palette)?; - Ok((Rc::downgrade(&sprite.data), sprite)) - } - - fn try_get_vram_palette_asoc( - palette_map: &mut HashMap>, - palette: &'static Palette16, - ) -> Result { - let id = PaletteId::from_static_palette(palette); - Ok(match palette_map.entry(id) { - crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() { - Some(data) => PaletteVram { data }, - None => { - let pv = PaletteVram::new(palette)?; - entry.insert(Rc::downgrade(&pv.data)); - pv - } - }, - crate::hash_map::Entry::Vacant(entry) => { - let pv = PaletteVram::new(palette)?; - entry.insert(Rc::downgrade(&pv.data)); - pv - } - }) - } - /// Attempts to get a sprite pub fn try_get_vram_sprite( &mut self, @@ -240,23 +207,20 @@ impl SpriteLoader { let id = SpriteId::from_static_sprite(sprite); - Ok(match self.static_sprite_map.entry(id) { - crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() { - Some(data) => SpriteVram { data }, - None => { - let (weak, vram) = - Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?; - entry.insert(weak); - vram - } - }, - crate::hash_map::Entry::Vacant(entry) => { - let (weak, vram) = - Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?; - entry.insert(weak); - vram - } - }) + if let crate::hash_map::Entry::Occupied(entry) = self.static_sprite_map.entry(id) { + return Ok(SpriteVram { + data: entry.get().clone(), + }); + } + + let palette = self.try_get_vram_palette(sprite.palette)?; + + let sprite = SpriteVram::new(sprite.data, sprite.size, palette.clone()).or_else(|_| { + self.garbage_collect_sprites(); + SpriteVram::new(sprite.data, sprite.size, palette) + })?; + self.static_sprite_map.insert(id, sprite.data.clone()); + Ok(sprite) } /// Attempts to allocate a static palette @@ -264,7 +228,19 @@ impl SpriteLoader { &mut self, palette: &'static Palette16, ) -> Result { - Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette) + let id = PaletteId::from_static_palette(palette); + if let crate::hash_map::Entry::Occupied(entry) = self.static_palette_map.entry(id) { + return Ok(PaletteVram { + data: entry.get().clone(), + }); + } + + let pv = PaletteVram::new(palette).or_else(|_| { + self.garbage_collect(); + PaletteVram::new(palette) + })?; + self.static_palette_map.insert(id, pv.data.clone()); + Ok(pv) } /// Allocates a sprite to vram, panics if it cannot fit. @@ -286,14 +262,18 @@ impl SpriteLoader { } } + fn garbage_collect_sprites(&mut self) { + self.static_sprite_map + .retain(|_, v| Rc::strong_count(v) != 1); + } + /// Remove internal references to sprites that no longer exist in vram. If /// you neglect calling this, memory will leak over time in relation to the /// total number of different sprites used. It will not leak vram. pub fn garbage_collect(&mut self) { - self.static_sprite_map - .retain(|_, v| Weak::strong_count(v) != 0); + self.garbage_collect_sprites(); self.static_palette_map - .retain(|_, v| Weak::strong_count(v) != 0); + .retain(|_, v| Rc::strong_count(v) != 1); } } diff --git a/agb/src/display/object/unmanaged.rs b/agb/src/display/object/unmanaged.rs index 42f609faa..52f7b1040 100644 --- a/agb/src/display/object/unmanaged.rs +++ b/agb/src/display/object/unmanaged.rs @@ -2,4 +2,6 @@ mod attributes; mod object; pub use attributes::AffineMode; -pub use object::{OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; +pub use object::{ + OamDisplay, OamDisplayResult, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged, +}; diff --git a/agb/src/display/object/unmanaged/attributes.rs b/agb/src/display/object/unmanaged/attributes.rs index ef5f8b171..34d6bcfc4 100644 --- a/agb/src/display/object/unmanaged/attributes.rs +++ b/agb/src/display/object/unmanaged/attributes.rs @@ -1,3 +1,4 @@ +use agb_fixnum::Vector2D; use bilge::prelude::*; use crate::display::Priority; @@ -100,18 +101,16 @@ impl Attributes { self } - pub fn hide(&mut self) -> &mut Self { - self.a0.set_object_mode(ObjectMode::Disabled); - - self - } - pub fn set_y(&mut self, y: u16) -> &mut Self { self.a0.set_y(y as u8); self } + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + self.set_x(position.x as u16).set_y(position.y as u16) + } + pub fn set_palette(&mut self, palette_id: u16) -> &mut Self { self.a2.set_palette_bank(u4::new(palette_id as u8)); diff --git a/agb/src/display/object/unmanaged/object.rs b/agb/src/display/object/unmanaged/object.rs index 98fc2a659..ce9f2a475 100644 --- a/agb/src/display/object/unmanaged/object.rs +++ b/agb/src/display/object/unmanaged/object.rs @@ -1,6 +1,6 @@ use core::{cell::UnsafeCell, marker::PhantomData}; -use agb_fixnum::Vector2D; +use agb_fixnum::{Rect, Vector2D}; use alloc::vec::Vec; use crate::display::{ @@ -8,7 +8,7 @@ use crate::display::{ affine::AffineMatrixVram, sprites::SpriteVram, AffineMatrixInstance, OBJECT_ATTRIBUTE_MEMORY, }, - Priority, + Priority, HEIGHT, WIDTH, }; use super::attributes::{AffineMode, Attributes}; @@ -68,6 +68,7 @@ pub struct OamUnmanaged<'gba> { pub struct OamIterator<'oam> { index: usize, + position: Vector2D, frame_data: &'oam UnsafeCell, } @@ -77,6 +78,7 @@ pub struct OamIterator<'oam> { /// See [`OamIterator`] for potential pitfalls. pub struct OamSlot<'oam> { slot: usize, + position: Vector2D, frame_data: &'oam UnsafeCell, } @@ -109,6 +111,7 @@ impl OamSlot<'_> { if let Some(affine_matrix) = &object.affine_matrix { Self::handle_affine(&mut attributes, frame_data, affine_matrix); } + attributes.set_position(self.position + object.position); attributes.write(unsafe { OBJECT_ATTRIBUTE_MEMORY.add(self.slot * 4) }); frame_data.this_frame_sprites.push(object.sprite.clone()); @@ -145,6 +148,7 @@ impl<'oam> Iterator for OamIterator<'oam> { } else { self.index += 1; Some(OamSlot { + position: self.position, slot: idx, frame_data: self.frame_data, }) @@ -152,6 +156,31 @@ impl<'oam> Iterator for OamIterator<'oam> { } } +impl OamIterator<'_> { + fn set_inner(&mut self, object: &ObjectUnmanaged) -> OamDisplayResult { + let screen_area: Rect = Rect::new((0i32, 0i32).into(), (WIDTH, HEIGHT).into()); + let sprite_size = object.sprite.size().to_width_height(); + let sprite_size: Vector2D = (sprite_size.0 as i32, sprite_size.1 as i32).into(); + let my_area = Rect::new(object.position + self.position, sprite_size); + + if !screen_area.touches(my_area) { + return OamDisplayResult::Written; + } + + if let Some(slot) = self.next() { + slot.set(object); + OamDisplayResult::Written + } else { + OamDisplayResult::SomeNotWritten + } + } + + /// Writes objects in the Renderable to slots in OAM. + pub fn set(&mut self, renderable: R) { + renderable.set_in(self); + } +} + impl Drop for OamIterator<'_> { fn drop(&mut self) { let number_writen = self.index; @@ -183,6 +212,7 @@ impl OamUnmanaged<'_> { ); OamIterator { + position: (0, 0).into(), index: 0, frame_data: &self.frame_data, } @@ -209,6 +239,7 @@ pub struct ObjectUnmanaged { attributes: Attributes, sprite: SpriteVram, affine_matrix: Option, + position: Vector2D, } impl ObjectUnmanaged { @@ -223,11 +254,14 @@ impl ObjectUnmanaged { attributes: Attributes::default(), sprite, affine_matrix: None, + position: (0, 0).into(), }; sprite.attributes.set_sprite(sprite_location, shape, size); sprite.attributes.set_palette(palette_location); + sprite.attributes.show(); + sprite } @@ -238,15 +272,9 @@ impl ObjectUnmanaged { self.attributes.is_visible() } - /// Display the sprite in Normal mode. - pub fn show(&mut self) -> &mut Self { - self.attributes.show(); - - self - } - + #[must_use] /// Display the sprite in Affine mode. - pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + pub fn show_affine(mut self, affine_mode: AffineMode) -> Self { assert!( self.affine_matrix.is_some(), "affine matrix must be set before enabling affine matrix!" @@ -257,78 +285,58 @@ impl ObjectUnmanaged { self } + #[must_use] /// Sets the horizontal flip, note that this only has a visible affect in Normal mode. - pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + pub fn set_hflip(mut self, flip: bool) -> Self { self.attributes.set_hflip(flip); self } + #[must_use] /// Sets the vertical flip, note that this only has a visible affect in Normal mode. - pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + pub fn set_vflip(mut self, flip: bool) -> Self { self.attributes.set_vflip(flip); self } + #[must_use] /// Sets the priority of the object relative to the backgrounds priority. - pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + pub fn set_priority(mut self, priority: Priority) -> Self { self.attributes.set_priority(priority); self } - /// Changes the sprite mode to be hidden, can be changed to Normal or Affine - /// modes using [`show`][ObjectUnmanaged::show] and - /// [`show_affine`][ObjectUnmanaged::show_affine] respectively. - pub fn hide(&mut self) -> &mut Self { - self.attributes.hide(); - - self - } - - /// Sets the x position of the object. - pub fn set_x(&mut self, x: u16) -> &mut Self { - self.attributes.set_x(x); - - self - } - - /// Sets the y position of the object. - pub fn set_y(&mut self, y: u16) -> &mut Self { - self.attributes.set_y(y); - - self - } - + #[must_use] /// Sets the position of the object. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - self.set_y(position.y.rem_euclid(1 << 9) as u16); - self.set_x(position.x.rem_euclid(1 << 9) as u16); + pub fn set_position(mut self, position: Vector2D) -> Self { + self.position = position; self } + #[must_use] /// Sets the affine matrix. This only has an affect in Affine mode. - pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self { + pub fn set_affine_matrix(mut self, affine_matrix: AffineMatrixInstance) -> Self { let vram = affine_matrix.vram(); self.affine_matrix = Some(vram); self } - fn set_sprite_attributes(&mut self, sprite: &SpriteVram) -> &mut Self { + fn set_sprite_attributes(&mut self, sprite: &SpriteVram) { let size = sprite.size(); let (shape, size) = size.shape_size(); self.attributes.set_sprite(sprite.location(), shape, size); self.attributes.set_palette(sprite.palette_location()); - - self } + #[must_use] /// Sets the current sprite for the object. - pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { + pub fn set_sprite(mut self, sprite: SpriteVram) -> Self { self.set_sprite_attributes(&sprite); self.sprite = sprite; @@ -348,14 +356,14 @@ mod tests { #[test_case] fn object_usage(gba: &mut crate::Gba) { - const GRAPHICS: &Graphics = include_aseprite!( + static GRAPHICS: &Graphics = include_aseprite!( "../examples/the-purple-night/gfx/objects.aseprite", "../examples/the-purple-night/gfx/boss.aseprite" ); - const BOSS: &Tag = GRAPHICS.tags().get("Boss"); + static BOSS: &Tag = GRAPHICS.tags().get("Boss"); - let (mut gfx, mut loader) = gba.display.object.get_unmanaged(); + let (mut gfx, mut loader) = gba.display.object.get(); { let mut slotter = gfx.iter(); @@ -363,12 +371,70 @@ mod tests { let slot_a = slotter.next().unwrap(); let slot_b = slotter.next().unwrap(); - let mut obj = ObjectUnmanaged::new(loader.get_vram_sprite(BOSS.sprite(2))); - - obj.show(); + let obj = ObjectUnmanaged::new(loader.get_vram_sprite(BOSS.sprite(2))); slot_b.set(&obj); slot_a.set(&obj); } } } + +/// Something (or multiple things) that can be written to oam slots +pub trait OamDisplay { + /// Write it to oam slots, returns whether all the writes could succeed. + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult; + + /// Write it to oam slots with a given offset. + fn set_at(self, oam: &mut OamIterator, position: Vector2D) -> OamDisplayResult + where + Self: Sized, + { + let initial_position = oam.position; + oam.position += position; + let result = self.set_in(oam); + oam.position = initial_position; + result + } +} + +impl OamDisplay for ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(&self) + } +} + +impl OamDisplay for &ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(self) + } +} + +impl OamDisplay for &mut ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(self) + } +} + +impl OamDisplay for T +where + T: IntoIterator, + O: OamDisplay, +{ + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + for object in self.into_iter() { + if matches!(object.set_in(oam), OamDisplayResult::SomeNotWritten) { + return OamDisplayResult::SomeNotWritten; + } + } + + OamDisplayResult::Written + } +} + +/// The result of setting on the Oam +pub enum OamDisplayResult { + /// All objects were written successfully + Written, + /// Some objects were not written + SomeNotWritten, +} diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 063090e28..9bac66580 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -174,7 +174,6 @@ mod no_game; /// Default game pub use no_game::no_game; -pub(crate) mod arena; mod global_asm; pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator}; diff --git a/agb/src/no_game.rs b/agb/src/no_game.rs index 0b84e5f1a..8774303d9 100644 --- a/agb/src/no_game.rs +++ b/agb/src/no_game.rs @@ -4,7 +4,7 @@ use agb_fixnum::{num, Num, Vector2D}; use alloc::vec::Vec; use alloc::{boxed::Box, vec}; -use crate::display::object::{DynamicSprite, PaletteVram, Size, SpriteVram}; +use crate::display::object::{DynamicSprite, OamDisplay, PaletteVram, Size, SpriteVram}; use crate::display::palette16::Palette16; use crate::{ display::{object::ObjectUnmanaged, HEIGHT, WIDTH}, @@ -140,7 +140,7 @@ fn generate_sprites() -> Box<[SpriteVram]> { } pub fn no_game(mut gba: crate::Gba) -> ! { - let (mut oam, _) = gba.display.object.get_unmanaged(); + let (mut oam, _) = gba.display.object.get(); let squares = generate_sprites(); @@ -196,15 +196,11 @@ pub fn no_game(mut gba: crate::Gba) -> ! { (idx, *position + Vector2D::new(time.sin(), time.cos()) * 10) }) .map(|(idx, pos)| { - let mut obj = ObjectUnmanaged::new(squares[idx % squares.len()].clone()); - obj.show().set_position(pos.floor()); - obj + ObjectUnmanaged::new(squares[idx % squares.len()].clone()).set_position(pos.floor()) }) .collect(); vblank.wait_for_vblank(); - for (obj, slot) in letters.iter().zip(oam.iter()) { - slot.set(obj); - } + letters.set_in(&mut oam.iter()); } } diff --git a/book/games/pong/gba.ld b/book/games/pong/gba.ld index 525260d9a..5b95adca2 100644 --- a/book/games/pong/gba.ld +++ b/book/games/pong/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/book/games/pong/gba_mb.ld b/book/games/pong/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/book/games/pong/gba_mb.ld +++ b/book/games/pong/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/amplitude/gba.ld b/examples/amplitude/gba.ld index 525260d9a..5b95adca2 100644 --- a/examples/amplitude/gba.ld +++ b/examples/amplitude/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/amplitude/gba_mb.ld b/examples/amplitude/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/examples/amplitude/gba_mb.ld +++ b/examples/amplitude/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/amplitude/src/lib.rs b/examples/amplitude/src/lib.rs index ccff7f216..f371e724c 100644 --- a/examples/amplitude/src/lib.rs +++ b/examples/amplitude/src/lib.rs @@ -14,8 +14,8 @@ use agb::{ self, affine::AffineMatrix, object::{ - AffineMatrixInstance, AffineMode, Graphics, OamIterator, ObjectUnmanaged, Sprite, - SpriteLoader, SpriteVram, Tag, + AffineMatrixInstance, AffineMode, Graphics, OamDisplay, OamIterator, ObjectUnmanaged, + Sprite, SpriteLoader, SpriteVram, Tag, }, palette16::Palette16, }, @@ -29,7 +29,7 @@ use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; type Number = Num; struct Saw { - object: ObjectUnmanaged, + affine_matrix: AffineMatrixInstance, position: Vector2D, angle: Number, rotation_speed: Number, @@ -67,7 +67,7 @@ fn draw_bar( colour: Colour, oam: &mut OamIterator, sprite_cache: &SpriteCache, -) -> Option<()> { +) { let length = length as i32; let number_of_sprites = length / 8; let size_of_last = length % 8; @@ -77,23 +77,18 @@ fn draw_bar( Colour::Blue => &sprite_cache.bars[1], }; - for sprite_idx in 0..number_of_sprites { - let mut object = ObjectUnmanaged::new(sprites[0].clone()); - object - .show() - .set_position(position + (sprite_idx * 8, 0).into()); - oam.next()?.set(&object); - } + (0..number_of_sprites) + .map(|sprite_idx| { + ObjectUnmanaged::new(sprites[0].clone()) + .set_position(position + (sprite_idx * 8, 0).into()) + }) + .set_in(oam); if size_of_last != 0 { - let mut object = ObjectUnmanaged::new(sprites[8 - size_of_last as usize].clone()); - object - .show() - .set_position(position + (number_of_sprites * 8, 0).into()); - oam.next()?.set(&object); + ObjectUnmanaged::new(sprites[8 - size_of_last as usize].clone()) + .set_position(position + (number_of_sprites * 8, 0).into()) + .set_in(oam); } - - Some(()) } fn draw_number( @@ -102,7 +97,7 @@ fn draw_number( oam: &mut OamIterator, direction: DrawDirection, sprite_cache: &SpriteCache, -) -> Option<()> { +) { let mut digits = Vec::new(); if number == 0 { digits.push(0); @@ -120,20 +115,17 @@ fn draw_number( }; for digit in digits { - let mut obj = ObjectUnmanaged::new(sprite_cache.numbers[digit as usize].clone()); - obj.show().set_position(current_position); - - oam.next()?.set(&obj); + ObjectUnmanaged::new(sprite_cache.numbers[digit as usize].clone()) + .set_position(current_position) + .set_in(oam); current_position -= (4, 0).into(); } - - Some(()) } impl SpriteCache { fn new(loader: &mut SpriteLoader) -> Self { - const SPRITES: &Graphics = include_aseprite!( + static SPRITES: &Graphics = include_aseprite!( "gfx/circles.aseprite", "gfx/saw.aseprite", "gfx/numbers.aseprite", @@ -152,12 +144,12 @@ impl SpriteCache { .into_boxed_slice() } - const NUMBERS: &Tag = SPRITES.tags().get("numbers"); - const BLUE_CIRCLE: &Sprite = SPRITES.tags().get("Blue").sprite(0); - const RED_CIRCLE: &Sprite = SPRITES.tags().get("Red").sprite(0); - const SAW: &Sprite = SPRITES.tags().get("Saw").sprite(0); - const BAR_RED: &Tag = SPRITES.tags().get("Red Bar"); - const BAR_BLUE: &Tag = SPRITES.tags().get("Blue Bar"); + static NUMBERS: &Tag = SPRITES.tags().get("numbers"); + static BLUE_CIRCLE: &Sprite = SPRITES.tags().get("Blue").sprite(0); + static RED_CIRCLE: &Sprite = SPRITES.tags().get("Red").sprite(0); + static SAW: &Sprite = SPRITES.tags().get("Saw").sprite(0); + static BAR_RED: &Tag = SPRITES.tags().get("Red Bar"); + static BAR_BLUE: &Tag = SPRITES.tags().get("Blue Bar"); Self { saw: loader.get_vram_sprite(SAW), @@ -217,7 +209,7 @@ impl Game { } } - fn frame(&mut self, sprite_cache: &SpriteCache) -> GameState { + fn frame(&mut self) -> GameState { self.input.update(); let (height, colour) = if self.input.is_pressed(Button::A) && self.energy > 0.into() { @@ -265,13 +257,7 @@ impl Game { let angle_affine_matrix = AffineMatrix::from_rotation(saw.angle); - saw.object.set_affine_matrix(AffineMatrixInstance::new( - angle_affine_matrix.to_object_wrapping(), - )); - saw.object.show_affine(AffineMode::Affine); - - saw.object - .set_position(saw.position.floor() - (16, 16).into()); + saw.affine_matrix = AffineMatrixInstance::new(angle_affine_matrix.to_object_wrapping()); if (saw.position - self.head_position).magnitude_squared() < ((16 + 4) * (16 + 4)).into() @@ -299,7 +285,9 @@ impl Game { let rotation_speed = rotation_magnitude * rotation_direction; let saw = Saw { - object: ObjectUnmanaged::new(sprite_cache.saw.clone()), + affine_matrix: AffineMatrixInstance::new( + AffineMatrix::identity().to_object_wrapping(), + ), position: (300, rng::gen().rem_euclid(display::HEIGHT)).into(), angle: 0.into(), rotation_speed, @@ -320,25 +308,27 @@ impl Game { } } - fn render(&self, oam: &mut OamIterator, sprite_cache: &SpriteCache) -> Option<()> { - for saw in self.saws.iter() { - oam.next()?.set(&saw.object); - } - - for circle in self.circles.iter() { - let mut object = ObjectUnmanaged::new(match circle.colour { - Colour::Red => sprite_cache.red.clone(), - Colour::Blue => sprite_cache.blue.clone(), - }); - - object - .show() - .set_position(circle.position.floor() - (4, 4).into()); - - oam.next()?.set(&object); - } - - Some(()) + fn render(&self, oam: &mut OamIterator, sprite_cache: &SpriteCache) { + self.saws + .iter() + .map(|x| { + ObjectUnmanaged::new(sprite_cache.saw.clone()) + .set_affine_matrix(x.affine_matrix.clone()) + .set_position(x.position.floor() - (16, 16).into()) + .show_affine(AffineMode::Affine) + }) + .set_in(oam); + + self.circles + .iter() + .map(|circle| { + ObjectUnmanaged::new(match circle.colour { + Colour::Red => sprite_cache.red.clone(), + Colour::Blue => sprite_cache.blue.clone(), + }) + .set_position(circle.position.floor() - (4, 4).into()) + }) + .set_in(oam); } } @@ -386,7 +376,7 @@ struct FinalisedSettings { } pub fn main(mut gba: agb::Gba) -> ! { - let (mut unmanaged, mut sprites) = gba.display.object.get_unmanaged(); + let (mut unmanaged, mut sprites) = gba.display.object.get(); let sprite_cache = SpriteCache::new(&mut sprites); let (_background, mut vram) = gba.display.video.tiled0(); @@ -410,7 +400,7 @@ pub fn main(mut gba: agb::Gba) -> ! { energy_recover_speed: 0.into(), }); loop { - let state = game.frame(&sprite_cache); + let state = game.frame(); if game.alive_frames > max_score { max_score = game.alive_frames; } diff --git a/examples/combo/gba.ld b/examples/combo/gba.ld index 525260d9a..5b95adca2 100644 --- a/examples/combo/gba.ld +++ b/examples/combo/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/combo/gba_mb.ld b/examples/combo/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/examples/combo/gba_mb.ld +++ b/examples/combo/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/hyperspace-roll/gba.ld b/examples/hyperspace-roll/gba.ld index 525260d9a..5b95adca2 100644 --- a/examples/hyperspace-roll/gba.ld +++ b/examples/hyperspace-roll/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/hyperspace-roll/gba_mb.ld b/examples/hyperspace-roll/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/examples/hyperspace-roll/gba_mb.ld +++ b/examples/hyperspace-roll/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/the-hat-chooses-the-wizard/gba.ld b/examples/the-hat-chooses-the-wizard/gba.ld index 525260d9a..5b95adca2 100644 --- a/examples/the-hat-chooses-the-wizard/gba.ld +++ b/examples/the-hat-chooses-the-wizard/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/the-hat-chooses-the-wizard/src/enemies.rs b/examples/the-hat-chooses-the-wizard/src/enemies.rs index d7904ae5c..d3984590b 100644 --- a/examples/the-hat-chooses-the-wizard/src/enemies.rs +++ b/examples/the-hat-chooses-the-wizard/src/enemies.rs @@ -2,18 +2,18 @@ use crate::TAG_MAP; use super::{sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level}; use agb::{ - display::object::{OamManaged, Tag}, + display::object::{ObjectUnmanaged, SpriteLoader, Tag}, fixnum::Vector2D, }; -const SLIME_IDLE: &Tag = TAG_MAP.get("Slime Idle"); -const SLIME_JUMP: &Tag = TAG_MAP.get("Slime Jump"); -const SLIME_SPLAT: &Tag = TAG_MAP.get("Slime splat"); +static SLIME_IDLE: &Tag = TAG_MAP.get("Slime Idle"); +static SLIME_JUMP: &Tag = TAG_MAP.get("Slime Jump"); +static SLIME_SPLAT: &Tag = TAG_MAP.get("Slime splat"); -const SNAIL_EMERGE: &Tag = TAG_MAP.get("Snail Emerge"); -const SNAIL_MOVE: &Tag = TAG_MAP.get("Snail Move"); -const SNAIL_DEATH: &Tag = TAG_MAP.get("Snail Death"); -const SNAIL_IDLE: &Tag = TAG_MAP.get("Snail Idle"); +static SNAIL_EMERGE: &Tag = TAG_MAP.get("Snail Emerge"); +static SNAIL_MOVE: &Tag = TAG_MAP.get("Snail Move"); +static SNAIL_DEATH: &Tag = TAG_MAP.get("Snail Death"); +static SNAIL_IDLE: &Tag = TAG_MAP.get("Snail Idle"); enum UpdateState { Nothing, @@ -22,9 +22,9 @@ enum UpdateState { } #[derive(Default)] -pub enum Enemy<'a> { - Slime(Slime<'a>), - Snail(Snail<'a>), +pub enum Enemy { + Slime(Slime), + Snail(Snail), #[default] Empty, } @@ -34,13 +34,13 @@ pub enum EnemyUpdateState { KillPlayer, } -impl<'a> Enemy<'a> { - pub fn new_slime(object: &'a OamManaged, start_pos: Vector2D) -> Self { - Enemy::Slime(Slime::new(object, start_pos + (0, 1).into())) +impl Enemy { + pub fn new_slime(start_pos: Vector2D) -> Self { + Enemy::Slime(Slime::new(start_pos + (0, 1).into())) } - pub fn new_snail(object: &'a OamManaged, start_pos: Vector2D) -> Self { - Enemy::Snail(Snail::new(object, start_pos)) + pub fn new_snail(start_pos: Vector2D) -> Self { + Enemy::Snail(Snail::new(start_pos)) } pub fn collides_with_hat(&self, position: Vector2D) -> bool { @@ -52,7 +52,6 @@ impl<'a> Enemy<'a> { pub fn update( &mut self, - controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -60,12 +59,8 @@ impl<'a> Enemy<'a> { sfx_player: &mut SfxPlayer, ) -> EnemyUpdateState { let update_state = match self { - Enemy::Slime(slime) => { - slime.update(controller, level, player_pos, hat_state, timer, sfx_player) - } - Enemy::Snail(snail) => { - snail.update(controller, level, player_pos, hat_state, timer, sfx_player) - } + Enemy::Slime(slime) => slime.update(level, player_pos, hat_state, timer, sfx_player), + Enemy::Snail(snail) => snail.update(level, player_pos, hat_state, timer, sfx_player), Enemy::Empty => UpdateState::Nothing, }; @@ -79,27 +74,23 @@ impl<'a> Enemy<'a> { } } - pub fn commit(&mut self, background_offset: Vector2D) { + pub fn to_object(&self, sprite_loader: &mut SpriteLoader) -> Option { match self { - Enemy::Slime(slime) => slime.commit(background_offset), - Enemy::Snail(snail) => snail.commit(background_offset), - Enemy::Empty => {} + Enemy::Slime(slime) => slime.to_object(sprite_loader), + Enemy::Snail(snail) => snail.to_object(sprite_loader), + Enemy::Empty => None, } } } -struct EnemyInfo<'a> { - entity: Entity<'a>, +struct EnemyInfo { + entity: Entity, } -impl<'a> EnemyInfo<'a> { - fn new( - object: &'a OamManaged, - start_pos: Vector2D, - collision: Vector2D, - ) -> Self { +impl EnemyInfo { + fn new(start_pos: Vector2D, collision: Vector2D) -> Self { let mut enemy_info = EnemyInfo { - entity: Entity::new(object, collision), + entity: Entity::new(collision), }; enemy_info.entity.position = start_pos; enemy_info @@ -118,8 +109,8 @@ impl<'a> EnemyInfo<'a> { self.entity.update_position(level); } - fn commit(&mut self, background_offset: Vector2D) { - self.entity.commit_position(background_offset); + fn to_object(&self, sprite_loader: &mut SpriteLoader) -> Option { + self.entity.to_object(sprite_loader) } } @@ -129,24 +120,21 @@ enum SlimeState { Dying(i32), // the start frame of the dying animation } -pub struct Slime<'a> { - enemy_info: EnemyInfo<'a>, +pub struct Slime { + enemy_info: EnemyInfo, state: SlimeState, } -impl<'a> Slime<'a> { - fn new(object: &'a OamManaged, start_pos: Vector2D) -> Self { - let slime = Slime { - enemy_info: EnemyInfo::new(object, start_pos, (14u16, 14u16).into()), +impl Slime { + fn new(start_pos: Vector2D) -> Self { + Slime { + enemy_info: EnemyInfo::new(start_pos, (14u16, 14u16).into()), state: SlimeState::Idle, - }; - - slime + } } fn update( &mut self, - controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -161,9 +149,8 @@ impl<'a> Slime<'a> { let offset = (timer / 16) as usize; let frame = SLIME_IDLE.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); if (self.enemy_info.entity.position - player_pos).magnitude_squared() < (64 * 64).into() @@ -201,9 +188,8 @@ impl<'a> Slime<'a> { self.state = SlimeState::Idle; } else { let frame = SLIME_JUMP.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); } if player_has_collided { @@ -227,9 +213,8 @@ impl<'a> Slime<'a> { } let frame = SLIME_SPLAT.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); } } @@ -238,8 +223,8 @@ impl<'a> Slime<'a> { UpdateState::Nothing } - fn commit(&mut self, background_offset: Vector2D) { - self.enemy_info.commit(background_offset); + fn to_object(&self, sprite_loader: &mut SpriteLoader) -> Option { + self.enemy_info.to_object(sprite_loader) } } @@ -251,19 +236,17 @@ enum SnailState { Death(i32), // start frame } -pub struct Snail<'a> { - enemy_info: EnemyInfo<'a>, +pub struct Snail { + enemy_info: EnemyInfo, state: SnailState, } -impl<'a> Snail<'a> { - fn new(object: &'a OamManaged, start_pos: Vector2D) -> Self { - let snail = Snail { - enemy_info: EnemyInfo::new(object, start_pos, (16u16, 16u16).into()), +impl Snail { + fn new(start_pos: Vector2D) -> Self { + Snail { + enemy_info: EnemyInfo::new(start_pos, (16u16, 16u16).into()), state: SnailState::Idle(0), - }; - - snail + } } pub fn collides_with(&self, position: Vector2D) -> bool { @@ -272,7 +255,6 @@ impl<'a> Snail<'a> { fn update( &mut self, - controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -298,9 +280,8 @@ impl<'a> Snail<'a> { } let frame = SNAIL_IDLE.animation_sprite(0); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); if player_has_collided { if hat_state != HatState::WizardTowards { return UpdateState::KillPlayer; @@ -318,9 +299,8 @@ impl<'a> Snail<'a> { self.enemy_info.entity.velocity = (0, 0).into(); let frame = SNAIL_EMERGE.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); if player_has_collided { if hat_state != HatState::WizardTowards { @@ -340,17 +320,16 @@ impl<'a> Snail<'a> { let offset = (timer - time) as usize / 8; let frame = SNAIL_MOVE.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); if timer % 32 == 0 { let x_vel: FixedNumberType = if self.enemy_info.entity.position.x < player_pos.x { - self.enemy_info.entity.sprite.set_hflip(false); + self.enemy_info.entity.set_hflip(false); 1 } else { - self.enemy_info.entity.sprite.set_hflip(true); + self.enemy_info.entity.set_hflip(true); -1 } .into(); @@ -374,9 +353,8 @@ impl<'a> Snail<'a> { } let frame = SNAIL_EMERGE.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); self.enemy_info.entity.velocity = (0, 0).into(); if player_has_collided { @@ -403,9 +381,7 @@ impl<'a> Snail<'a> { return UpdateState::Remove; }; - let sprite = controller.sprite(frame); - - self.enemy_info.entity.sprite.set_sprite(sprite); + self.enemy_info.entity.set_sprite(frame); self.enemy_info.entity.velocity = (0, 0).into(); } } @@ -415,7 +391,7 @@ impl<'a> Snail<'a> { UpdateState::Nothing } - fn commit(&mut self, background_offset: Vector2D) { - self.enemy_info.commit(background_offset); + fn to_object(&self, sprite_loader: &mut SpriteLoader) -> Option { + self.enemy_info.to_object(sprite_loader) } } diff --git a/examples/the-hat-chooses-the-wizard/src/lib.rs b/examples/the-hat-chooses-the-wizard/src/lib.rs index 5a03e89ce..252069720 100644 --- a/examples/the-hat-chooses-the-wizard/src/lib.rs +++ b/examples/the-hat-chooses-the-wizard/src/lib.rs @@ -8,7 +8,7 @@ extern crate alloc; use agb::{ display::{ - object::{Graphics, OamManaged, Object, Tag, TagMap}, + object::{Graphics, OamDisplay, ObjectUnmanaged, Sprite, SpriteLoader, Tag, TagMap}, tiled::{ InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap, VRamManager, @@ -19,7 +19,7 @@ use agb::{ input::{self, Button, ButtonController}, sound::mixer::Frequency, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, vec::Vec}; use sfx::SfxPlayer; mod enemies; @@ -103,38 +103,58 @@ mod map_tiles { agb::include_background_gfx!(tile_sheet, "2ce8f4", background => "gfx/tile_sheet.png"); -const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); -const TAG_MAP: &TagMap = GRAPHICS.tags(); +static GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); +static TAG_MAP: &TagMap = GRAPHICS.tags(); -const WALKING: &Tag = TAG_MAP.get("Walking"); -const JUMPING: &Tag = TAG_MAP.get("Jumping"); -const FALLING: &Tag = TAG_MAP.get("Falling"); -const PLAYER_DEATH: &Tag = TAG_MAP.get("Player Death"); -const HAT_SPIN_1: &Tag = TAG_MAP.get("HatSpin"); -const HAT_SPIN_2: &Tag = TAG_MAP.get("HatSpin2"); -const HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3"); +static WALKING: &Tag = TAG_MAP.get("Walking"); +static JUMPING: &Tag = TAG_MAP.get("Jumping"); +static FALLING: &Tag = TAG_MAP.get("Falling"); +static PLAYER_DEATH: &Tag = TAG_MAP.get("Player Death"); +static HAT_SPIN_1: &Tag = TAG_MAP.get("HatSpin"); +static HAT_SPIN_2: &Tag = TAG_MAP.get("HatSpin2"); +static HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3"); type FixedNumberType = FixedNum<10>; -pub struct Entity<'a> { - sprite: Object<'a>, +pub struct Entity { + sprite: Option<&'static Sprite>, + hflip: bool, position: Vector2D, velocity: Vector2D, collision_mask: Vector2D, } -impl<'a> Entity<'a> { - pub fn new(object: &'a OamManaged, collision_mask: Vector2D) -> Self { - let mut dummy_object = object.object_sprite(WALKING.sprite(0)); - dummy_object.set_priority(Priority::P1); +impl Entity { + pub fn new(collision_mask: Vector2D) -> Self { Entity { - sprite: dummy_object, + sprite: None, + hflip: false, collision_mask, position: (0, 0).into(), velocity: (0, 0).into(), } } + fn set_hflip(&mut self, hflip: bool) { + self.hflip = hflip; + } + + fn set_sprite(&mut self, sprite: &'static Sprite) { + self.sprite = Some(sprite); + } + + fn to_object(&self, sprite_loader: &mut SpriteLoader) -> Option { + let sprite = self.sprite?; + let sprite_size = sprite.size().to_width_height(); + let sprite_size: Vector2D = (sprite_size.0 as i32, sprite_size.1 as i32).into(); + + Some( + ObjectUnmanaged::new(sprite_loader.get_vram_sprite(sprite)) + .set_position(self.position.floor() - sprite_size / 2) + .set_hflip(self.hflip), + ) + } + fn something_at_point bool>( &self, position: Vector2D, @@ -254,16 +274,6 @@ impl<'a> Entity<'a> { unit_vector * low } - - fn commit_position(&mut self, offset: Vector2D) { - let position = (self.position - offset).floor(); - self.sprite.set_position(position - (8, 8).into()); - if position.x < -8 || position.x > WIDTH + 8 || position.y < -8 || position.y > HEIGHT + 8 { - self.sprite.hide(); - } else { - self.sprite.show(); - } - } } struct Map<'a, 'b> { @@ -324,9 +334,9 @@ pub enum HatState { WizardTowards, } -struct Player<'a> { - wizard: Entity<'a>, - hat: Entity<'a>, +struct Player { + wizard: Entity, + hat: Entity, hat_state: HatState, hat_left_range: bool, hat_slow_counter: i32, @@ -346,24 +356,14 @@ fn ping_pong(i: i32, n: i32) -> i32 { } } -impl<'a> Player<'a> { - fn new(controller: &'a OamManaged, start_position: Vector2D) -> Self { - let mut wizard = Entity::new(controller, (6_u16, 14_u16).into()); - let mut hat = Entity::new(controller, (6_u16, 6_u16).into()); - - wizard - .sprite - .set_sprite(controller.sprite(HAT_SPIN_1.sprite(0))); - hat.sprite - .set_sprite(controller.sprite(HAT_SPIN_1.sprite(0))); - - wizard.sprite.show(); - hat.sprite.show(); - - hat.sprite.set_z(-1); +impl Player { + fn new(start_position: Vector2D) -> Self { + let mut wizard = Entity::new((6_u16, 14_u16).into()); + let mut hat = Entity::new((6_u16, 6_u16).into()); wizard.position = start_position; hat.position = start_position - (0, 10).into(); + hat.set_sprite(HAT_SPIN_1.sprite(0)); Player { wizard, @@ -381,7 +381,6 @@ impl<'a> Player<'a> { fn update_frame( &mut self, input: &ButtonController, - controller: &'a OamManaged, timer: i32, level: &Level, enemies: &[enemies::Enemy], @@ -460,9 +459,8 @@ impl<'a> Player<'a> { self.wizard_frame = offset as u8; let frame = WALKING.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.wizard.sprite.set_sprite(sprite); + self.wizard.set_sprite(frame); } if self.wizard.velocity.y < -FixedNumberType::new(1) / 16 { @@ -470,9 +468,8 @@ impl<'a> Player<'a> { self.wizard_frame = 5; let frame = JUMPING.animation_sprite(0); - let sprite = controller.sprite(frame); - self.wizard.sprite.set_sprite(sprite); + self.wizard.set_sprite(frame); } else if self.wizard.velocity.y > FixedNumberType::new(1) / 16 { // going down let offset = if self.wizard.velocity.y * 2 > 3.into() { @@ -485,9 +482,8 @@ impl<'a> Player<'a> { self.wizard_frame = 0; let frame = FALLING.animation_sprite(offset); - let sprite = controller.sprite(frame); - self.wizard.sprite.set_sprite(sprite); + self.wizard.set_sprite(frame); } if input.x_tri() != agb::input::Tri::Zero { @@ -509,16 +505,12 @@ impl<'a> Player<'a> { match self.facing { agb::input::Tri::Negative => { - self.wizard.sprite.set_hflip(true); - self.hat - .sprite - .set_sprite(controller.sprite(hat_base_tile.sprite(5))); + self.wizard.set_hflip(true); + self.hat.set_sprite(hat_base_tile.sprite(5)); } agb::input::Tri::Positive => { - self.wizard.sprite.set_hflip(false); - self.hat - .sprite - .set_sprite(controller.sprite(hat_base_tile.sprite(0))); + self.wizard.set_hflip(false); + self.hat.set_sprite(hat_base_tile.sprite(0)); } _ => {} } @@ -543,9 +535,8 @@ impl<'a> Player<'a> { let hat_sprite_offset = (timer / hat_sprite_divider) as usize; - self.hat.sprite.set_sprite( - controller.sprite(hat_base_tile.animation_sprite(hat_sprite_offset)), - ); + self.hat + .set_sprite(hat_base_tile.animation_sprite(hat_sprite_offset)); if self.hat_slow_counter < 30 && self.hat.velocity.magnitude() < 2.into() { self.hat.velocity = (0, 0).into(); @@ -576,9 +567,8 @@ impl<'a> Player<'a> { self.hat.position = self.wizard.position - hat_resting_position; } HatState::WizardTowards => { - self.hat.sprite.set_sprite( - controller.sprite(hat_base_tile.animation_sprite(timer as usize / 2)), - ); + self.hat + .set_sprite(hat_base_tile.animation_sprite(timer as usize / 2)); let distance_vector = self.hat.position - self.wizard.position + hat_resting_position; let distance = distance_vector.magnitude(); @@ -601,9 +591,9 @@ struct PlayingLevel<'a, 'b> { timer: i32, background: Map<'a, 'b>, input: ButtonController, - player: Player<'a>, + player: Player, - enemies: [enemies::Enemy<'a>; 16], + enemies: [enemies::Enemy; 16], } enum UpdateState { @@ -615,20 +605,19 @@ enum UpdateState { impl<'a, 'b> PlayingLevel<'a, 'b> { fn open_level( level: &'a Level, - object_control: &'a OamManaged, background: &'a mut InfiniteScrolledMap<'b>, foreground: &'a mut InfiniteScrolledMap<'b>, input: ButtonController, ) -> Self { - let mut e: [enemies::Enemy<'a>; 16] = Default::default(); + let mut e: [enemies::Enemy; 16] = Default::default(); let mut enemy_count = 0; for &slime in level.slimes { - e[enemy_count] = enemies::Enemy::new_slime(object_control, slime.into()); + e[enemy_count] = enemies::Enemy::new_slime(slime.into()); enemy_count += 1; } for &snail in level.snails { - e[enemy_count] = enemies::Enemy::new_snail(object_control, snail.into()); + e[enemy_count] = enemies::Enemy::new_snail(snail.into()); enemy_count += 1; } @@ -650,7 +639,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { level, position: background_position, }, - player: Player::new(object_control, start_pos), + player: Player::new(start_pos), input, enemies: e, } @@ -673,30 +662,21 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { fn dead_start(&mut self) { self.player.wizard.velocity = (0, -1).into(); - self.player.wizard.sprite.set_priority(Priority::P0); } - fn dead_update(&mut self, controller: &'a OamManaged) -> bool { + fn dead_update(&mut self) -> bool { self.timer += 1; let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8); - let sprite = controller.sprite(frame); self.player.wizard.velocity += (0.into(), FixedNumberType::new(1) / 32).into(); self.player.wizard.position += self.player.wizard.velocity; - self.player.wizard.sprite.set_sprite(sprite); - - self.player.wizard.commit_position(self.background.position); + self.player.wizard.set_sprite(frame); self.player.wizard.position.y - self.background.position.y < (HEIGHT + 8).into() } - fn update_frame( - &mut self, - sfx_player: &mut SfxPlayer, - vram: &mut VRamManager, - controller: &'a OamManaged, - ) -> UpdateState { + fn update_frame(&mut self, sfx_player: &mut SfxPlayer, vram: &mut VRamManager) -> UpdateState { self.timer += 1; self.input.update(); @@ -704,7 +684,6 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { self.player.update_frame( &self.input, - controller, self.timer, self.background.level, &self.enemies, @@ -713,7 +692,6 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { for enemy in self.enemies.iter_mut() { match enemy.update( - controller, self.background.level, self.player.wizard.position, self.player.hat_state, @@ -728,13 +706,6 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { self.background.position = self.get_next_map_position(); self.background.commit_position(vram); - self.player.wizard.commit_position(self.background.position); - self.player.hat.commit_position(self.background.position); - - for enemy in self.enemies.iter_mut() { - enemy.commit(self.background.position); - } - player_dead |= self .player .wizard @@ -752,6 +723,17 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { } } + fn generate_objects(&self, sprite_loader: &mut SpriteLoader) -> Vec { + [ + self.player.hat.to_object(sprite_loader), + self.player.wizard.to_object(sprite_loader), + ] + .into_iter() + .chain(self.enemies.iter().map(|x| x.to_object(sprite_loader))) + .flatten() + .collect() + } + fn get_next_map_position(&self) -> Vector2D { // want to ensure the player and the hat are visible if possible, so try to position the map // so the centre is at the average position. But give the player some extra priority @@ -827,7 +809,7 @@ pub fn main(mut agb: agb::Gba) -> ! { vram.set_background_palettes(tile_sheet::PALETTES); - let object = agb.display.object.get_managed(); + let (mut oam, mut sprite_loader) = agb.display.object.get(); let vblank = agb::interrupt::VBlank::get(); let mut current_level = 0; @@ -839,6 +821,9 @@ pub fn main(mut agb: agb::Gba) -> ! { sfx.frame(); vblank.wait_for_vblank(); + { + let _ = oam.iter(); + } level_display::write_level( &mut world_display, @@ -896,7 +881,6 @@ pub fn main(mut agb: agb::Gba) -> ! { let mut level = PlayingLevel::open_level( &map_tiles::LEVELS[current_level as usize], - &object, &mut background, &mut foreground, agb::input::ButtonController::new(), @@ -917,21 +901,20 @@ pub fn main(mut agb: agb::Gba) -> ! { vblank.wait_for_vblank(); } - object.commit(); - level.show_backgrounds(); world_display.hide(); loop { - match level.update_frame(&mut sfx, &mut vram, &object) { + match level.update_frame(&mut sfx, &mut vram) { UpdateState::Normal => {} UpdateState::Dead => { level.dead_start(); - while level.dead_update(&object) { + while level.dead_update() { sfx.frame(); + let objects = level.generate_objects(&mut sprite_loader); vblank.wait_for_vblank(); - object.commit(); + objects.set_at(&mut oam.iter(), -level.background.position.floor()); } break; } @@ -942,15 +925,18 @@ pub fn main(mut agb: agb::Gba) -> ! { } sfx.frame(); + let objects = level.generate_objects(&mut sprite_loader); vblank.wait_for_vblank(); - object.commit(); + objects.set_at(&mut oam.iter(), -level.background.position.floor()); } level.hide_backgrounds(); level.clear_backgrounds(&mut vram); } - object.commit(); + { + let _ = oam.iter(); + } splash_screen::show_splash_screen( splash_screen::SplashScreen::End, diff --git a/examples/the-purple-night/gba.ld b/examples/the-purple-night/gba.ld index 525260d9a..5b95adca2 100644 --- a/examples/the-purple-night/gba.ld +++ b/examples/the-purple-night/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/examples/the-purple-night/gba_mb.ld b/examples/the-purple-night/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/examples/the-purple-night/gba_mb.ld +++ b/examples/the-purple-night/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/template/gba.ld b/template/gba.ld index 525260d9a..5b95adca2 100644 --- a/template/gba.ld +++ b/template/gba.ld @@ -23,6 +23,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .; diff --git a/template/gba_mb.ld b/template/gba_mb.ld index 118baca6c..a75ce07ae 100644 --- a/template/gba_mb.ld +++ b/template/gba_mb.ld @@ -21,6 +21,7 @@ SECTIONS { KEEP(*(.crt0)); *(.crt0 .crt0*); *(.text .text*); + *(.sprites .sprites*); . = ALIGN(4); } > rom __text_end = .;