mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Merge pull request #182 from gwilymk/better-backgrounds-gwilym
Improve background management
This commit is contained in:
commit
3710d9ad1c
|
@ -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 position: Vector2D<T>,
|
||||
pub size: Vector2D<T>,
|
||||
|
@ -653,19 +653,21 @@ impl<T: Number> Rect<T> {
|
|||
|
||||
impl<T: FixedWidthUnsignedInteger> Rect<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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Some((x, y))
|
||||
let ret_x = x;
|
||||
x = x + T::one();
|
||||
|
||||
Some((ret_x, y))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#![no_main]
|
||||
|
||||
use agb::{
|
||||
display::tiled::{TileFormat, TileSet, TileSetting},
|
||||
display::{
|
||||
background::Map,
|
||||
object::{Object, ObjectController, Size, Sprite},
|
||||
palette16::Palette16,
|
||||
HEIGHT, WIDTH,
|
||||
|
@ -47,15 +47,26 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
.unwrap()
|
||||
};
|
||||
|
||||
let mut gfx = gba.display.video.tiled0();
|
||||
let (gfx, mut vram) = gba.display.video.tiled0();
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
|
||||
gfx.set_background_palette_raw(&MAP_PALETTE);
|
||||
gfx.set_background_tilemap(0, &MAP_TILES);
|
||||
vram.set_background_palette_raw(&MAP_PALETTE);
|
||||
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.commit();
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@ use agb::display::example_logo;
|
|||
|
||||
#[agb::entry]
|
||||
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 {}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@ struct BackCosines {
|
|||
|
||||
#[agb::entry]
|
||||
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 cosines = [0_u16; 32];
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Bitarray<const N: usize> {
|
||||
a: [u32; N],
|
||||
}
|
||||
|
@ -21,7 +22,26 @@ impl<const N: usize> Bitarray<N> {
|
|||
let value_mask = value << (index % 32);
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +1,31 @@
|
|||
use crate::display::background::BackgroundDistributor;
|
||||
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager};
|
||||
|
||||
crate::include_gfx!("gfx/agb_logo.toml");
|
||||
|
||||
pub fn display_logo(gfx: &mut BackgroundDistributor) {
|
||||
use super::background::Map;
|
||||
gfx.set_background_palettes(agb_logo::test_logo.palettes);
|
||||
gfx.set_background_tilemap(0, agb_logo::test_logo.tiles);
|
||||
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
|
||||
vram.set_background_palettes(agb_logo::test_logo.palettes);
|
||||
|
||||
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 tile_id in 0..(30 * 20) {
|
||||
let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize] as u16;
|
||||
entries[tile_id as usize] = tile_id | (palette_entry << 12);
|
||||
for y in 0..20 {
|
||||
for x in 0..30 {
|
||||
let tile_id = y * 30 + x;
|
||||
|
||||
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));
|
||||
back.show();
|
||||
back.commit();
|
||||
map.commit();
|
||||
map.show();
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -25,9 +33,11 @@ mod tests {
|
|||
|
||||
#[test_case]
|
||||
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");
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ use video::Video;
|
|||
|
||||
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.
|
||||
pub mod bitmap3;
|
||||
/// Graphics mode 4. Bitmap 4 provides two 8-bit paletted framebuffers with page switching.
|
||||
|
@ -20,6 +18,8 @@ pub mod object;
|
|||
pub mod palette16;
|
||||
/// Data produced by agb-image-converter
|
||||
pub mod tile_data;
|
||||
/// Graphics mode 0. Four regular backgrounds.
|
||||
pub mod tiled;
|
||||
/// Giving out graphics mode.
|
||||
pub mod video;
|
||||
|
||||
|
@ -110,7 +110,7 @@ pub fn busy_wait_for_vblank() {
|
|||
while VCOUNT.get() < 160 {}
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier)]
|
||||
#[derive(BitfieldSpecifier, Clone, Copy)]
|
||||
pub enum Priority {
|
||||
P0 = 0,
|
||||
P1 = 1,
|
||||
|
|
236
agb/src/display/tiled/infinite_scrolled_map.rs
Normal file
236
agb/src/display/tiled/infinite_scrolled_map.rs
Normal 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
|
||||
}
|
||||
}
|
185
agb/src/display/tiled/map.rs
Normal file
185
agb/src/display/tiled/map.rs
Normal 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);
|
||||
}
|
||||
}
|
49
agb/src/display/tiled/mod.rs
Normal file
49
agb/src/display/tiled/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
37
agb/src/display/tiled/tiled0.rs
Normal file
37
agb/src/display/tiled/tiled0.rs
Normal 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)
|
||||
}
|
||||
}
|
280
agb/src/display/tiled/vram_manager.rs
Normal file
280
agb/src/display/tiled/vram_manager.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
use super::{background::BackgroundDistributor, bitmap3::Bitmap3, bitmap4::Bitmap4};
|
||||
use super::{
|
||||
bitmap3::Bitmap3,
|
||||
bitmap4::Bitmap4,
|
||||
tiled::{Tiled0, VRamManager},
|
||||
};
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Video {}
|
||||
|
@ -14,7 +18,7 @@ impl Video {
|
|||
unsafe { Bitmap4::new() }
|
||||
}
|
||||
|
||||
pub fn tiled0(&mut self) -> BackgroundDistributor {
|
||||
unsafe { BackgroundDistributor::new() }
|
||||
pub fn tiled0(&mut self) -> (Tiled0, VRamManager<'_>) {
|
||||
(unsafe { Tiled0::new() }, VRamManager::new())
|
||||
}
|
||||
}
|
||||
|
|
26
agb/src/dma.rs
Normal file
26
agb/src/dma.rs
Normal 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));
|
||||
}
|
|
@ -142,6 +142,7 @@ mod arena;
|
|||
mod bitarray;
|
||||
/// Implements everything relating to things that are displayed on screen.
|
||||
pub mod display;
|
||||
mod dma;
|
||||
/// Button inputs to the system.
|
||||
pub mod input;
|
||||
#[doc(hidden)] // hide for now as the implementation in here is unsound
|
||||
|
|
|
@ -49,9 +49,11 @@ impl<T, const N: usize> MemoryMapped1DArray<T, N> {
|
|||
array: address as *mut [T; N],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, n: usize) -> T {
|
||||
unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() }
|
||||
}
|
||||
|
||||
pub fn set(&self, n: usize, val: T) {
|
||||
unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) }
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.8.0", path = "../../agb", default-features = false }
|
||||
agb = { version = "0.8.0", path = "../../agb" }
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -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 NUMBERS_START: u16 = 12 * 28 + 3;
|
||||
const HYPHEN: u16 = 12 * 28 + 11;
|
||||
pub const BLANK: u16 = 11 * 28;
|
||||
|
||||
pub fn write_level(background: &mut BackgroundRegister, world: u32, level: u32) {
|
||||
let map = background.get_block();
|
||||
let mut counter = 0;
|
||||
pub fn write_level(
|
||||
map: &mut RegularMap,
|
||||
world: u32,
|
||||
level: u32,
|
||||
tile_set_ref: TileSetReference,
|
||||
vram: &mut VRamManager,
|
||||
) {
|
||||
for (i, &tile) in [
|
||||
LEVEL_START,
|
||||
LEVEL_START + 1,
|
||||
LEVEL_START + 2,
|
||||
BLANK,
|
||||
world as u16 + NUMBERS_START - 1,
|
||||
HYPHEN,
|
||||
level as u16 + NUMBERS_START - 1,
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
map.set_tile(
|
||||
vram,
|
||||
(i as u16, 0).into(),
|
||||
tile_set_ref,
|
||||
TileSetting::from_raw(tile),
|
||||
);
|
||||
}
|
||||
|
||||
map[0][0] = LEVEL_START;
|
||||
map[0][1] = LEVEL_START + 1;
|
||||
map[0][2] = LEVEL_START + 2;
|
||||
|
||||
counter += 4;
|
||||
|
||||
map[0][counter] = world as u16 + NUMBERS_START - 1;
|
||||
counter += 1;
|
||||
map[0][counter] = HYPHEN;
|
||||
counter += 1;
|
||||
map[0][counter] = level as u16 + NUMBERS_START - 1;
|
||||
counter += 1;
|
||||
|
||||
background.set_position((-(WIDTH / 2 - counter as i32 * 8 / 2), -(HEIGHT / 2 - 4)).into());
|
||||
map.set_scroll_pos(
|
||||
(
|
||||
-(WIDTH / 2 - 7 as i32 * 8 / 2) as u16,
|
||||
-(HEIGHT / 2 - 4) as u16,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
#![no_std]
|
||||
#![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 level_display;
|
||||
mod sfx;
|
||||
|
@ -82,16 +97,6 @@ mod map_tiles {
|
|||
|
||||
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 TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
||||
|
@ -258,40 +263,27 @@ impl<'a> Entity<'a> {
|
|||
}
|
||||
|
||||
struct Map<'a, 'b> {
|
||||
background: &'a mut BackgroundRegular<'b>,
|
||||
foreground: &'a mut BackgroundRegular<'b>,
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
level: &'a Level,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c> Map<'a, 'b> {
|
||||
pub fn commit_position(&mut self) {
|
||||
self.background.set_position(self.position.floor());
|
||||
self.foreground.set_position(self.position.floor());
|
||||
impl<'a, 'b> Map<'a, 'b> {
|
||||
pub fn commit_position(&mut self, vram: &mut VRamManager) {
|
||||
self.background.set_pos(vram, self.position.floor());
|
||||
self.foreground.set_pos(vram, self.position.floor());
|
||||
|
||||
self.background.commit();
|
||||
self.foreground.commit();
|
||||
}
|
||||
|
||||
fn load_foreground(&'c mut self) -> impl Iterator<Item = ()> + 'c {
|
||||
self.background.set_position(self.position.floor());
|
||||
self.background.set_map(agb::display::background::Map::new(
|
||||
self.level.foreground,
|
||||
self.level.dimensions,
|
||||
0,
|
||||
));
|
||||
self.background.commit_partial()
|
||||
pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.background.init_partial(vram, self.position.floor())
|
||||
}
|
||||
|
||||
fn load_background(&'c mut self) -> impl Iterator<Item = ()> + 'c {
|
||||
self.foreground.set_position(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()
|
||||
pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.foreground.init_partial(vram, self.position.floor())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,8 +613,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
|
|||
fn open_level(
|
||||
level: &'a Level,
|
||||
object_control: &'a ObjectController,
|
||||
background: &'a mut BackgroundRegular<'b>,
|
||||
foreground: &'a mut BackgroundRegular<'b>,
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
input: ButtonController,
|
||||
) -> Self {
|
||||
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) {
|
||||
self.background.background.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) {
|
||||
self.player.wizard.velocity = (0, -1).into();
|
||||
self.player.wizard.sprite.set_priority(Priority::P0);
|
||||
|
@ -696,8 +690,9 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
|
|||
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
sfx_player: &mut sfx::SfxPlayer,
|
||||
vram: &mut VRamManager,
|
||||
controller: &'a ObjectController,
|
||||
) -> UpdateState {
|
||||
self.timer += 1;
|
||||
self.input.update();
|
||||
|
@ -728,7 +723,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
|
|||
}
|
||||
|
||||
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.hat.commit_position(self.background.position);
|
||||
|
@ -783,24 +778,45 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
|
|||
|
||||
#[agb::entry]
|
||||
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 {
|
||||
let mut tiled = agb.display.video.tiled0();
|
||||
vram.set_background_palettes(tile_sheet::background.palettes);
|
||||
|
||||
let mut object = agb.display.object.get();
|
||||
let mut timer_controller = agb.timers.timers();
|
||||
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();
|
||||
let mut music_box = sfx::MusicBox::new();
|
||||
|
||||
|
@ -821,8 +837,11 @@ fn main(mut agb: agb::Gba) -> ! {
|
|||
&mut world_display,
|
||||
current_level / 8 + 1,
|
||||
current_level % 8 + 1,
|
||||
tile_set_ref,
|
||||
&mut vram,
|
||||
);
|
||||
|
||||
world_display.commit();
|
||||
world_display.show();
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
|
@ -830,6 +849,37 @@ fn main(mut agb: agb::Gba) -> ! {
|
|||
vblank.wait_for_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(
|
||||
&map_tiles::LEVELS[current_level as usize],
|
||||
&object,
|
||||
|
@ -837,33 +887,38 @@ fn main(mut agb: agb::Gba) -> ! {
|
|||
&mut foreground,
|
||||
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);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
|
||||
level_load.next();
|
||||
}
|
||||
level_load.count();
|
||||
let mut level_load = level.load_2().step_by(24);
|
||||
for _ in 0..30 {
|
||||
|
||||
while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_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();
|
||||
|
||||
world_display.hide();
|
||||
|
||||
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::Dead => {
|
||||
level.dead_start();
|
||||
|
@ -886,13 +941,17 @@ fn main(mut agb: agb::Gba) -> ! {
|
|||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
level.hide_backgrounds();
|
||||
level.clear_backgrounds(&mut vram);
|
||||
}
|
||||
|
||||
splash_screen::show_splash_screen(
|
||||
&mut agb,
|
||||
splash_screen::SplashScreen::End,
|
||||
Some(&mut mixer),
|
||||
Some(&mut music_box),
|
||||
&mut splash_screen,
|
||||
&mut vram,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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");
|
||||
|
||||
|
@ -9,39 +12,77 @@ pub enum SplashScreen {
|
|||
}
|
||||
|
||||
pub fn show_splash_screen(
|
||||
agb: &mut agb::Gba,
|
||||
which: SplashScreen,
|
||||
mut mixer: Option<&mut Mixer>,
|
||||
mut music_box: Option<&mut MusicBox>,
|
||||
map: &mut RegularMap,
|
||||
vram: &mut VRamManager,
|
||||
) {
|
||||
let mut tiled = agb.display.video.tiled0();
|
||||
|
||||
match which {
|
||||
map.set_scroll_pos((0u16, 0u16).into());
|
||||
let (tile_set_ref, palette) = match which {
|
||||
SplashScreen::Start => {
|
||||
tiled.set_background_tilemap(0, splash_screens::splash.tiles);
|
||||
tiled.set_background_palettes(splash_screens::splash.palettes);
|
||||
let tile_set_ref = vram.add_tileset(TileSet::new(
|
||||
splash_screens::splash.tiles,
|
||||
TileFormat::FourBpp,
|
||||
));
|
||||
|
||||
(tile_set_ref, splash_screens::splash.palettes)
|
||||
}
|
||||
SplashScreen::End => {
|
||||
tiled.set_background_tilemap(0, splash_screens::thanks_for_playing.tiles);
|
||||
tiled.set_background_palettes(splash_screens::thanks_for_playing.palettes);
|
||||
let tile_set_ref = vram.add_tileset(TileSet::new(
|
||||
splash_screens::thanks_for_playing.tiles,
|
||||
TileFormat::FourBpp,
|
||||
));
|
||||
|
||||
(tile_set_ref, splash_screens::thanks_for_playing.palettes)
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
splash_screen_display.show();
|
||||
map.commit();
|
||||
vram.set_background_palettes(palette);
|
||||
map.show();
|
||||
|
||||
loop {
|
||||
input.update();
|
||||
if input.is_just_pressed(
|
||||
|
@ -64,5 +105,9 @@ pub fn show_splash_screen(
|
|||
mixer.after_vblank();
|
||||
}
|
||||
}
|
||||
splash_screen_display.hide();
|
||||
|
||||
map.hide();
|
||||
map.clear(vram);
|
||||
|
||||
vram.remove_tileset(tile_set_ref);
|
||||
}
|
||||
|
|
|
@ -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),*];
|
||||
|
|
|
@ -8,20 +8,22 @@ 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},
|
||||
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
|
||||
tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager},
|
||||
Priority, HEIGHT, WIDTH,
|
||||
},
|
||||
fixnum::{FixedNum, Rect, Vector2D},
|
||||
input::{Button, ButtonController, Tri},
|
||||
interrupt::VBlank,
|
||||
};
|
||||
use generational_arena::Arena;
|
||||
use sfx::Sfx;
|
||||
|
||||
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite");
|
||||
const TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
@ -54,45 +56,36 @@ agb::include_gfx!("gfx/background.toml");
|
|||
|
||||
type Number = FixedNum<8>;
|
||||
|
||||
struct Level {
|
||||
background: BackgroundRegular<'static>,
|
||||
foreground: BackgroundRegular<'static>,
|
||||
clouds: BackgroundRegular<'static>,
|
||||
struct Level<'a> {
|
||||
background: InfiniteScrolledMap<'a>,
|
||||
foreground: InfiniteScrolledMap<'a>,
|
||||
clouds: InfiniteScrolledMap<'a>,
|
||||
|
||||
slime_spawns: Vec<(u16, u16)>,
|
||||
bat_spawns: Vec<(u16, u16)>,
|
||||
emu_spawns: Vec<(u16, u16)>,
|
||||
}
|
||||
|
||||
impl Level {
|
||||
impl<'a> Level<'a> {
|
||||
fn load_level(
|
||||
mut backdrop: BackgroundRegular<'static>,
|
||||
mut foreground: BackgroundRegular<'static>,
|
||||
mut clouds: BackgroundRegular<'static>,
|
||||
mut backdrop: InfiniteScrolledMap<'a>,
|
||||
mut foreground: InfiniteScrolledMap<'a>,
|
||||
mut clouds: InfiniteScrolledMap<'a>,
|
||||
start_pos: Vector2D<i32>,
|
||||
vram: &mut VRamManager,
|
||||
sfx: &mut Sfx,
|
||||
) -> 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);
|
||||
let vblank = VBlank::get();
|
||||
|
||||
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);
|
||||
let mut between_updates = || {
|
||||
sfx.frame();
|
||||
vblank.wait_for_vblank();
|
||||
sfx.after_vblank();
|
||||
};
|
||||
|
||||
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, start_pos, &mut between_updates);
|
||||
foreground.init(vram, start_pos, &mut between_updates);
|
||||
clouds.init(vram, start_pos / 4, &mut between_updates);
|
||||
|
||||
backdrop.commit();
|
||||
foreground.commit();
|
||||
|
@ -150,6 +143,12 @@ impl Level {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self, vram: &mut VRamManager) {
|
||||
self.background.clear(vram);
|
||||
self.foreground.clear(vram);
|
||||
self.clouds.clear(vram);
|
||||
}
|
||||
}
|
||||
|
||||
struct Entity<'a> {
|
||||
|
@ -392,10 +391,10 @@ impl SwordState {
|
|||
}
|
||||
fn attack_frame(self, timer: u16) -> u16 {
|
||||
match self {
|
||||
SwordState::LongSword => (self.attack_duration() - timer) / 8,
|
||||
SwordState::ShortSword => (self.attack_duration() - timer) / 8,
|
||||
SwordState::Dagger => (self.attack_duration() - timer) / 8,
|
||||
SwordState::Swordless => (self.attack_duration() - timer) / 8,
|
||||
SwordState::LongSword => (self.attack_duration().saturating_sub(timer)) / 8,
|
||||
SwordState::ShortSword => (self.attack_duration().saturating_sub(timer)) / 8,
|
||||
SwordState::Dagger => (self.attack_duration().saturating_sub(timer)) / 8,
|
||||
SwordState::Swordless => (self.attack_duration().saturating_sub(timer)) / 8,
|
||||
}
|
||||
}
|
||||
fn jump_attack_tag(self) -> &'static Tag {
|
||||
|
@ -407,7 +406,7 @@ impl SwordState {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
7
|
||||
|
@ -1870,7 +1869,7 @@ struct Game<'a> {
|
|||
player: Player<'a>,
|
||||
input: ButtonController,
|
||||
frame_count: u32,
|
||||
level: Level,
|
||||
level: Level<'a>,
|
||||
offset: Vector2D<Number>,
|
||||
shake_time: u16,
|
||||
sunrise_timer: u16,
|
||||
|
@ -1883,8 +1882,6 @@ struct Game<'a> {
|
|||
boss: BossState<'a>,
|
||||
move_state: MoveState,
|
||||
fade_count: u16,
|
||||
|
||||
background_distributor: &'a mut BackgroundDistributor,
|
||||
}
|
||||
|
||||
enum MoveState {
|
||||
|
@ -1902,9 +1899,14 @@ impl<'a> Game<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self, vram: &mut VRamManager) {
|
||||
self.level.clear(vram);
|
||||
}
|
||||
|
||||
fn advance_frame(
|
||||
&mut self,
|
||||
object_controller: &'a ObjectController,
|
||||
vram: &mut VRamManager,
|
||||
sfx: &mut sfx::Sfx,
|
||||
) -> GameStatus {
|
||||
let mut state = GameStatus::Continue;
|
||||
|
@ -1924,7 +1926,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 {
|
||||
|
@ -1946,7 +1948,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2049,18 +2051,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);
|
||||
self.level.background.commit();
|
||||
self.level.foreground.commit();
|
||||
self.level.clouds.commit();
|
||||
let background_offset = (this_frame_offset.floor().x, 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);
|
||||
|
||||
for i in remove {
|
||||
self.enemies.remove(i);
|
||||
|
@ -2105,6 +2100,10 @@ impl<'a> Game<'a> {
|
|||
.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 {
|
||||
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 a = modified_palette.get_colour(0);
|
||||
|
@ -2173,10 +2172,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);
|
||||
|
@ -2187,15 +2186,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 ObjectController,
|
||||
level: Level,
|
||||
background_distributor: &'a mut BackgroundDistributor,
|
||||
start_at_boss: bool,
|
||||
) -> Self {
|
||||
fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self {
|
||||
let mut player = Player::new(object);
|
||||
let mut offset = (8, 8).into();
|
||||
if start_at_boss {
|
||||
|
@ -2219,8 +2213,6 @@ impl<'a> Game<'a> {
|
|||
move_state: MoveState::Advancing,
|
||||
sunrise_timer: 0,
|
||||
fade_count: 0,
|
||||
|
||||
background_distributor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2239,19 +2231,68 @@ fn game_with_level(gba: &mut agb::Gba) {
|
|||
let mut start_at_boss = false;
|
||||
|
||||
loop {
|
||||
let mut background = gba.display.video.tiled0();
|
||||
background.set_background_palettes(background::background.palettes);
|
||||
background.set_background_tilemap(0, background::background.tiles);
|
||||
let (background, mut vram) = gba.display.video.tiled0();
|
||||
|
||||
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 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(
|
||||
&object,
|
||||
Level::load_level(
|
||||
background.get_regular().unwrap(),
|
||||
background.get_regular().unwrap(),
|
||||
background.get_regular().unwrap(),
|
||||
),
|
||||
&mut background,
|
||||
Level::load_level(backdrop, foreground, clouds, start_pos, &mut vram, &mut sfx),
|
||||
start_at_boss,
|
||||
);
|
||||
|
||||
|
@ -2259,7 +2300,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 vram, &mut sfx) {
|
||||
GameStatus::Continue => {}
|
||||
GameStatus::Lost => {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
game.clear(&mut vram);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue