Merge pull request #182 from gwilymk/better-backgrounds-gwilym

Improve background management
This commit is contained in:
Gwilym Kuiper 2022-03-12 23:26:42 +00:00 committed by GitHub
commit 3710d9ad1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1262 additions and 713 deletions

View file

@ -587,7 +587,7 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> From<Vector2D<I>> for Vector2
} }
} }
#[derive(PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Rect<T: Number> { pub struct Rect<T: Number> {
pub position: Vector2D<T>, pub position: Vector2D<T>,
pub size: Vector2D<T>, pub size: Vector2D<T>,
@ -653,19 +653,21 @@ impl<T: Number> Rect<T> {
impl<T: FixedWidthUnsignedInteger> Rect<T> { impl<T: FixedWidthUnsignedInteger> Rect<T> {
pub fn iter(self) -> impl Iterator<Item = (T, T)> { pub fn iter(self) -> impl Iterator<Item = (T, T)> {
let mut x = self.position.x - T::one(); let mut x = self.position.x;
let mut y = self.position.y; let mut y = self.position.y;
core::iter::from_fn(move || { 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; x = self.position.x;
y = y + T::one(); y = y + T::one();
if y > self.position.y + self.size.y { if y >= self.position.y + self.size.y {
return None; return None;
} }
} }
Some((x, y)) let ret_x = x;
x = x + T::one();
Some((ret_x, y))
}) })
} }
} }

View file

@ -2,8 +2,8 @@
#![no_main] #![no_main]
use agb::{ use agb::{
display::tiled::{TileFormat, TileSet, TileSetting},
display::{ display::{
background::Map,
object::{Object, ObjectController, Size, Sprite}, object::{Object, ObjectController, Size, Sprite},
palette16::Palette16, palette16::Palette16,
HEIGHT, WIDTH, HEIGHT, WIDTH,
@ -47,15 +47,26 @@ fn main(mut gba: agb::Gba) -> ! {
.unwrap() .unwrap()
}; };
let mut gfx = gba.display.video.tiled0(); let (gfx, mut vram) = gba.display.video.tiled0();
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new(); let mut input = agb::input::ButtonController::new();
gfx.set_background_palette_raw(&MAP_PALETTE); vram.set_background_palette_raw(&MAP_PALETTE);
gfx.set_background_tilemap(0, &MAP_TILES); let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp);
let tileset_ref = vram.add_tileset(tileset);
let mut background = gfx.background(agb::display::Priority::P0);
for (i, &tile) in MAP_MAP.iter().enumerate() {
let i = i as u16;
background.set_tile(
&mut vram,
(i % 32, i / 32).into(),
tileset_ref,
TileSetting::from_raw(tile),
);
}
let mut background = gfx.get_regular().unwrap();
background.set_map(Map::new(&MAP_MAP, (32_u32, 32_u32).into(), 0));
background.show(); background.show();
background.commit(); background.commit();

View file

@ -5,9 +5,11 @@ use agb::display::example_logo;
#[agb::entry] #[agb::entry]
fn main(mut gba: agb::Gba) -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gfx = gba.display.video.tiled0(); let (gfx, mut vram) = gba.display.video.tiled0();
example_logo::display_logo(&mut gfx); let mut map = gfx.background(agb::display::Priority::P0);
example_logo::display_logo(&mut map, &mut vram);
loop {} loop {}
} }

View file

@ -17,9 +17,11 @@ struct BackCosines {
#[agb::entry] #[agb::entry]
fn main(mut gba: agb::Gba) -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gfx = gba.display.video.tiled0(); let (gfx, mut vram) = gba.display.video.tiled0();
example_logo::display_logo(&mut gfx); let mut background = gfx.background(agb::display::Priority::P0);
example_logo::display_logo(&mut background, &mut vram);
let mut time = 0; let mut time = 0;
let cosines = [0_u16; 32]; let cosines = [0_u16; 32];

View file

@ -1,3 +1,4 @@
#[derive(Debug)]
pub struct Bitarray<const N: usize> { pub struct Bitarray<const N: usize> {
a: [u32; N], a: [u32; N],
} }
@ -21,7 +22,26 @@ impl<const N: usize> Bitarray<N> {
let value_mask = value << (index % 32); let value_mask = value << (index % 32);
self.a[index / 32] = self.a[index / 32] & !mask | value_mask self.a[index / 32] = self.a[index / 32] & !mask | value_mask
} }
pub fn first_zero(&self) -> Option<usize> {
for index in 0..N * 32 {
if let Some(bit) = self.get(index) {
if !bit {
return Some(index);
} }
}
}
None
}
}
impl<const N: usize> Default for Bitarray<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1,484 +0,0 @@
use core::ops::Index;
use crate::{
fixnum::{Rect, Vector2D},
memory_mapped::{MemoryMapped, MemoryMapped1DArray},
};
use super::{
palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority,
DISPLAY_CONTROL,
};
const PALETTE_BACKGROUND: MemoryMapped1DArray<u16, 256> =
unsafe { MemoryMapped1DArray::new(0x0500_0000) };
const TILE_BACKGROUND: MemoryMapped1DArray<u32, { 2048 * 8 }> =
unsafe { MemoryMapped1DArray::new(0x06000000) };
const MAP: *mut [[[u16; 32]; 32]; 32] = 0x0600_0000 as *mut _;
pub enum ColourMode {
FourBitPerPixel = 0,
EightBitPerPixel = 1,
}
#[derive(Clone, Copy)]
pub enum BackgroundSize {
S32x32 = 0,
S64x32 = 1,
S32x64 = 2,
S64x64 = 3,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum Mutability {
Immutable,
Mutable,
}
struct MapStorage<'a> {
s: *const [u16],
mutability: Mutability,
_phantom: core::marker::PhantomData<&'a ()>,
}
impl<'a> Index<usize> for MapStorage<'a> {
type Output = u16;
fn index(&self, index: usize) -> &Self::Output {
&self.get()[index]
}
}
impl<'a> MapStorage<'a> {
fn new(store: &[u16]) -> MapStorage {
MapStorage {
s: store as *const _,
mutability: Mutability::Immutable,
_phantom: core::marker::PhantomData,
}
}
fn new_mutable(store: &mut [u16]) -> MapStorage {
MapStorage {
s: store as *const _,
mutability: Mutability::Mutable,
_phantom: core::marker::PhantomData,
}
}
fn get(&self) -> &[u16] {
unsafe { &*self.s }
}
fn get_mut(&mut self) -> &mut [u16] {
assert!(
self.mutability == Mutability::Mutable,
"backing storage must be mutable in order to get internal storage mutably"
);
unsafe { &mut *(self.s as *mut _) }
}
}
/// The map background is the method of drawing game maps to the screen. It
/// automatically handles copying the correct portion of a provided map to the
/// assigned block depending on given coordinates.
#[allow(dead_code)]
pub struct BackgroundRegular<'a> {
register: BackgroundRegister,
commited_position: Vector2D<i32>,
shadowed_position: Vector2D<i32>,
poisoned: bool,
copy_size: Vector2D<u16>,
map: Option<Map<'a>>,
}
pub struct Map<'a> {
store: MapStorage<'a>,
pub dimensions: Vector2D<u32>,
pub default: u16,
}
impl<'a> Map<'a> {
pub fn new(map: &[u16], dimensions: Vector2D<u32>, default: u16) -> Map {
Map {
store: MapStorage::new(map),
dimensions,
default,
}
}
pub fn new_mutable(map: &mut [u16], dimensions: Vector2D<u32>, default: u16) -> Map {
Map {
store: MapStorage::new_mutable(map),
dimensions,
default,
}
}
fn get_position(&self, x: i32, y: i32) -> u16 {
if x < 0 || x as u32 >= self.dimensions.x || y < 0 || y as u32 >= self.dimensions.y {
self.default
} else {
self.store[y as usize * self.dimensions.x as usize + x as usize]
}
}
pub fn get_store(&self) -> &[u16] {
self.store.get()
}
pub fn get_mutable_store(&mut self) -> &mut [u16] {
self.store.get_mut()
}
}
pub struct BackgroundRegister {
background: u8,
block: u8,
shadowed_register: u16,
}
impl<'a> BackgroundRegister {
unsafe fn new(background: u8, block: u8, background_size: BackgroundSize) -> Self {
let mut b = Self {
background,
block,
shadowed_register: 0,
};
b.set_block(block);
b.set_colour_mode(ColourMode::FourBitPerPixel);
b.set_background_size(background_size);
b.write_register();
b
}
/// Sets the background to be shown on screen. Requires the background to
/// have a map enabled otherwise a panic is caused.
pub fn show(&mut self) {
let mode = DISPLAY_CONTROL.get();
let new_mode = mode | (1 << (self.background + 0x08));
DISPLAY_CONTROL.set(new_mode);
}
/// Hides the background, nothing from this background is rendered to screen.
pub fn hide(&mut self) {
let mode = DISPLAY_CONTROL.get();
let new_mode = mode & !(1 << (self.background + 0x08));
DISPLAY_CONTROL.set(new_mode);
}
pub fn set_priority(&mut self, p: Priority) {
unsafe { self.set_shadowed_register_bits(p as u16, 0x2, 0x0) };
}
unsafe fn set_shadowed_register_bits(&mut self, value: u16, length: u16, shift: u16) {
let mask = !(((1 << length) - 1) << shift);
let new = (self.shadowed_register & mask) | (value << shift);
self.shadowed_register = new;
}
pub fn write_register(&self) {
unsafe { self.get_register().set(self.shadowed_register) };
}
unsafe fn get_register(&self) -> MemoryMapped<u16> {
MemoryMapped::new(0x0400_0008 + 2 * self.background as usize)
}
unsafe fn set_block(&mut self, block: u8) {
self.set_shadowed_register_bits(block as u16, 5, 0x8);
}
unsafe fn set_colour_mode(&mut self, mode: ColourMode) {
self.set_shadowed_register_bits(mode as u16, 0x1, 0x7);
}
unsafe fn set_background_size(&mut self, size: BackgroundSize) {
self.set_shadowed_register_bits(size as u16, 0x2, 0xE);
}
unsafe fn set_position_x_register(&self, x: u16) {
*((0x0400_0010 + 4 * self.background as usize) as *mut u16) = x
}
unsafe fn set_position_y_register(&self, y: u16) {
*((0x0400_0012 + 4 * self.background as usize) as *mut u16) = y
}
pub fn set_position(&self, position: Vector2D<i32>) {
unsafe {
self.set_position_x_register((position.x % (32 * 8)) as u16);
self.set_position_y_register((position.y % (32 * 8)) as u16);
}
}
pub fn get_block(&mut self) -> &mut [[u16; 32]; 32] {
unsafe { &mut (*MAP)[self.block as usize] }
}
pub fn clear_partial(&'a mut self, tile: u16) -> impl Iterator<Item = ()> + 'a {
self.get_block()
.iter_mut()
.flatten()
.map(move |t| unsafe { (t as *mut u16).write_volatile(tile) })
}
pub fn clear(&mut self, tile: u16) {
self.clear_partial(tile).count();
}
}
impl<'a, 'b> BackgroundRegular<'a> {
unsafe fn new(
background: u8,
block: u8,
background_size: BackgroundSize,
) -> BackgroundRegular<'a> {
BackgroundRegular {
register: BackgroundRegister::new(background, block, background_size),
commited_position: (0, 0).into(),
shadowed_position: (0, 0).into(),
copy_size: (30_u16, 20_u16).into(),
poisoned: true,
map: None,
}
}
/// Sets the background to be shown on screen. Requires the background to
/// have a map enabled otherwise a panic is caused.
pub fn show(&mut self) {
assert!(self.map.is_some());
self.register.show();
}
/// Hides the background, nothing from this background is rendered to screen.
pub fn hide(&mut self) {
self.register.hide();
}
pub fn set_priority(&mut self, p: Priority) {
self.register.set_priority(p);
}
pub fn set_position(&mut self, position: Vector2D<i32>) {
self.shadowed_position = position;
}
pub fn get_map(&mut self) -> Option<&mut Map<'a>> {
self.poisoned = true;
self.map.as_mut()
}
pub fn set_map(&mut self, map: Map<'a>) {
self.poisoned = true;
self.map = Some(map);
}
pub fn commit_partial(&'b mut self) -> impl Iterator<Item = ()> + 'b {
// commit shadowed register
self.register.write_register();
let map = self.map.as_ref().unwrap();
let commited_screen = Rect::new(self.commited_position, self.copy_size.change_base());
let shadowed_screen = Rect::new(self.shadowed_position, self.copy_size.change_base());
let iter = if self.poisoned || !shadowed_screen.touches(commited_screen) {
let positions_to_be_updated = Rect::new(
self.shadowed_position / 8 - (1, 1).into(),
self.copy_size.change_base() + (1, 1).into(),
)
.iter();
positions_to_be_updated.chain(Rect::new((0, 0).into(), (0, 0).into()).iter())
} else {
let commited_block = self.commited_position / 8;
let shadowed_block = self.shadowed_position / 8;
let top_bottom_rect: Rect<i32> = {
let top_bottom_height = commited_block.y - shadowed_block.y;
let new_y = if top_bottom_height < 0 {
commited_block.y + self.copy_size.y as i32
} else {
shadowed_block.y - 1
};
Rect::new(
(shadowed_block.x - 1, new_y).into(),
(32, top_bottom_height.abs()).into(),
)
};
let left_right_rect: Rect<i32> = {
let left_right_width = commited_block.x - shadowed_block.x;
let new_x = if left_right_width < 0 {
commited_block.x + self.copy_size.x as i32
} else {
shadowed_block.x - 1
};
Rect::new(
(new_x, shadowed_block.y - 1).into(),
(left_right_width.abs(), 22).into(),
)
};
top_bottom_rect.iter().chain(left_right_rect.iter())
};
// update commited position
self.commited_position = self.shadowed_position;
self.poisoned = false;
// update position in registers
self.register.set_position(self.commited_position);
let block = self.register.get_block();
iter.map(move |(x, y)| {
block[y.rem_euclid(32) as usize][x.rem_euclid(32) as usize] = map.get_position(x, y)
})
}
pub fn commit(&mut self) {
self.commit_partial().count();
}
}
fn decide_background_mode(num_regular: u8, num_affine: u8) -> Option<DisplayMode> {
if num_affine == 0 && num_regular <= 4 {
Some(DisplayMode::Tiled0)
} else if num_affine == 1 && num_regular <= 2 {
Some(DisplayMode::Tiled1)
} else if num_affine == 2 && num_regular == 0 {
Some(DisplayMode::Tiled2)
} else {
None
}
}
pub struct BackgroundDistributor {
used_blocks: u32,
num_regular: u8,
num_affine: u8,
}
impl<'b> BackgroundDistributor {
pub(crate) unsafe fn new() -> Self {
set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D);
set_graphics_mode(DisplayMode::Tiled0);
BackgroundDistributor {
used_blocks: 0,
num_regular: 0,
num_affine: 0,
}
}
fn set_background_tilemap_entry(&mut self, index: u32, data: u32) {
TILE_BACKGROUND.set(index as usize, data);
}
/// Copies raw palettes to the background palette without any checks.
pub fn set_background_palette_raw(&mut self, palette: &[u16]) {
for (index, &colour) in palette.iter().enumerate() {
PALETTE_BACKGROUND.set(index, colour);
}
}
fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) {
for (colour_index, &colour) in palette.colours.iter().enumerate() {
PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour);
}
}
/// Copies palettes to the background palettes without any checks.
pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) {
for (palette_index, entry) in palettes.iter().enumerate() {
self.set_background_palette(palette_index as u8, entry)
}
}
/// Gets a map background if possible and assigns an unused block to it.
pub fn get_regular(&mut self) -> Result<BackgroundRegular<'b>, &'static str> {
let new_mode = decide_background_mode(self.num_regular + 1, self.num_affine)
.ok_or("there is no mode compatible with the requested backgrounds")?;
unsafe { set_graphics_mode(new_mode) };
if !self.used_blocks == 0 {
return Err("all blocks are used");
}
let mut availiable_block = u8::MAX;
for i in 0..32 {
if (1 << i) & self.used_blocks == 0 {
availiable_block = i;
break;
}
}
assert!(
availiable_block != u8::MAX,
"should be able to find a block"
);
self.used_blocks |= 1 << availiable_block;
let background = self.num_regular;
self.num_regular += 1;
Ok(unsafe { BackgroundRegular::new(background, availiable_block, BackgroundSize::S32x32) })
}
pub fn get_raw_regular(&mut self) -> Result<BackgroundRegister, &'static str> {
let new_mode = decide_background_mode(self.num_regular + 1, self.num_affine)
.ok_or("there is no mode compatible with the requested backgrounds")?;
unsafe { set_graphics_mode(new_mode) };
if !self.used_blocks == 0 {
return Err("all blocks are used");
}
let mut availiable_block = u8::MAX;
for i in 0..32 {
if (1 << i) & self.used_blocks == 0 {
availiable_block = i;
break;
}
}
assert!(
availiable_block != u8::MAX,
"should be able to find a block"
);
self.used_blocks |= 1 << availiable_block;
let background = self.num_regular;
self.num_regular += 1;
Ok(
unsafe {
BackgroundRegister::new(background, availiable_block, BackgroundSize::S32x32)
},
)
}
/// Copies tiles to tilemap starting at the starting tile. Cannot overwrite
/// blocks that are already written to, panic is caused if this is attempted.
pub fn set_background_tilemap(&mut self, start_tile: u32, tiles: &[u32]) {
let u32_per_block = 512;
let start_block = (start_tile * 8) / u32_per_block;
// round up rather than down
let end_block = (start_tile * 8 + tiles.len() as u32 + u32_per_block - 1) / u32_per_block;
let blocks_to_use: u32 = ((1 << (end_block - start_block)) - 1) << start_block;
assert!(
self.used_blocks & blocks_to_use == 0,
"blocks {} to {} should be unused for this copy to succeed",
start_block,
end_block
);
self.used_blocks |= blocks_to_use;
for (index, &tile) in tiles.iter().enumerate() {
self.set_background_tilemap_entry(start_tile * 8 + index as u32, tile)
}
}
}

View file

@ -1,23 +1,31 @@
use crate::display::background::BackgroundDistributor; use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager};
crate::include_gfx!("gfx/agb_logo.toml"); crate::include_gfx!("gfx/agb_logo.toml");
pub fn display_logo(gfx: &mut BackgroundDistributor) { pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
use super::background::Map; vram.set_background_palettes(agb_logo::test_logo.palettes);
gfx.set_background_palettes(agb_logo::test_logo.palettes);
gfx.set_background_tilemap(0, agb_logo::test_logo.tiles);
let mut back = gfx.get_regular().unwrap(); let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp);
let background_tilemap_reference = vram.add_tileset(background_tilemap);
let mut entries: [u16; 30 * 20] = [0; 30 * 20]; for y in 0..20 {
for tile_id in 0..(30 * 20) { for x in 0..30 {
let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize] as u16; let tile_id = y * 30 + x;
entries[tile_id as usize] = tile_id | (palette_entry << 12);
let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize];
let tile_setting = TileSetting::new(tile_id, false, false, palette_entry);
map.set_tile(
vram,
(x, y).into(),
background_tilemap_reference,
tile_setting,
);
}
} }
back.set_map(Map::new(&entries, (30_u32, 20_u32).into(), 0)); map.commit();
back.show(); map.show();
back.commit();
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -25,9 +33,11 @@ mod tests {
#[test_case] #[test_case]
fn logo_display(gba: &mut crate::Gba) { fn logo_display(gba: &mut crate::Gba) {
let mut gfx = gba.display.video.tiled0(); let (gfx, mut vram) = gba.display.video.tiled0();
display_logo(&mut gfx); let mut map = gfx.background(crate::display::Priority::P0);
display_logo(&mut map, &mut vram);
crate::test_runner::assert_image_output("gfx/test_logo.png"); crate::test_runner::assert_image_output("gfx/test_logo.png");
} }

View file

@ -6,8 +6,6 @@ use video::Video;
use self::object::ObjectController; use self::object::ObjectController;
/// Graphics mode 0. Four regular backgrounds.
pub mod background;
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
pub mod bitmap3; pub mod bitmap3;
/// Graphics mode 4. Bitmap 4 provides two 8-bit paletted framebuffers with page switching. /// Graphics mode 4. Bitmap 4 provides two 8-bit paletted framebuffers with page switching.
@ -20,6 +18,8 @@ pub mod object;
pub mod palette16; pub mod palette16;
/// Data produced by agb-image-converter /// Data produced by agb-image-converter
pub mod tile_data; pub mod tile_data;
/// Graphics mode 0. Four regular backgrounds.
pub mod tiled;
/// Giving out graphics mode. /// Giving out graphics mode.
pub mod video; pub mod video;
@ -110,7 +110,7 @@ pub fn busy_wait_for_vblank() {
while VCOUNT.get() < 160 {} while VCOUNT.get() < 160 {}
} }
#[derive(BitfieldSpecifier)] #[derive(BitfieldSpecifier, Clone, Copy)]
pub enum Priority { pub enum Priority {
P0 = 0, P0 = 0,
P1 = 1, P1 = 1,

View file

@ -0,0 +1,236 @@
use alloc::boxed::Box;
use super::{MapLoan, RegularMap, TileSetReference, TileSetting, VRamManager};
use crate::{
display,
fixnum::{Rect, Vector2D},
};
pub struct InfiniteScrolledMap<'a> {
map: MapLoan<'a, RegularMap>,
get_tile: Box<dyn Fn(Vector2D<i32>) -> (TileSetReference, TileSetting)>,
current_pos: Vector2D<i32>,
offset: Vector2D<i32>,
copied_up_to: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartialUpdateStatus {
Done,
Continue,
}
impl<'a> InfiniteScrolledMap<'a> {
pub fn new(
map: MapLoan<'a, RegularMap>,
get_tile: Box<dyn Fn(Vector2D<i32>) -> (TileSetReference, TileSetting)>,
) -> Self {
Self {
map,
get_tile,
current_pos: (0, 0).into(),
offset: (0, 0).into(),
copied_up_to: 0,
}
}
pub fn init(
&mut self,
vram: &mut VRamManager,
pos: Vector2D<i32>,
between_updates: &mut impl FnMut(),
) {
while self.init_partial(vram, pos) != PartialUpdateStatus::Done {
between_updates();
}
}
pub fn init_partial(
&mut self,
vram: &mut VRamManager,
pos: Vector2D<i32>,
) -> PartialUpdateStatus {
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) + 1;
let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8) + 1;
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
let offset_scroll = (
offset.x.rem_euclid(32 * 8) as u16,
offset.y.rem_euclid(32 * 8) as u16,
)
.into();
self.map.set_scroll_pos(offset_scroll);
self.offset = (x_start, y_start).into();
let copy_from = self.copied_up_to;
const ROWS_TO_COPY: i32 = 2;
for (y_idx, y) in
((y_start + copy_from)..(y_end.min(y_start + copy_from + ROWS_TO_COPY))).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 + copy_from as usize) as u16).into(),
tile_set_ref,
tile_setting,
);
}
}
if copy_from + ROWS_TO_COPY >= y_end - y_start {
self.copied_up_to = 0;
PartialUpdateStatus::Done
} else {
self.copied_up_to = copy_from + ROWS_TO_COPY;
PartialUpdateStatus::Continue
}
}
pub fn set_pos(
&mut self,
vram: &mut VRamManager,
new_pos: Vector2D<i32>,
) -> PartialUpdateStatus {
let old_pos = self.current_pos;
let difference = new_pos - old_pos;
if difference.x.abs() > 10 * 8 || difference.y.abs() > 10 * 8 {
return self.init_partial(vram, new_pos);
}
self.current_pos = new_pos;
let new_tile_x = div_floor(new_pos.x, 8);
let new_tile_y = div_floor(new_pos.y, 8);
let difference_tile_x = div_ceil(difference.x, 8);
let difference_tile_y = div_ceil(difference.y, 8);
let vertical_rect_to_update: Rect<i32> = if div_floor(old_pos.x, 8) != new_tile_x {
// need to update the x line
// calculate which direction we need to update
let direction = difference.x.signum();
// either need to update 20 or 21 tiles depending on whether the y coordinate is a perfect multiple
let y_tiles_to_update = 22;
let line_to_update = if direction < 0 {
// moving to the left, so need to update the left most position
new_tile_x
} else {
// moving to the right, so need to update the right most position
new_tile_x + 30 // TODO is this correct?
};
Rect::new(
(line_to_update, new_tile_y - 1).into(),
(difference_tile_x, y_tiles_to_update).into(),
)
} else {
Rect::new((0i32, 0).into(), (0i32, 0).into())
};
let horizontal_rect_to_update: Rect<i32> = if div_floor(old_pos.y, 8) != new_tile_y {
// need to update the y line
// calculate which direction we need to update
let direction = difference.y.signum();
// either need to update 30 or 31 tiles depending on whether the x coordinate is a perfect multiple
let x_tiles_to_update: i32 = 32;
let line_to_update = if direction < 0 {
// moving up so need to update the top
new_tile_y
} else {
// moving down so need to update the bottom
new_tile_y + 20 // TODO is this correct?
};
Rect::new(
(new_tile_x - 1, line_to_update).into(),
(x_tiles_to_update, difference_tile_y).into(),
)
} else {
Rect::new((0i32, 0).into(), (0i32, 0).into())
};
for (tile_x, tile_y) in vertical_rect_to_update
.iter()
.chain(horizontal_rect_to_update.iter())
{
let (tile_set_ref, tile_setting) = (self.get_tile)((tile_x, tile_y).into());
self.map.set_tile(
vram,
(
(tile_x - self.offset.x).rem_euclid(32) as u16,
(tile_y - self.offset.y).rem_euclid(32) as u16,
)
.into(),
tile_set_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);
PartialUpdateStatus::Done
}
pub fn show(&mut self) {
self.map.show();
}
pub fn hide(&mut self) {
self.map.hide();
}
pub fn commit(&mut self) {
self.map.commit();
}
pub fn clear(&mut self, vram: &mut VRamManager) {
self.map.clear(vram);
}
}
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
}
}

View file

@ -0,0 +1,185 @@
use core::cell::RefCell;
use core::ops::{Deref, DerefMut};
use crate::bitarray::Bitarray;
use crate::display::{Priority, DISPLAY_CONTROL};
use crate::dma::dma_copy;
use crate::fixnum::Vector2D;
use crate::memory_mapped::MemoryMapped;
use super::{Tile, TileSetReference, TileSetting, VRamManager};
pub struct RegularMap {
background_id: u8,
screenblock: u8,
x_scroll: u16,
y_scroll: u16,
priority: Priority,
tiles: [Tile; 32 * 32],
tiles_dirty: bool,
}
pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1;
impl RegularMap {
pub(crate) fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self {
Self {
background_id,
screenblock,
x_scroll: 0,
y_scroll: 0,
priority,
tiles: [Tile::default(); 32 * 32],
tiles_dirty: true,
}
}
pub fn set_tile(
&mut self,
vram: &mut VRamManager,
pos: Vector2D<u16>,
tileset_ref: TileSetReference,
tile_setting: TileSetting,
) {
let pos = (pos.x + pos.y * 32) as usize;
let old_tile = self.tiles[pos];
if old_tile != Tile::default() {
vram.remove_tile(old_tile.tile_index());
}
let tile_index = tile_setting.index();
let new_tile = if tile_index != TRANSPARENT_TILE_INDEX {
let new_tile_idx = vram.add_tile(tileset_ref, tile_index);
Tile::new(new_tile_idx, tile_setting)
} else {
Tile::default()
};
if old_tile == new_tile {
// no need to mark as dirty if nothing changes
return;
}
self.tiles[pos] = new_tile;
self.tiles_dirty = true;
}
pub fn clear(&mut self, vram: &mut VRamManager) {
for tile in self.tiles.iter_mut() {
if *tile != Tile::default() {
vram.remove_tile(tile.tile_index());
}
*tile = Tile::default();
}
}
pub fn show(&mut self) {
let mode = DISPLAY_CONTROL.get();
let new_mode = mode | (1 << (self.background_id + 0x08));
DISPLAY_CONTROL.set(new_mode);
}
pub fn hide(&mut self) {
let mode = DISPLAY_CONTROL.get();
let new_mode = mode & !(1 << (self.background_id + 0x08));
DISPLAY_CONTROL.set(new_mode);
}
pub fn commit(&mut self) {
let new_bg_control_value = (self.priority as u16) | ((self.screenblock as u16) << 8);
self.bg_control_register().set(new_bg_control_value);
self.bg_h_offset().set(self.x_scroll);
self.bg_v_offset().set(self.y_scroll);
if !self.tiles_dirty {
return;
}
let screenblock_memory = self.screenblock_memory();
unsafe {
dma_copy(
self.tiles.as_ptr() as *const u16,
screenblock_memory,
32 * 32,
);
}
self.tiles_dirty = false;
}
pub fn set_scroll_pos(&mut self, pos: Vector2D<u16>) {
self.x_scroll = pos.x;
self.y_scroll = pos.y;
}
pub fn get_scroll_pos(&self) -> Vector2D<u16> {
(self.x_scroll, self.y_scroll).into()
}
const fn bg_control_register(&self) -> MemoryMapped<u16> {
unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) }
}
const fn bg_h_offset(&self) -> MemoryMapped<u16> {
unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) }
}
const fn bg_v_offset(&self) -> MemoryMapped<u16> {
unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) }
}
const fn screenblock_memory(&self) -> *mut u16 {
(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16
}
}
pub struct MapLoan<'a, T> {
map: T,
background_id: u8,
regular_map_list: &'a RefCell<Bitarray<1>>,
}
impl<'a, T> Deref for MapLoan<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.map
}
}
impl<'a, T> DerefMut for MapLoan<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.map
}
}
impl<'a, T> MapLoan<'a, T> {
pub(crate) fn new(
map: T,
background_id: u8,
regular_map_list: &'a RefCell<Bitarray<1>>,
) -> Self {
MapLoan {
map,
background_id,
regular_map_list,
}
}
}
impl<'a, T> Drop for MapLoan<'a, T> {
fn drop(&mut self) {
self.regular_map_list
.borrow_mut()
.set(self.background_id as usize, false);
}
}

View file

@ -0,0 +1,49 @@
mod infinite_scrolled_map;
mod map;
mod tiled0;
mod vram_manager;
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
pub use map::{MapLoan, RegularMap};
pub use tiled0::Tiled0;
pub use vram_manager::{TileFormat, TileIndex, TileSet, TileSetReference, VRamManager};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
struct Tile(u16);
impl Tile {
fn new(idx: TileIndex, setting: TileSetting) -> Self {
Self(idx.index() | setting.setting())
}
fn tile_index(self) -> TileIndex {
TileIndex::new(self.0 as usize & ((1 << 10) - 1))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TileSetting(u16);
impl TileSetting {
pub const fn new(tile_id: u16, hflip: bool, vflip: bool, palette_id: u8) -> Self {
Self(
(tile_id & ((1 << 10) - 1))
| ((hflip as u16) << 10)
| ((vflip as u16) << 11)
| ((palette_id as u16) << 12),
)
}
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
fn index(self) -> u16 {
self.0 & ((1 << 10) - 1)
}
fn setting(self) -> u16 {
self.0 & !((1 << 10) - 1)
}
}

View file

@ -0,0 +1,37 @@
use core::cell::RefCell;
use crate::{
bitarray::Bitarray,
display::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority},
};
use super::{MapLoan, RegularMap};
pub struct Tiled0 {
regular: RefCell<Bitarray<1>>,
}
impl Tiled0 {
pub(crate) unsafe fn new() -> Self {
set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D);
set_graphics_mode(DisplayMode::Tiled0);
Self {
regular: Default::default(),
}
}
pub fn background(&self, priority: Priority) -> MapLoan<'_, RegularMap> {
let mut regular = self.regular.borrow_mut();
let new_background = regular.first_zero().unwrap();
if new_background >= 4 {
panic!("can only have 4 active backgrounds");
}
let bg = RegularMap::new(new_background as u8, (new_background + 16) as u8, priority);
regular.set(new_background, true);
MapLoan::new(bg, new_background as u8, &self.regular)
}
}

View file

@ -0,0 +1,280 @@
use core::{alloc::Layout, ptr::NonNull};
use alloc::vec;
use alloc::vec::Vec;
use crate::{
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
display::palette16,
dma::dma_copy,
memory_mapped::MemoryMapped1DArray,
};
const TILE_RAM_START: usize = 0x0600_0000;
const PALETTE_BACKGROUND: MemoryMapped1DArray<u16, 256> =
unsafe { MemoryMapped1DArray::new(0x0500_0000) };
static TILE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || TILE_RAM_START,
end: || TILE_RAM_START + 0x8000,
})
};
const TILE_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(8 * 8 / 2, 8 * 8 / 2) };
#[cfg(debug_assertions)]
unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! {
unreachable!("{}", message);
}
#[cfg(not(debug_assertions))]
const unsafe fn debug_unreachable_unchecked(_message: &'static str) -> ! {
use core::hint::unreachable_unchecked;
unreachable_unchecked();
}
#[derive(Clone, Copy, Debug)]
pub enum TileFormat {
FourBpp,
}
impl TileFormat {
/// Returns the size of the tile in bytes
fn tile_size(self) -> usize {
match self {
TileFormat::FourBpp => 8 * 8 / 2,
}
}
}
pub struct TileSet<'a> {
tiles: &'a [u32],
format: TileFormat,
}
impl<'a> TileSet<'a> {
pub fn new(tiles: &'a [u32], format: TileFormat) -> Self {
Self { tiles, format }
}
fn num_tiles(&self) -> usize {
self.tiles.len() / self.format.tile_size() * 4
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct TileSetReference {
id: u16,
generation: u16,
}
impl TileSetReference {
fn new(id: u16, generation: u16) -> Self {
Self { id, generation }
}
}
#[derive(Debug)]
pub struct TileIndex(u16);
impl TileIndex {
pub(crate) const fn new(index: usize) -> Self {
Self(index as u16)
}
pub(crate) const fn index(&self) -> u16 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct TileReference(NonNull<u32>);
enum ArenaStorageItem<T> {
EndOfFreeList,
NextFree(usize),
Data(T, u16),
}
pub struct VRamManager<'a> {
tilesets: Vec<ArenaStorageItem<TileSet<'a>>>,
generation: u16,
free_pointer: Option<usize>,
tile_set_to_vram: Vec<Vec<Option<TileReference>>>,
reference_counts: Vec<(u16, Option<(TileSetReference, u16)>)>,
}
impl<'a> VRamManager<'a> {
pub fn new() -> Self {
Self {
tilesets: Vec::new(),
generation: 0,
free_pointer: None,
tile_set_to_vram: Default::default(),
reference_counts: Default::default(),
}
}
pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference {
let generation = self.generation;
self.generation = self.generation.wrapping_add(1);
let num_tiles = tileset.num_tiles();
let tileset = ArenaStorageItem::Data(tileset, generation);
let index = if let Some(ptr) = self.free_pointer.take() {
match self.tilesets[ptr] {
ArenaStorageItem::EndOfFreeList => {
self.tilesets[ptr] = tileset;
ptr
}
ArenaStorageItem::NextFree(next_free) => {
self.free_pointer = Some(next_free);
self.tilesets[ptr] = tileset;
ptr
}
_ => unsafe { debug_unreachable_unchecked("Free pointer cannot point to data") },
}
} else {
self.tilesets.push(tileset);
self.tilesets.len() - 1
};
self.tile_set_to_vram
.resize(self.tilesets.len(), Default::default());
self.tile_set_to_vram[index] = vec![Default::default(); num_tiles];
TileSetReference::new(index as u16, generation)
}
pub fn remove_tileset(&mut self, tile_set_ref: TileSetReference) {
let tileset = &self.tilesets[tile_set_ref.id as usize];
match tileset {
ArenaStorageItem::Data(_, generation) => {
debug_assert_eq!(
*generation, tile_set_ref.generation,
"Tileset generation must be the same when removing"
);
self.tilesets[tile_set_ref.id as usize] = if let Some(ptr) = self.free_pointer {
ArenaStorageItem::NextFree(ptr)
} else {
ArenaStorageItem::EndOfFreeList
};
self.free_pointer = Some(tile_set_ref.id as usize);
}
_ => panic!("Must remove valid tileset"),
}
}
fn index_from_reference(reference: TileReference) -> usize {
let difference = reference.0.as_ptr() as usize - TILE_RAM_START;
difference / (8 * 8 / 2)
}
fn reference_from_index(index: TileIndex) -> TileReference {
let ptr = (index.index() * (8 * 8 / 2)) as usize + TILE_RAM_START;
TileReference(NonNull::new(ptr as *mut _).unwrap())
}
pub(crate) fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex {
let reference = self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize];
if let Some(reference) = reference {
let index = Self::index_from_reference(reference);
self.reference_counts[index].0 += 1;
return TileIndex::new(index);
}
let new_reference: NonNull<u32> =
unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast();
let tile_slice = if let ArenaStorageItem::Data(data, generation) =
&self.tilesets[tile_set_ref.id as usize]
{
debug_assert_eq!(
*generation, tile_set_ref.generation,
"Stale tile data requested"
);
let tile_offset = (tile as usize) * data.format.tile_size() / 4;
&data.tiles[tile_offset..(tile_offset + data.format.tile_size() / 4)]
} else {
panic!("Tile set ref must point to existing tile set");
};
let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2;
unsafe {
dma_copy(
tile_slice.as_ptr() as *const u16,
new_reference.as_ptr() as *mut u16,
tile_size_in_half_words,
);
}
let tile_reference = TileReference(new_reference);
let index = Self::index_from_reference(tile_reference);
self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize] = Some(tile_reference);
self.reference_counts
.resize(self.reference_counts.len().max(index + 1), (0, None));
self.reference_counts[index] = (1, Some((tile_set_ref, tile)));
TileIndex::new(index)
}
pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) {
let index = tile_index.index() as usize;
assert!(
self.reference_counts[index].0 > 0,
"Trying to decrease the reference count of {} below 0",
index
);
self.reference_counts[index].0 -= 1;
if self.reference_counts[index].0 != 0 {
return;
}
let tile_reference = Self::reference_from_index(tile_index);
unsafe {
TILE_ALLOCATOR.dealloc(tile_reference.0.cast().as_ptr(), TILE_LAYOUT);
}
let tile_ref = self.reference_counts[index].1.unwrap();
self.tile_set_to_vram[tile_ref.0.id as usize][tile_ref.1 as usize] = None;
self.reference_counts[index].1 = None;
}
/// Copies raw palettes to the background palette without any checks.
pub fn set_background_palette_raw(&mut self, palette: &[u16]) {
for (index, &colour) in palette.iter().enumerate() {
PALETTE_BACKGROUND.set(index, colour);
}
}
fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) {
for (colour_index, &colour) in palette.colours.iter().enumerate() {
PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour);
}
}
/// Copies palettes to the background palettes without any checks.
pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) {
for (palette_index, entry) in palettes.iter().enumerate() {
self.set_background_palette(palette_index as u8, entry)
}
}
}

View file

@ -1,4 +1,8 @@
use super::{background::BackgroundDistributor, bitmap3::Bitmap3, bitmap4::Bitmap4}; use super::{
bitmap3::Bitmap3,
bitmap4::Bitmap4,
tiled::{Tiled0, VRamManager},
};
#[non_exhaustive] #[non_exhaustive]
pub struct Video {} pub struct Video {}
@ -14,7 +18,7 @@ impl Video {
unsafe { Bitmap4::new() } unsafe { Bitmap4::new() }
} }
pub fn tiled0(&mut self) -> BackgroundDistributor { pub fn tiled0(&mut self) -> (Tiled0, VRamManager<'_>) {
unsafe { BackgroundDistributor::new() } (unsafe { Tiled0::new() }, VRamManager::new())
} }
} }

26
agb/src/dma.rs Normal file
View file

@ -0,0 +1,26 @@
use crate::memory_mapped::MemoryMapped;
const fn dma_source_addr(dma: usize) -> usize {
0x0400_00b0 + 0x0c * dma
}
const fn dma_dest_addr(dma: usize) -> usize {
0x0400_00b4 + 0x0c * dma
}
const fn dma_control_addr(dma: usize) -> usize {
0x0400_00b8 + 0x0c * dma
}
const DMA3_SOURCE_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_source_addr(3)) };
const DMA3_DEST_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_dest_addr(3)) };
const DMA3_CONTROL: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_control_addr(3)) };
pub(crate) unsafe fn dma_copy(src: *const u16, dest: *mut u16, count: usize) {
assert!(count < u16::MAX as usize);
DMA3_SOURCE_ADDR.set(src as u32);
DMA3_DEST_ADDR.set(dest as u32);
DMA3_CONTROL.set(count as u32 | (1 << 31));
}

View file

@ -142,6 +142,7 @@ mod arena;
mod bitarray; mod bitarray;
/// Implements everything relating to things that are displayed on screen. /// Implements everything relating to things that are displayed on screen.
pub mod display; pub mod display;
mod dma;
/// Button inputs to the system. /// Button inputs to the system.
pub mod input; pub mod input;
#[doc(hidden)] // hide for now as the implementation in here is unsound #[doc(hidden)] // hide for now as the implementation in here is unsound

View file

@ -49,9 +49,11 @@ impl<T, const N: usize> MemoryMapped1DArray<T, N> {
array: address as *mut [T; N], array: address as *mut [T; N],
} }
} }
pub fn get(&self, n: usize) -> T { pub fn get(&self, n: usize) -> T {
unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() } unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() }
} }
pub fn set(&self, n: usize, val: T) { pub fn set(&self, n: usize, val: T) {
unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) } unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) }
} }

View file

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
agb = { version = "0.8.0", path = "../../agb", default-features = false } agb = { version = "0.8.0", path = "../../agb" }
[build-dependencies] [build-dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View file

@ -1,26 +1,45 @@
use agb::display::{background::BackgroundRegister, HEIGHT, WIDTH}; use agb::display::{
tiled::{RegularMap, TileSetReference, TileSetting, VRamManager},
HEIGHT, WIDTH,
};
const LEVEL_START: u16 = 12 * 28; const LEVEL_START: u16 = 12 * 28;
const NUMBERS_START: u16 = 12 * 28 + 3; const NUMBERS_START: u16 = 12 * 28 + 3;
const HYPHEN: u16 = 12 * 28 + 11; const HYPHEN: u16 = 12 * 28 + 11;
pub const BLANK: u16 = 11 * 28; pub const BLANK: u16 = 11 * 28;
pub fn write_level(background: &mut BackgroundRegister, world: u32, level: u32) { pub fn write_level(
let map = background.get_block(); map: &mut RegularMap,
let mut counter = 0; world: u32,
level: u32,
map[0][0] = LEVEL_START; tile_set_ref: TileSetReference,
map[0][1] = LEVEL_START + 1; vram: &mut VRamManager,
map[0][2] = LEVEL_START + 2; ) {
for (i, &tile) in [
counter += 4; LEVEL_START,
LEVEL_START + 1,
map[0][counter] = world as u16 + NUMBERS_START - 1; LEVEL_START + 2,
counter += 1; BLANK,
map[0][counter] = HYPHEN; world as u16 + NUMBERS_START - 1,
counter += 1; HYPHEN,
map[0][counter] = level as u16 + NUMBERS_START - 1; level as u16 + NUMBERS_START - 1,
counter += 1; ]
.iter()
background.set_position((-(WIDTH / 2 - counter as i32 * 8 / 2), -(HEIGHT / 2 - 4)).into()); .enumerate()
{
map.set_tile(
vram,
(i as u16, 0).into(),
tile_set_ref,
TileSetting::from_raw(tile),
);
}
map.set_scroll_pos(
(
-(WIDTH / 2 - 7 as i32 * 8 / 2) as u16,
-(HEIGHT / 2 - 4) as u16,
)
.into(),
);
} }

View file

@ -1,6 +1,21 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
extern crate alloc;
use agb::{
display::{
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
tiled::{
InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager,
},
Priority, HEIGHT, WIDTH,
},
fixnum::{FixedNum, Vector2D},
input::{self, Button, ButtonController},
};
use alloc::boxed::Box;
mod enemies; mod enemies;
mod level_display; mod level_display;
mod sfx; mod sfx;
@ -82,16 +97,6 @@ mod map_tiles {
agb::include_gfx!("gfx/tile_sheet.toml"); agb::include_gfx!("gfx/tile_sheet.toml");
use agb::{
display::{
background::BackgroundRegular,
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
Priority, HEIGHT, WIDTH,
},
fixnum::{FixedNum, Vector2D},
input::{self, Button, ButtonController},
};
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite");
const TAG_MAP: &TagMap = GRAPHICS.tags(); const TAG_MAP: &TagMap = GRAPHICS.tags();
@ -258,40 +263,27 @@ impl<'a> Entity<'a> {
} }
struct Map<'a, 'b> { struct Map<'a, 'b> {
background: &'a mut BackgroundRegular<'b>, background: &'a mut InfiniteScrolledMap<'b>,
foreground: &'a mut BackgroundRegular<'b>, foreground: &'a mut InfiniteScrolledMap<'b>,
position: Vector2D<FixedNumberType>, position: Vector2D<FixedNumberType>,
level: &'a Level, level: &'a Level,
} }
impl<'a, 'b, 'c> Map<'a, 'b> { impl<'a, 'b> Map<'a, 'b> {
pub fn commit_position(&mut self) { pub fn commit_position(&mut self, vram: &mut VRamManager) {
self.background.set_position(self.position.floor()); self.background.set_pos(vram, self.position.floor());
self.foreground.set_position(self.position.floor()); self.foreground.set_pos(vram, self.position.floor());
self.background.commit(); self.background.commit();
self.foreground.commit(); self.foreground.commit();
} }
fn load_foreground(&'c mut self) -> impl Iterator<Item = ()> + 'c { pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
self.background.set_position(self.position.floor()); self.background.init_partial(vram, self.position.floor())
self.background.set_map(agb::display::background::Map::new(
self.level.foreground,
self.level.dimensions,
0,
));
self.background.commit_partial()
} }
fn load_background(&'c mut self) -> impl Iterator<Item = ()> + 'c { pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
self.foreground.set_position(self.position.floor()); self.foreground.init_partial(vram, self.position.floor())
self.foreground.set_map(agb::display::background::Map::new(
self.level.background,
self.level.dimensions,
0,
));
self.foreground.set_priority(Priority::P2);
self.foreground.commit_partial()
} }
} }
@ -621,8 +613,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
fn open_level( fn open_level(
level: &'a Level, level: &'a Level,
object_control: &'a ObjectController, object_control: &'a ObjectController,
background: &'a mut BackgroundRegular<'b>, background: &'a mut InfiniteScrolledMap<'b>,
foreground: &'a mut BackgroundRegular<'b>, foreground: &'a mut InfiniteScrolledMap<'b>,
input: ButtonController, input: ButtonController,
) -> Self { ) -> Self {
let mut e: [enemies::Enemy<'a>; 16] = Default::default(); let mut e: [enemies::Enemy<'a>; 16] = Default::default();
@ -661,19 +653,21 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
} }
} }
fn load_1(&'c mut self) -> impl Iterator<Item = ()> + 'c {
self.background.load_background()
}
fn load_2(&'c mut self) -> impl Iterator<Item = ()> + 'c {
self.background.load_foreground()
}
fn show_backgrounds(&mut self) { fn show_backgrounds(&mut self) {
self.background.background.show(); self.background.background.show();
self.background.foreground.show(); self.background.foreground.show();
} }
fn hide_backgrounds(&mut self) {
self.background.background.hide();
self.background.foreground.hide();
}
fn clear_backgrounds(&mut self, vram: &mut VRamManager) {
self.background.background.clear(vram);
self.background.foreground.clear(vram);
}
fn dead_start(&mut self) { fn dead_start(&mut self) {
self.player.wizard.velocity = (0, -1).into(); self.player.wizard.velocity = (0, -1).into();
self.player.wizard.sprite.set_priority(Priority::P0); self.player.wizard.sprite.set_priority(Priority::P0);
@ -696,8 +690,9 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
fn update_frame( fn update_frame(
&mut self, &mut self,
controller: &'a ObjectController,
sfx_player: &mut sfx::SfxPlayer, sfx_player: &mut sfx::SfxPlayer,
vram: &mut VRamManager,
controller: &'a ObjectController,
) -> UpdateState { ) -> UpdateState {
self.timer += 1; self.timer += 1;
self.input.update(); self.input.update();
@ -728,7 +723,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
} }
self.background.position = self.get_next_map_position(); self.background.position = self.get_next_map_position();
self.background.commit_position(); self.background.commit_position(vram);
self.player.wizard.commit_position(self.background.position); self.player.wizard.commit_position(self.background.position);
self.player.hat.commit_position(self.background.position); self.player.hat.commit_position(self.background.position);
@ -783,24 +778,45 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
#[agb::entry] #[agb::entry]
fn main(mut agb: agb::Gba) -> ! { fn main(mut agb: agb::Gba) -> ! {
splash_screen::show_splash_screen(&mut agb, splash_screen::SplashScreen::Start, None, None); let (tiled, mut vram) = agb.display.video.tiled0();
vram.set_background_palettes(tile_sheet::background.palettes);
let mut splash_screen = tiled.background(Priority::P0);
let mut world_display = tiled.background(Priority::P0);
let tile_set_ref = vram.add_tileset(TileSet::new(
tile_sheet::background.tiles,
TileFormat::FourBpp,
));
for y in 0..32u16 {
for x in 0..32u16 {
world_display.set_tile(
&mut vram,
(x, y).into(),
tile_set_ref,
TileSetting::from_raw(level_display::BLANK),
);
}
}
world_display.commit();
world_display.show();
splash_screen::show_splash_screen(
splash_screen::SplashScreen::Start,
None,
None,
&mut splash_screen,
&mut vram,
);
loop { loop {
let mut tiled = agb.display.video.tiled0(); vram.set_background_palettes(tile_sheet::background.palettes);
let mut object = agb.display.object.get(); let mut object = agb.display.object.get();
let mut timer_controller = agb.timers.timers(); let mut timer_controller = agb.timers.timers();
let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0);
tiled.set_background_palettes(tile_sheet::background.palettes);
tiled.set_background_tilemap(0, tile_sheet::background.tiles);
let mut world_display = tiled.get_raw_regular().unwrap();
world_display.clear(level_display::BLANK);
world_display.show();
let mut background = tiled.get_regular().unwrap();
let mut foreground = tiled.get_regular().unwrap();
mixer.enable(); mixer.enable();
let mut music_box = sfx::MusicBox::new(); let mut music_box = sfx::MusicBox::new();
@ -821,8 +837,11 @@ fn main(mut agb: agb::Gba) -> ! {
&mut world_display, &mut world_display,
current_level / 8 + 1, current_level / 8 + 1,
current_level % 8 + 1, current_level % 8 + 1,
tile_set_ref,
&mut vram,
); );
world_display.commit();
world_display.show(); world_display.show();
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
@ -830,6 +849,37 @@ fn main(mut agb: agb::Gba) -> ! {
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank(); mixer.after_vblank();
let mut background = InfiniteScrolledMap::new(
tiled.background(Priority::P2),
Box::new(move |pos: Vector2D<i32>| {
let level = &map_tiles::LEVELS[current_level as usize];
(
tile_set_ref,
TileSetting::from_raw(
*level
.background
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
.unwrap_or(&0),
),
)
}),
);
let mut foreground = InfiniteScrolledMap::new(
tiled.background(Priority::P0),
Box::new(move |pos: Vector2D<i32>| {
let level = &map_tiles::LEVELS[current_level as usize];
(
tile_set_ref,
TileSetting::from_raw(
*level
.foreground
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
.unwrap_or(&0),
),
)
}),
);
let mut level = PlayingLevel::open_level( let mut level = PlayingLevel::open_level(
&map_tiles::LEVELS[current_level as usize], &map_tiles::LEVELS[current_level as usize],
&object, &object,
@ -837,33 +887,38 @@ fn main(mut agb: agb::Gba) -> ! {
&mut foreground, &mut foreground,
agb::input::ButtonController::new(), agb::input::ButtonController::new(),
); );
let mut level_load = level.load_1().step_by(24);
for _ in 0..30 { while level.background.init_background(&mut vram) != PartialUpdateStatus::Done {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank(); mixer.after_vblank();
level_load.next();
} }
level_load.count();
let mut level_load = level.load_2().step_by(24); while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done {
for _ in 0..30 {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank(); mixer.after_vblank();
level_load.next();
} }
level_load.count();
for _ in 0..20 {
music_box.before_frame(&mut mixer);
mixer.frame();
vblank.wait_for_vblank();
mixer.after_vblank();
}
level.show_backgrounds(); level.show_backgrounds();
world_display.hide(); world_display.hide();
loop { loop {
match level.update_frame(&object, &mut sfx::SfxPlayer::new(&mut mixer, &music_box)) match level.update_frame(
{ &mut sfx::SfxPlayer::new(&mut mixer, &music_box),
&mut vram,
&object,
) {
UpdateState::Normal => {} UpdateState::Normal => {}
UpdateState::Dead => { UpdateState::Dead => {
level.dead_start(); level.dead_start();
@ -886,13 +941,17 @@ fn main(mut agb: agb::Gba) -> ! {
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank(); mixer.after_vblank();
} }
level.hide_backgrounds();
level.clear_backgrounds(&mut vram);
} }
splash_screen::show_splash_screen( splash_screen::show_splash_screen(
&mut agb,
splash_screen::SplashScreen::End, splash_screen::SplashScreen::End,
Some(&mut mixer), Some(&mut mixer),
Some(&mut music_box), Some(&mut music_box),
&mut splash_screen,
&mut vram,
); );
} }
} }

View file

@ -1,5 +1,8 @@
use super::sfx::MusicBox; use super::sfx::MusicBox;
use agb::sound::mixer::Mixer; use agb::{
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager},
sound::mixer::Mixer,
};
agb::include_gfx!("gfx/splash_screens.toml"); agb::include_gfx!("gfx/splash_screens.toml");
@ -9,39 +12,77 @@ pub enum SplashScreen {
} }
pub fn show_splash_screen( pub fn show_splash_screen(
agb: &mut agb::Gba,
which: SplashScreen, which: SplashScreen,
mut mixer: Option<&mut Mixer>, mut mixer: Option<&mut Mixer>,
mut music_box: Option<&mut MusicBox>, mut music_box: Option<&mut MusicBox>,
map: &mut RegularMap,
vram: &mut VRamManager,
) { ) {
let mut tiled = agb.display.video.tiled0(); map.set_scroll_pos((0u16, 0u16).into());
let (tile_set_ref, palette) = match which {
match which {
SplashScreen::Start => { SplashScreen::Start => {
tiled.set_background_tilemap(0, splash_screens::splash.tiles); let tile_set_ref = vram.add_tileset(TileSet::new(
tiled.set_background_palettes(splash_screens::splash.palettes); splash_screens::splash.tiles,
TileFormat::FourBpp,
));
(tile_set_ref, splash_screens::splash.palettes)
} }
SplashScreen::End => { SplashScreen::End => {
tiled.set_background_tilemap(0, splash_screens::thanks_for_playing.tiles); let tile_set_ref = vram.add_tileset(TileSet::new(
tiled.set_background_palettes(splash_screens::thanks_for_playing.palettes); splash_screens::thanks_for_playing.tiles,
} TileFormat::FourBpp,
}
let vblank = agb::interrupt::VBlank::get();
let mut splash_screen_display = tiled.get_regular().unwrap();
let mut entries: [u16; 30 * 20] = [0; 30 * 20];
for tile_id in 0..(30 * 20) {
entries[tile_id as usize] = tile_id;
}
let mut input = agb::input::ButtonController::new();
splash_screen_display.set_map(agb::display::background::Map::new(
&entries,
(30_u32, 20_u32).into(),
0,
)); ));
splash_screen_display.set_position((0, 0).into());
splash_screen_display.commit(); (tile_set_ref, splash_screens::thanks_for_playing.palettes)
splash_screen_display.show(); }
};
let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new();
if let Some(ref mut mixer) = mixer {
if let Some(ref mut music_box) = music_box {
music_box.before_frame(mixer);
}
mixer.frame();
}
vblank.wait_for_vblank();
if let Some(ref mut mixer) = mixer {
mixer.after_vblank();
}
for y in 0..20u16 {
for x in 0..30u16 {
map.set_tile(
vram,
(x, y).into(),
tile_set_ref,
TileSetting::from_raw(y * 30 + x),
);
}
if let Some(ref mut mixer) = mixer {
if let Some(ref mut music_box) = music_box {
music_box.before_frame(mixer);
}
mixer.frame();
}
vblank.wait_for_vblank();
if let Some(ref mut mixer) = mixer {
mixer.after_vblank();
}
}
map.commit();
vram.set_background_palettes(palette);
map.show();
loop { loop {
input.update(); input.update();
if input.is_just_pressed( if input.is_just_pressed(
@ -64,5 +105,9 @@ pub fn show_splash_screen(
mixer.after_vblank(); mixer.after_vblank();
} }
} }
splash_screen_display.hide();
map.hide();
map.clear(vram);
vram.remove_tileset(tile_set_ref);
} }

View file

@ -45,8 +45,8 @@ fn main() {
pub const CLOUD_MAP: &[u16] = &[#(#cloud_tiles),*]; pub const CLOUD_MAP: &[u16] = &[#(#cloud_tiles),*];
pub const BACKGROUND_MAP: &[u16] = &[#(#background_tiles),*]; pub const BACKGROUND_MAP: &[u16] = &[#(#background_tiles),*];
pub const FOREGROUND_MAP: &[u16] = &[#(#foreground_tiles),*]; pub const FOREGROUND_MAP: &[u16] = &[#(#foreground_tiles),*];
pub const WIDTH: u32 = #width; pub const WIDTH: i32 = #width as i32;
pub const HEIGHT: u32 = #height; pub const HEIGHT: i32 = #height as i32;
pub const SLIME_SPAWNS_X: &[u16] = &[#(#slimes_x),*]; pub const SLIME_SPAWNS_X: &[u16] = &[#(#slimes_x),*];
pub const SLIME_SPAWNS_Y: &[u16] = &[#(#slimes_y),*]; pub const SLIME_SPAWNS_Y: &[u16] = &[#(#slimes_y),*];

View file

@ -8,20 +8,22 @@ mod sfx;
use core::cmp::Ordering; use core::cmp::Ordering;
use alloc::vec::Vec; use alloc::{boxed::Box, vec::Vec};
use rng::get_random; use rng::get_random;
use agb::{ use agb::{
display::{ display::{
background::{BackgroundDistributor, BackgroundRegular},
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager},
Priority, HEIGHT, WIDTH, Priority, HEIGHT, WIDTH,
}, },
fixnum::{FixedNum, Rect, Vector2D}, fixnum::{FixedNum, Rect, Vector2D},
input::{Button, ButtonController, Tri}, input::{Button, ButtonController, Tri},
interrupt::VBlank,
}; };
use generational_arena::Arena; use generational_arena::Arena;
use sfx::Sfx;
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite"); const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite");
const TAG_MAP: &TagMap = GRAPHICS.tags(); const TAG_MAP: &TagMap = GRAPHICS.tags();
@ -54,45 +56,36 @@ agb::include_gfx!("gfx/background.toml");
type Number = FixedNum<8>; type Number = FixedNum<8>;
struct Level { struct Level<'a> {
background: BackgroundRegular<'static>, background: InfiniteScrolledMap<'a>,
foreground: BackgroundRegular<'static>, foreground: InfiniteScrolledMap<'a>,
clouds: BackgroundRegular<'static>, clouds: InfiniteScrolledMap<'a>,
slime_spawns: Vec<(u16, u16)>, slime_spawns: Vec<(u16, u16)>,
bat_spawns: Vec<(u16, u16)>, bat_spawns: Vec<(u16, u16)>,
emu_spawns: Vec<(u16, u16)>, emu_spawns: Vec<(u16, u16)>,
} }
impl Level { impl<'a> Level<'a> {
fn load_level( fn load_level(
mut backdrop: BackgroundRegular<'static>, mut backdrop: InfiniteScrolledMap<'a>,
mut foreground: BackgroundRegular<'static>, mut foreground: InfiniteScrolledMap<'a>,
mut clouds: BackgroundRegular<'static>, mut clouds: InfiniteScrolledMap<'a>,
start_pos: Vector2D<i32>,
vram: &mut VRamManager,
sfx: &mut Sfx,
) -> Self { ) -> Self {
backdrop.set_position(Vector2D::new(0, 0)); let vblank = VBlank::get();
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)); let mut between_updates = || {
foreground.set_map(agb::display::background::Map::new( sfx.frame();
tilemap::FOREGROUND_MAP, vblank.wait_for_vblank();
Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), sfx.after_vblank();
0, };
));
foreground.set_priority(Priority::P0);
clouds.set_position(Vector2D::new(0, -5)); backdrop.init(vram, start_pos, &mut between_updates);
clouds.set_map(agb::display::background::Map::new( foreground.init(vram, start_pos, &mut between_updates);
tilemap::CLOUD_MAP, clouds.init(vram, start_pos / 4, &mut between_updates);
Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT),
0,
));
clouds.set_priority(Priority::P3);
backdrop.commit(); backdrop.commit();
foreground.commit(); foreground.commit();
@ -150,6 +143,12 @@ impl Level {
None None
} }
} }
fn clear(&mut self, vram: &mut VRamManager) {
self.background.clear(vram);
self.foreground.clear(vram);
self.clouds.clear(vram);
}
} }
struct Entity<'a> { struct Entity<'a> {
@ -392,10 +391,10 @@ impl SwordState {
} }
fn attack_frame(self, timer: u16) -> u16 { fn attack_frame(self, timer: u16) -> u16 {
match self { match self {
SwordState::LongSword => (self.attack_duration() - timer) / 8, SwordState::LongSword => (self.attack_duration().saturating_sub(timer)) / 8,
SwordState::ShortSword => (self.attack_duration() - timer) / 8, SwordState::ShortSword => (self.attack_duration().saturating_sub(timer)) / 8,
SwordState::Dagger => (self.attack_duration() - timer) / 8, SwordState::Dagger => (self.attack_duration().saturating_sub(timer)) / 8,
SwordState::Swordless => (self.attack_duration() - timer) / 8, SwordState::Swordless => (self.attack_duration().saturating_sub(timer)) / 8,
} }
} }
fn jump_attack_tag(self) -> &'static Tag { fn jump_attack_tag(self) -> &'static Tag {
@ -407,7 +406,7 @@ impl SwordState {
} }
} }
fn jump_attack_frame(self, timer: u16) -> u16 { fn jump_attack_frame(self, timer: u16) -> u16 {
(self.jump_attack_duration() - timer) / 8 (self.jump_attack_duration().saturating_sub(timer)) / 8
} }
fn hold_frame(self) -> u16 { fn hold_frame(self) -> u16 {
7 7
@ -1870,7 +1869,7 @@ struct Game<'a> {
player: Player<'a>, player: Player<'a>,
input: ButtonController, input: ButtonController,
frame_count: u32, frame_count: u32,
level: Level, level: Level<'a>,
offset: Vector2D<Number>, offset: Vector2D<Number>,
shake_time: u16, shake_time: u16,
sunrise_timer: u16, sunrise_timer: u16,
@ -1883,8 +1882,6 @@ struct Game<'a> {
boss: BossState<'a>, boss: BossState<'a>,
move_state: MoveState, move_state: MoveState,
fade_count: u16, fade_count: u16,
background_distributor: &'a mut BackgroundDistributor,
} }
enum MoveState { enum MoveState {
@ -1902,9 +1899,14 @@ impl<'a> Game<'a> {
} }
} }
fn clear(&mut self, vram: &mut VRamManager) {
self.level.clear(vram);
}
fn advance_frame( fn advance_frame(
&mut self, &mut self,
object_controller: &'a ObjectController, object_controller: &'a ObjectController,
vram: &mut VRamManager,
sfx: &mut sfx::Sfx, sfx: &mut sfx::Sfx,
) -> GameStatus { ) -> GameStatus {
let mut state = GameStatus::Continue; let mut state = GameStatus::Continue;
@ -1924,7 +1926,7 @@ impl<'a> Game<'a> {
self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into(); self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into();
} }
MoveState::FollowingPlayer => { MoveState::FollowingPlayer => {
Game::update_sunrise(self.background_distributor, self.sunrise_timer); Game::update_sunrise(vram, self.sunrise_timer);
if self.sunrise_timer < 120 { if self.sunrise_timer < 120 {
self.sunrise_timer += 1; self.sunrise_timer += 1;
} else { } else {
@ -1946,7 +1948,7 @@ impl<'a> Game<'a> {
if boss.gone { if boss.gone {
self.fade_count += 1; self.fade_count += 1;
self.fade_count = self.fade_count.min(600); 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);
} }
} }
} }
@ -2049,18 +2051,11 @@ impl<'a> Game<'a> {
self.player.commit(this_frame_offset); self.player.commit(this_frame_offset);
self.boss.commit(this_frame_offset); self.boss.commit(this_frame_offset);
self.level let background_offset = (this_frame_offset.floor().x, 8).into();
.background
.set_position(this_frame_offset.floor()); self.level.background.set_pos(vram, background_offset);
self.level self.level.foreground.set_pos(vram, background_offset);
.foreground self.level.clouds.set_pos(vram, background_offset / 4);
.set_position(this_frame_offset.floor());
self.level
.clouds
.set_position(this_frame_offset.floor() / 4);
self.level.background.commit();
self.level.foreground.commit();
self.level.clouds.commit();
for i in remove { for i in remove {
self.enemies.remove(i); self.enemies.remove(i);
@ -2105,6 +2100,10 @@ impl<'a> Game<'a> {
.commit_with_fudge(this_frame_offset, (0, 0).into()); .commit_with_fudge(this_frame_offset, (0, 0).into());
} }
self.level.background.commit();
self.level.foreground.commit();
self.level.clouds.commit();
for i in remove { for i in remove {
self.particles.remove(i); self.particles.remove(i);
} }
@ -2162,7 +2161,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 mut modified_palette = background::background.palettes[0].clone();
let a = modified_palette.get_colour(0); let a = modified_palette.get_colour(0);
@ -2173,10 +2172,10 @@ impl<'a> Game<'a> {
let modified_palettes = [modified_palette]; 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 mut modified_palette = background::background.palettes[0].clone();
let c = modified_palette.get_colour(2); let c = modified_palette.get_colour(2);
@ -2187,15 +2186,10 @@ impl<'a> Game<'a> {
let modified_palettes = [modified_palette]; let modified_palettes = [modified_palette];
background_distributor.set_background_palettes(&modified_palettes); vram.set_background_palettes(&modified_palettes);
} }
fn new( fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self {
object: &'a ObjectController,
level: Level,
background_distributor: &'a mut BackgroundDistributor,
start_at_boss: bool,
) -> Self {
let mut player = Player::new(object); let mut player = Player::new(object);
let mut offset = (8, 8).into(); let mut offset = (8, 8).into();
if start_at_boss { if start_at_boss {
@ -2219,8 +2213,6 @@ impl<'a> Game<'a> {
move_state: MoveState::Advancing, move_state: MoveState::Advancing,
sunrise_timer: 0, sunrise_timer: 0,
fade_count: 0, fade_count: 0,
background_distributor,
} }
} }
} }
@ -2239,19 +2231,68 @@ fn game_with_level(gba: &mut agb::Gba) {
let mut start_at_boss = false; let mut start_at_boss = false;
loop { loop {
let mut background = gba.display.video.tiled0(); let (background, mut vram) = gba.display.video.tiled0();
background.set_background_palettes(background::background.palettes);
background.set_background_tilemap(0, background::background.tiles); vram.set_background_palettes(background::background.palettes);
let tileset_ref = vram.add_tileset(TileSet::new(
background::background.tiles,
TileFormat::FourBpp,
));
let object = gba.display.object.get(); let object = gba.display.object.get();
let backdrop = InfiniteScrolledMap::new(
background.background(Priority::P2),
Box::new(move |pos| {
(
tileset_ref,
TileSetting::from_raw(
*tilemap::BACKGROUND_MAP
.get((pos.x + tilemap::WIDTH * pos.y) as usize)
.unwrap_or(&0),
),
)
}),
);
let foreground = InfiniteScrolledMap::new(
background.background(Priority::P0),
Box::new(move |pos| {
(
tileset_ref,
TileSetting::from_raw(
*tilemap::FOREGROUND_MAP
.get((pos.x + tilemap::WIDTH * pos.y) as usize)
.unwrap_or(&0),
),
)
}),
);
let clouds = InfiniteScrolledMap::new(
background.background(Priority::P3),
Box::new(move |pos| {
(
tileset_ref,
TileSetting::from_raw(
*tilemap::CLOUD_MAP
.get((pos.x + tilemap::WIDTH * pos.y) as usize)
.unwrap_or(&0),
),
)
}),
);
let start_pos = if start_at_boss {
(130 * 8, 8).into()
} else {
(8, 8).into()
};
let mut game = Game::new( let mut game = Game::new(
&object, &object,
Level::load_level( Level::load_level(backdrop, foreground, clouds, start_pos, &mut vram, &mut sfx),
background.get_regular().unwrap(),
background.get_regular().unwrap(),
background.get_regular().unwrap(),
),
&mut background,
start_at_boss, start_at_boss,
); );
@ -2259,7 +2300,7 @@ fn game_with_level(gba: &mut agb::Gba) {
sfx.frame(); sfx.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
sfx.after_vblank(); sfx.after_vblank();
match game.advance_frame(&object, &mut sfx) { match game.advance_frame(&object, &mut vram, &mut sfx) {
GameStatus::Continue => {} GameStatus::Continue => {}
GameStatus::Lost => { GameStatus::Lost => {
break false; break false;
@ -2270,7 +2311,9 @@ fn game_with_level(gba: &mut agb::Gba) {
} }
get_random(); // advance RNG to make it less predictable between runs get_random(); // advance RNG to make it less predictable between runs
} };
game.clear(&mut vram);
} }
} }