diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 2385fc36..6128be88 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -587,7 +587,7 @@ impl From> for Vector2 } } -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Rect { pub position: Vector2D, pub size: Vector2D, @@ -657,10 +657,10 @@ impl Rect { let mut y = self.position.y; core::iter::from_fn(move || { x = x + T::one(); - if x > self.position.x + self.size.x { + if x >= self.position.x + self.size.x { x = self.position.x; y = y + T::one(); - if y > self.position.y + self.size.y { + if y >= self.position.y + self.size.y { return None; } } diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index e61da840..b7436e08 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -7,7 +7,7 @@ use agb::display::example_logo; fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - let mut map = gfx.background(); + let mut map = gfx.background(agb::display::Priority::P0); let mut vram = gfx.vram; example_logo::display_logo(&mut map, &mut vram); diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index e237a13c..af1d5049 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -19,7 +19,7 @@ struct BackCosines { fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - let mut background = gfx.background(); + let mut background = gfx.background(agb::display::Priority::P0); example_logo::display_logo(&mut background, &mut gfx.vram); diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index a47876bb..2f59ffd9 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,9 +1,10 @@ -use alloc::vec; use alloc::vec::Vec; +use alloc::{boxed::Box, vec}; use hashbrown::HashMap; use crate::{ - fixnum::Vector2D, + display, + fixnum::{Rect, Vector2D}, memory_mapped::{MemoryMapped, MemoryMapped1DArray}, }; @@ -267,14 +268,14 @@ pub struct RegularMap { } impl RegularMap { - fn new(background_id: u8, screenblock: u8) -> Self { + fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { Self { background_id, screenblock, x_scroll: 0, y_scroll: 0, - priority: Priority::P0, + priority, tiles: [Tile::default(); 32 * 32], tiles_dirty: true, @@ -335,13 +336,35 @@ impl RegularMap { } let screenblock_memory = self.screenblock_memory(); - for (i, tile) in self.tiles.iter().enumerate() { - screenblock_memory.set(i, tile.0); + + let scroll_pos = self.get_scroll_pos(); + let x_scroll = scroll_pos.x % display::WIDTH as u16; + let y_scroll = scroll_pos.y % display::HEIGHT as u16; + let start_x = x_scroll / 8; + let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8; // divide by 8 rounding up + + let start_y = y_scroll / 8; + let end_y = (y_scroll + display::HEIGHT as u16 + 8 - 1) / 8; + + for y in start_y..end_y { + for x in start_x..end_x { + let id = y.rem_euclid(32) * 32 + x.rem_euclid(32); + screenblock_memory.set(id as usize, self.tiles[id as usize].0); + } } self.tiles_dirty = false; } + pub fn set_scroll_pos(&mut self, pos: Vector2D) { + self.x_scroll = pos.x; + self.y_scroll = pos.y; + } + + pub fn get_scroll_pos(&self) -> Vector2D { + (self.x_scroll, self.y_scroll).into() + } + const fn bg_control_register(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) } } @@ -359,6 +382,174 @@ impl RegularMap { } } +pub struct InfiniteScrolledMap { + map: RegularMap, + get_tile: Box) -> (TileSetReference, TileSetting)>, + + current_pos: Vector2D, + offset: Vector2D, +} + +impl InfiniteScrolledMap { + pub fn new( + map: RegularMap, + get_tile: Box) -> (TileSetReference, TileSetting)>, + ) -> Self { + Self { + map, + get_tile, + current_pos: (0, 0).into(), + offset: (0, 0).into(), + } + } + + pub fn init(&mut self, vram: &mut VRamManager, pos: Vector2D) { + self.current_pos = pos; + + let x_start = div_floor(self.current_pos.x, 8); + let y_start = div_floor(self.current_pos.y, 8); + + let x_end = div_ceil(self.current_pos.x + display::WIDTH, 8); + let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8); + + for (y_idx, y) in (y_start..y_end).enumerate() { + for (x_idx, x) in (x_start..x_end).enumerate() { + let pos = (x, y).into(); + let (tile_set_ref, tile_setting) = (self.get_tile)(pos); + + self.map.set_tile( + vram, + (x_idx as u16, y_idx as u16).into(), + tile_set_ref, + tile_setting, + ); + } + } + + let offset = self.current_pos - (x_start * 8, y_start * 8).into(); + let offset_scroll = ( + if offset.x < 0 { + (offset.x + 32 * 8) as u16 + } else { + offset.x as u16 + }, + if offset.y < 0 { + (offset.y + 32 * 8) as u16 + } else { + offset.y as u16 + }, + ) + .into(); + + self.map.set_scroll_pos(offset_scroll); + self.offset = offset; + } + + pub fn set_pos(&mut self, vram: &mut VRamManager, new_pos: Vector2D) { + let old_pos = self.current_pos; + + let difference = new_pos - old_pos; + + if difference.x.abs() > 8 || difference.y.abs() > 8 { + self.init(vram, new_pos); + return; + } + + let is_new_y_mod_8 = new_pos.y % 8 == 0; + let is_new_x_mod_8 = new_pos.x % 8 == 0; + + let top_bottom_rect: Rect = { + let top_bottom_height = difference.y.signum(); + let new_tile_y = if top_bottom_height > 0 { + div_ceil(new_pos.y, 8) + if is_new_y_mod_8 { 20 } else { 22 } + } else { + div_floor(new_pos.y, 8) + }; + + Rect::new( + (div_floor(new_pos.x, 8), new_tile_y).into(), + ( + if is_new_x_mod_8 { 30 } else { 32 }, + top_bottom_height.abs(), + ) + .into(), + ) + }; + + let left_right_rect: Rect = { + let left_right_width = difference.x.signum(); + let new_tile_x = if left_right_width > 0 { + div_ceil(new_pos.x, 8) + if is_new_x_mod_8 { 30 } else { 32 } + } else { + div_floor(new_pos.x, 8) + }; + + Rect::new( + (new_tile_x, div_floor(new_pos.y, 8)).into(), + (left_right_width.abs(), if is_new_y_mod_8 { 20 } else { 22 }).into(), + ) + }; + + self.offset += difference; + self.current_pos = new_pos; + let offset_block = self.offset / 8; + + for (x, y) in top_bottom_rect.iter().chain(left_right_rect.iter()) { + let (tileset_ref, tile_setting) = (self.get_tile)((x, y).into()); + + let tile_pos = ( + (x + offset_block.x).rem_euclid(32) as u16, + (y + offset_block.y).rem_euclid(32) as u16, + ) + .into(); + + self.map.set_tile(vram, tile_pos, tileset_ref, tile_setting) + } + + let current_scroll = self.map.get_scroll_pos(); + + let new_scroll = ( + (current_scroll.x as i32 + difference.x).rem_euclid(32 * 8) as u16, + (current_scroll.y as i32 + difference.y).rem_euclid(32 * 8) as u16, + ) + .into(); + + self.map.set_scroll_pos(new_scroll); + } + + pub fn show(&mut self) { + self.map.show(); + } + + pub fn hide(&mut self) { + self.map.hide(); + } + + pub fn commit(&mut self) { + self.map.commit(); + } +} + +fn div_floor(x: i32, y: i32) -> i32 { + if x > 0 && y < 0 { + (x - 1) / y - 1 + } else if x < 0 && y > 0 { + (x + 1) / y - 1 + } else { + x / y + } +} + +fn div_ceil(x: i32, y: i32) -> i32 { + if x > 0 && y > 0 { + (x - 1) / y + 1 + } else if x < 0 && y < 0 { + (x + 1) / y + 1 + } else { + x / y + } +} + pub struct Tiled0<'a> { num_regular: u8, next_screenblock: u8, @@ -379,12 +570,12 @@ impl Tiled0<'_> { } } - pub fn background(&mut self) -> RegularMap { + pub fn background(&mut self, priority: Priority) -> RegularMap { if self.num_regular == 4 { panic!("Can only create 4 backgrounds"); } - let bg = RegularMap::new(self.num_regular, self.next_screenblock); + let bg = RegularMap::new(self.num_regular, self.next_screenblock, priority); self.num_regular += 1; self.next_screenblock += 1; diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index a4c30119..4d94208e 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -35,7 +35,7 @@ mod tests { fn logo_display(gba: &mut crate::Gba) { let mut gfx = gba.display.video.tiled0(); - let mut map = gfx.background(); + let mut map = gfx.background(crate::display::Priority::P0); display_logo(&mut map, &mut gfx.vram); diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 7a6b1cdb..ebe8e942 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", ] [[package]] @@ -65,6 +66,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -94,9 +106,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -160,6 +172,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -250,10 +271,16 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.4" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", @@ -394,6 +421,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/examples/the-purple-night/build.rs b/examples/the-purple-night/build.rs index 94914e8e..3d933036 100644 --- a/examples/the-purple-night/build.rs +++ b/examples/the-purple-night/build.rs @@ -45,8 +45,8 @@ fn main() { pub const CLOUD_MAP: &[u16] = &[#(#cloud_tiles),*]; pub const BACKGROUND_MAP: &[u16] = &[#(#background_tiles),*]; pub const FOREGROUND_MAP: &[u16] = &[#(#foreground_tiles),*]; - pub const WIDTH: u32 = #width; - pub const HEIGHT: u32 = #height; + pub const WIDTH: i32 = #width as i32; + pub const HEIGHT: i32 = #height as i32; pub const SLIME_SPAWNS_X: &[u16] = &[#(#slimes_x),*]; pub const SLIME_SPAWNS_Y: &[u16] = &[#(#slimes_y),*]; diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index ea00124f..f4ca137e 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -8,13 +8,13 @@ mod sfx; use core::cmp::Ordering; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use rng::get_random; use agb::{ display::{ - background::{BackgroundDistributor, BackgroundRegular}, + background::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard}, Priority, HEIGHT, WIDTH, }, @@ -29,9 +29,9 @@ agb::include_gfx!("gfx/background.toml"); type Number = FixedNum<8>; struct Level { - background: BackgroundRegular<'static>, - foreground: BackgroundRegular<'static>, - clouds: BackgroundRegular<'static>, + background: InfiniteScrolledMap, + foreground: InfiniteScrolledMap, + clouds: InfiniteScrolledMap, slime_spawns: Vec<(u16, u16)>, bat_spawns: Vec<(u16, u16)>, @@ -40,33 +40,14 @@ struct Level { impl Level { fn load_level( - mut backdrop: BackgroundRegular<'static>, - mut foreground: BackgroundRegular<'static>, - mut clouds: BackgroundRegular<'static>, + mut backdrop: InfiniteScrolledMap, + mut foreground: InfiniteScrolledMap, + mut clouds: InfiniteScrolledMap, + vram: &mut VRamManager, ) -> Self { - backdrop.set_position(Vector2D::new(0, 0)); - backdrop.set_map(agb::display::background::Map::new( - tilemap::BACKGROUND_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - backdrop.set_priority(Priority::P2); - - foreground.set_position(Vector2D::new(0, 0)); - foreground.set_map(agb::display::background::Map::new( - tilemap::FOREGROUND_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - foreground.set_priority(Priority::P0); - - clouds.set_position(Vector2D::new(0, -5)); - clouds.set_map(agb::display::background::Map::new( - tilemap::CLOUD_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - clouds.set_priority(Priority::P3); + backdrop.init(vram, (8, 8).into()); + foreground.init(vram, (8, 8).into()); + clouds.init(vram, (2, 2).into()); backdrop.commit(); foreground.commit(); @@ -1804,8 +1785,6 @@ struct Game<'a> { boss: BossState<'a>, move_state: MoveState, fade_count: u16, - - background_distributor: &'a mut BackgroundDistributor, } enum MoveState { @@ -1826,6 +1805,7 @@ impl<'a> Game<'a> { fn advance_frame( &mut self, object_controller: &'a ObjectControl, + vram: &mut VRamManager, sfx: &mut sfx::Sfx, ) -> GameStatus { let mut state = GameStatus::Continue; @@ -1845,7 +1825,7 @@ impl<'a> Game<'a> { self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into(); } MoveState::FollowingPlayer => { - Game::update_sunrise(self.background_distributor, self.sunrise_timer); + Game::update_sunrise(vram, self.sunrise_timer); if self.sunrise_timer < 120 { self.sunrise_timer += 1; } else { @@ -1867,7 +1847,7 @@ impl<'a> Game<'a> { if boss.gone { self.fade_count += 1; self.fade_count = self.fade_count.min(600); - Game::update_fade_out(self.background_distributor, self.fade_count); + Game::update_fade_out(vram, self.fade_count); } } } @@ -1969,15 +1949,11 @@ impl<'a> Game<'a> { self.player.commit(this_frame_offset); self.boss.commit(this_frame_offset); - self.level - .background - .set_position(this_frame_offset.floor()); - self.level - .foreground - .set_position(this_frame_offset.floor()); - self.level - .clouds - .set_position(this_frame_offset.floor() / 4); + let background_offset = (this_frame_offset.floor().x + 8, 8).into(); + + self.level.background.set_pos(vram, background_offset); + self.level.foreground.set_pos(vram, background_offset); + self.level.clouds.set_pos(vram, background_offset / 4); self.level.background.commit(); self.level.foreground.commit(); self.level.clouds.commit(); @@ -2082,7 +2058,7 @@ impl<'a> Game<'a> { } } - fn update_sunrise(background_distributor: &'a mut BackgroundDistributor, time: u16) { + fn update_sunrise(vram: &mut VRamManager, time: u16) { let mut modified_palette = background::background.palettes[0].clone(); let a = modified_palette.get_colour(0); @@ -2093,10 +2069,10 @@ impl<'a> Game<'a> { let modified_palettes = [modified_palette]; - background_distributor.set_background_palettes(&modified_palettes); + vram.set_background_palettes(&modified_palettes); } - fn update_fade_out(background_distributor: &'a mut BackgroundDistributor, time: u16) { + fn update_fade_out(vram: &mut VRamManager, time: u16) { let mut modified_palette = background::background.palettes[0].clone(); let c = modified_palette.get_colour(2); @@ -2107,15 +2083,10 @@ impl<'a> Game<'a> { let modified_palettes = [modified_palette]; - background_distributor.set_background_palettes(&modified_palettes); + vram.set_background_palettes(&modified_palettes); } - fn new( - object: &'a ObjectControl, - level: Level, - background_distributor: &'a mut BackgroundDistributor, - start_at_boss: bool, - ) -> Self { + fn new(object: &'a ObjectControl, level: Level, start_at_boss: bool) -> Self { let mut player = Player::new(object); let mut offset = (8, 8).into(); if start_at_boss { @@ -2139,8 +2110,6 @@ impl<'a> Game<'a> { move_state: MoveState::Advancing, sunrise_timer: 0, fade_count: 0, - - background_distributor, } } } @@ -2170,19 +2139,59 @@ fn game_with_level(gba: &mut agb::Gba) { loop { let mut background = gba.display.video.tiled0(); - background.set_background_palettes(background::background.palettes); - background.set_background_tilemap(0, background::background.tiles); + background + .vram + .set_background_palettes(background::background.palettes); + + let tileset_ref = background.vram.add_tileset(TileSet::new( + background::background.tiles, + TileFormat::FourBpp, + )); + let mut object = gba.display.object.get(); object.enable(); + let backdrop = InfiniteScrolledMap::new( + background.background(Priority::P2), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + tilemap::BACKGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + + let foreground = InfiniteScrolledMap::new( + background.background(Priority::P0), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + tilemap::FOREGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + + let clouds = InfiniteScrolledMap::new( + background.background(Priority::P3), + Box::new(move |pos| { + agb::println!("CLOUDS: {}, {}", pos.x, pos.y); + + ( + tileset_ref, + TileSetting::from_raw( + tilemap::CLOUD_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + let mut game = Game::new( &object, - Level::load_level( - background.get_regular().unwrap(), - background.get_regular().unwrap(), - background.get_regular().unwrap(), - ), - &mut background, + Level::load_level(backdrop, foreground, clouds, &mut background.vram), start_at_boss, ); @@ -2190,7 +2199,7 @@ fn game_with_level(gba: &mut agb::Gba) { sfx.frame(); vblank.wait_for_vblank(); sfx.after_vblank(); - match game.advance_frame(&object, &mut sfx) { + match game.advance_frame(&object, &mut background.vram, &mut sfx) { GameStatus::Continue => {} GameStatus::Lost => { break false;