Deduplicate background tiles (#477)

This started as an attempt to get dungeon puzzler in the combo rom,
ended up fixing a _load_ of 256 colour bugs and the ability to
deduplicate tiles you import.

Fixes #448

- [x] Changelog updated / no changelog update needed
This commit is contained in:
Gwilym Inzani 2023-08-29 20:02:38 +01:00 committed by GitHub
commit dc5bf9de98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 711 additions and 201 deletions

View file

@ -12,14 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New tracker for playing XM files (see the `agb-tracker` crate). - New tracker for playing XM files (see the `agb-tracker` crate).
- You can now declare where looping sound channels should restart. - You can now declare where looping sound channels should restart.
- Fixnums now have constructors from_f32 and from_f64. This is mainly useful if using agb-fixnum outside of the Game Boy Advance e.g. in build scripts or macros. - Fixnums now have constructors from_f32 and from_f64. This is mainly useful if using agb-fixnum outside of the Game Boy Advance e.g. in build scripts or macros.
- New option when loading a background to automatically deduplicate tiles.
- Methods on tile_setting to toggle its hflip and vflip status.
### Changed ### Changed
- Sound channel panning and volume options are now `Num<i16, 8>` rather than `Num<i16, 4>` for improved precision and sound quality. - Sound channel panning and volume options are now `Num<i16, 8>` rather than `Num<i16, 4>` for improved precision and sound quality.
- Due to dependency changes, agb-gbafix is now released under MPL rather than GPL. - Due to dependency changes, agb-gbafix is now released under MPL rather than GPL.
- `include_background_gfx!` now produces tile settings directly rather than palette assigments.
### Fixed ### Fixed
- 256-colour backgrounds are better supported.
- Mono looping samples will now correctly play to the end if it doesn't perfectly align with a buffer boundry and short samples now also loop correctly. - Mono looping samples will now correctly play to the end if it doesn't perfectly align with a buffer boundry and short samples now also loop correctly.
## [0.16.0] - 2023/07/18 ## [0.16.0] - 2023/07/18

View file

@ -11,4 +11,5 @@ pub(crate) trait Config {
pub(crate) trait Image { pub(crate) trait Image {
fn filename(&self) -> String; fn filename(&self) -> String;
fn colours(&self) -> Colours; fn colours(&self) -> Colours;
fn deduplicate(&self) -> bool;
} }

View file

@ -0,0 +1,152 @@
use std::{collections::HashMap, hash::BuildHasher};
use crate::{colour::Colour, image_loader::Image};
pub struct Transformation {
pub vflip: bool,
pub hflip: bool,
}
impl Transformation {
pub fn none() -> Self {
Self {
vflip: false,
hflip: false,
}
}
pub fn vflipped() -> Self {
Self {
vflip: true,
hflip: false,
}
}
pub fn hflipped() -> Self {
Self {
vflip: false,
hflip: true,
}
}
pub fn vhflipped() -> Self {
Self {
vflip: true,
hflip: true,
}
}
}
pub struct DeduplicatedData {
pub new_index: usize,
pub transformation: Transformation,
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct Tile {
data: [Colour; 64],
}
impl Tile {
fn split_image(input: &Image) -> Vec<Self> {
let mut ret = vec![];
for y in 0..(input.height / 8) {
for x in 0..(input.width / 8) {
let mut tile_data = Vec::with_capacity(64);
for j in 0..8 {
for i in 0..8 {
tile_data.push(input.colour(x * 8 + i, y * 8 + j));
}
}
ret.push(Tile {
data: tile_data.try_into().unwrap(),
});
}
}
ret
}
fn vflipped(&self) -> Self {
let mut new_data = self.data;
for y in 0..4 {
for x in 0..8 {
new_data.swap(y * 8 + x, (7 - y) * 8 + x);
}
}
Self { data: new_data }
}
fn hflipped(&self) -> Self {
let mut new_data = self.data;
for y in 0..8 {
for x in 0..4 {
new_data.swap(y * 8 + x, y * 8 + (7 - x));
}
}
Self { data: new_data }
}
}
pub(crate) fn deduplicate_image(input: &Image, can_flip: bool) -> (Image, Vec<DeduplicatedData>) {
let mut resulting_tiles = vec![];
let mut deduplication_data = vec![];
let all_tiles = Tile::split_image(input);
let mut existing_tiles = HashMap::new();
let hasher = std::collections::hash_map::RandomState::new();
for tile in all_tiles {
let (tile, transformation) = if can_flip {
let vflipped = tile.vflipped();
let hflipped = tile.hflipped();
let vhflipped = vflipped.hflipped();
// find the one with the smallest hash
let tile_hash = hasher.hash_one(&tile);
let vflipped_hash = hasher.hash_one(&vflipped);
let hflipped_hash = hasher.hash_one(&hflipped);
let vhflipped_hash = hasher.hash_one(&vhflipped);
let minimum = tile_hash
.min(vflipped_hash)
.min(hflipped_hash)
.min(vhflipped_hash);
if minimum == tile_hash {
(tile, Transformation::none())
} else if minimum == vflipped_hash {
(vflipped, Transformation::vflipped())
} else if minimum == hflipped_hash {
(hflipped, Transformation::hflipped())
} else {
(vhflipped, Transformation::vhflipped())
}
} else {
(tile, Transformation::none())
};
let index = *existing_tiles.entry(tile.clone()).or_insert_with(|| {
resulting_tiles.push(tile);
resulting_tiles.len() - 1
});
deduplication_data.push(DeduplicatedData {
new_index: index,
transformation,
});
}
let image_data = resulting_tiles
.iter()
.flat_map(|tile| tile.data)
.collect::<Vec<_>>();
(Image::from_colour_data(image_data), deduplication_data)
}

View file

@ -4,6 +4,7 @@ use image::{DynamicImage, GenericImageView};
use crate::colour::Colour; use crate::colour::Colour;
#[derive(Clone)]
pub(crate) struct Image { pub(crate) struct Image {
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
@ -42,6 +43,14 @@ impl Image {
} }
} }
pub fn from_colour_data(colour_data: Vec<Colour>) -> Self {
Self {
height: colour_data.len() / 8,
colour_data,
width: 8,
}
}
pub fn colour(&self, x: usize, y: usize) -> Colour { pub fn colour(&self, x: usize, y: usize) -> Colour {
self.colour_data[x + y * self.width] self.colour_data[x + y * self.width]
} }

View file

@ -15,6 +15,7 @@ use quote::{format_ident, quote, ToTokens};
mod aseprite; mod aseprite;
mod colour; mod colour;
mod config; mod config;
mod deduplicator;
mod font_loader; mod font_loader;
mod image_loader; mod image_loader;
mod palette16; mod palette16;
@ -36,6 +37,7 @@ struct BackgroundGfxOption {
module_name: String, module_name: String,
file_name: String, file_name: String,
colours: Colours, colours: Colours,
deduplicate: bool,
} }
impl config::Image for BackgroundGfxOption { impl config::Image for BackgroundGfxOption {
@ -46,6 +48,10 @@ impl config::Image for BackgroundGfxOption {
fn colours(&self) -> Colours { fn colours(&self) -> Colours {
self.colours self.colours
} }
fn deduplicate(&self) -> bool {
self.deduplicate
}
} }
impl Parse for BackgroundGfxOption { impl Parse for BackgroundGfxOption {
@ -72,12 +78,30 @@ impl Parse for BackgroundGfxOption {
Colours::Colours16 Colours::Colours16
}; };
let lookahead = input.lookahead1();
let deduplicate = if lookahead.peek(syn::Ident) {
let deduplicate: syn::Ident = input.parse()?;
if deduplicate == "deduplicate" {
true
} else {
return Err(syn::Error::new_spanned(
deduplicate,
"Must either be the literal deduplicate or missing",
));
}
} else {
false
};
let file_name: syn::LitStr = input.parse()?; let file_name: syn::LitStr = input.parse()?;
Ok(Self { Ok(Self {
module_name: module_name.to_string(), module_name: module_name.to_string(),
file_name: file_name.value(), file_name: file_name.value(),
colours, colours,
deduplicate,
}) })
} }
} }
@ -406,6 +430,7 @@ fn convert_image(
) -> proc_macro2::TokenStream { ) -> proc_macro2::TokenStream {
let image_filename = &parent.join(settings.filename()); let image_filename = &parent.join(settings.filename());
let image = Image::load_from_file(image_filename); let image = Image::load_from_file(image_filename);
let deduplicate = settings.deduplicate();
rust_generator::generate_code( rust_generator::generate_code(
variable_name, variable_name,
@ -414,6 +439,7 @@ fn convert_image(
&image_filename.to_string_lossy(), &image_filename.to_string_lossy(),
crate_prefix.to_owned(), crate_prefix.to_owned(),
assignment_offset, assignment_offset,
deduplicate,
) )
} }
@ -468,7 +494,14 @@ fn palette_tile_data(
let mut tile_data = Vec::new(); let mut tile_data = Vec::new();
for (image_idx, image) in images.iter().enumerate() { for (image_idx, image) in images.iter().enumerate() {
add_image_to_tile_data(&mut tile_data, image, optimiser, image_idx, true) add_image_to_tile_data(
&mut tile_data,
image,
optimiser,
image_idx,
true,
&(0..images.len()).collect::<Vec<_>>(),
);
} }
let tile_data = collapse_to_4bpp(&tile_data); let tile_data = collapse_to_4bpp(&tile_data);
@ -491,6 +524,7 @@ fn add_image_to_tile_data(
optimiser: &Palette16OptimisationResults, optimiser: &Palette16OptimisationResults,
assignment_offset: usize, assignment_offset: usize,
is_sprite: bool, is_sprite: bool,
remap_index: &[usize],
) { ) {
let tile_size = 8; let tile_size = 8;
let tiles_x = image.width / tile_size; let tiles_x = image.width / tile_size;
@ -501,7 +535,7 @@ fn add_image_to_tile_data(
let assignment = if is_sprite { let assignment = if is_sprite {
assignment_offset assignment_offset
} else { } else {
y * tiles_x + x + assignment_offset remap_index[y * tiles_x + x] + assignment_offset
}; };
let palette_index = optimiser.assignments[assignment]; let palette_index = optimiser.assignments[assignment];

View file

@ -1,3 +1,4 @@
use crate::deduplicator::{DeduplicatedData, Transformation};
use crate::palette16::Palette16OptimisationResults; use crate::palette16::Palette16OptimisationResults;
use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp}; use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp};
use crate::{image_loader::Image, ByteString}; use crate::{image_loader::Image, ByteString};
@ -5,6 +6,7 @@ use crate::{image_loader::Image, ByteString};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use std::collections::BTreeMap;
use std::iter; use std::iter;
pub(crate) fn generate_palette_code( pub(crate) fn generate_palette_code(
@ -40,36 +42,77 @@ pub(crate) fn generate_code(
image_filename: &str, image_filename: &str,
crate_prefix: String, crate_prefix: String,
assignment_offset: Option<usize>, assignment_offset: Option<usize>,
deduplicate: bool,
) -> TokenStream { ) -> TokenStream {
let crate_prefix = format_ident!("{}", crate_prefix); let crate_prefix = format_ident!("{}", crate_prefix);
let output_variable_name = format_ident!("{}", output_variable_name); let output_variable_name = format_ident!("{}", output_variable_name);
let (image, dedup_data) = if deduplicate {
let (new_image, dedup_data) =
crate::deduplicator::deduplicate_image(image, assignment_offset.is_some());
(new_image, dedup_data)
} else {
(
image.clone(),
(0..(image.width * image.height / 8 / 8))
.map(|i| DeduplicatedData {
new_index: i,
transformation: Transformation::none(),
})
.collect(),
)
};
let remap_index = dedup_data
.iter()
.enumerate()
.map(|(i, data)| (data.new_index, i))
.collect::<BTreeMap<_, _>>(); // BTreeMap so that values below is in order
let remap_index = remap_index.values().cloned().collect::<Vec<_>>();
let (tile_data, assignments) = if let Some(assignment_offset) = assignment_offset { let (tile_data, assignments) = if let Some(assignment_offset) = assignment_offset {
let mut tile_data = Vec::new(); let mut tile_data = Vec::new();
add_image_to_tile_data(&mut tile_data, image, results, assignment_offset, false); add_image_to_tile_data(
&mut tile_data,
&image,
results,
assignment_offset,
false,
&remap_index,
);
let tile_data = collapse_to_4bpp(&tile_data); let tile_data = collapse_to_4bpp(&tile_data);
let num_tiles = image.width * image.height / 8usize.pow(2); let num_tiles = image.width * image.height / 8usize.pow(2);
let assignments = results let all_assignments = &results.assignments[assignment_offset..];
.assignments let assignments = (0..num_tiles)
.iter() .map(|tile_id| all_assignments[remap_index[tile_id]] as u8)
.skip(assignment_offset)
.take(num_tiles)
.map(|&x| x as u8)
.collect(); .collect();
(tile_data, assignments) (tile_data, assignments)
} else { } else {
let mut tile_data = Vec::new(); let mut tile_data = Vec::new();
add_image_256_to_tile_data(&mut tile_data, image, results); add_image_256_to_tile_data(&mut tile_data, &image, results);
(tile_data, vec![]) (tile_data, vec![])
}; };
let tile_settings = dedup_data.iter().map(|data| {
let palette_assignment = assignments.get(data.new_index).unwrap_or(&0);
let vflipped = data.transformation.vflip;
let hflipped = data.transformation.hflip;
let index = data.new_index as u16;
quote! {
#crate_prefix::display::tiled::TileSetting::new(#index, #hflipped, #vflipped, #palette_assignment)
}
});
let data = ByteString(&tile_data); let data = ByteString(&tile_data);
quote! { quote! {
@ -91,11 +134,11 @@ pub(crate) fn generate_code(
&ALIGNED.bytes &ALIGNED.bytes
}; };
const PALETTE_ASSIGNMENT: &[u8] = &[ const TILE_SETTINGS: &[#crate_prefix::display::tiled::TileSetting] = &[
#(#assignments),* #(#tile_settings),*
]; ];
#crate_prefix::display::tile_data::TileData::new(TILE_DATA, PALETTE_ASSIGNMENT) #crate_prefix::display::tile_data::TileData::new(TILE_DATA, TILE_SETTINGS)
}; };
} }
} }

View file

@ -3,7 +3,7 @@
use agb::{ use agb::{
display::{ display::{
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap}, tiled::{RegularBackgroundSize, TileFormat, TileSet, TiledMap},
Priority, Priority,
}, },
include_background_gfx, include_background_gfx,
@ -32,7 +32,7 @@ fn main(mut gba: agb::Gba) -> ! {
&mut vram, &mut vram,
(x, y).into(), (x, y).into(),
&tileset, &tileset,
TileSetting::new(0, false, false, 0), water_tiles::water_tiles.tile_settings[0],
); );
} }
} }

View file

@ -1,6 +1,6 @@
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}; use super::tiled::{RegularMap, TileFormat, TileSet, TiledMap, VRamManager};
crate::include_background_gfx!(crate, agb_logo, test_logo => "gfx/test_logo.png"); crate::include_background_gfx!(crate, agb_logo, test_logo => deduplicate "gfx/test_logo.png");
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
vram.set_background_palettes(agb_logo::PALETTES); vram.set_background_palettes(agb_logo::PALETTES);
@ -11,10 +11,12 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
for x in 0..30 { for x in 0..30 {
let tile_id = y * 30 + x; let tile_id = y * 30 + x;
let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize]; map.set_tile(
let tile_setting = TileSetting::new(tile_id, false, false, palette_entry); vram,
(x as u16, y as u16).into(),
map.set_tile(vram, (x, y).into(), &background_tilemap, tile_setting); &background_tilemap,
agb_logo::test_logo.tile_settings[tile_id],
);
} }
} }

View file

@ -1,15 +1,17 @@
use super::tiled::TileSetting;
#[non_exhaustive] #[non_exhaustive]
pub struct TileData { pub struct TileData {
pub tiles: &'static [u8], pub tiles: &'static [u8],
pub palette_assignments: &'static [u8], pub tile_settings: &'static [TileSetting],
} }
impl TileData { impl TileData {
#[must_use] #[must_use]
pub const fn new(tiles: &'static [u8], palette_assignments: &'static [u8]) -> Self { pub const fn new(tiles: &'static [u8], tile_settings: &'static [TileSetting]) -> Self {
TileData { TileData {
tiles, tiles,
palette_assignments, tile_settings,
} }
} }
} }

View file

@ -10,7 +10,7 @@ use crate::memory_mapped::MemoryMapped;
use super::{ use super::{
AffineBackgroundSize, BackgroundID, BackgroundSize, BackgroundSizePrivate, AffineBackgroundSize, BackgroundID, BackgroundSize, BackgroundSizePrivate,
RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager, RegularBackgroundSize, Tile, TileFormat, TileSet, TileSetting, VRamManager,
}; };
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
@ -20,10 +20,9 @@ pub trait TiledMapTypes: private::Sealed {
} }
trait TiledMapPrivate: TiledMapTypes { trait TiledMapPrivate: TiledMapTypes {
type TileType: Into<TileIndex> + Copy + Default + Eq + PartialEq;
type AffineMatrix; type AffineMatrix;
fn tiles_mut(&mut self) -> &mut [Self::TileType]; fn tiles_mut(&mut self) -> &mut [Tile];
fn tiles_dirty(&mut self) -> &mut bool; fn tiles_dirty(&mut self) -> &mut bool;
fn colours(&self) -> TileFormat; fn colours(&self) -> TileFormat;
@ -59,9 +58,11 @@ where
T::Size: BackgroundSizePrivate, T::Size: BackgroundSizePrivate,
{ {
fn clear(&mut self, vram: &mut VRamManager) { fn clear(&mut self, vram: &mut VRamManager) {
let colours = self.colours();
for tile in self.tiles_mut() { for tile in self.tiles_mut() {
if *tile != Default::default() { if *tile != Default::default() {
vram.remove_tile((*tile).into()); vram.remove_tile(tile.tile_index(colours));
} }
*tile = Default::default(); *tile = Default::default();
@ -82,18 +83,18 @@ where
fn commit(&mut self, vram: &mut VRamManager) { fn commit(&mut self, vram: &mut VRamManager) {
let screenblock_memory = self.screenblock_memory(); let screenblock_memory = self.screenblock_memory();
let tile_count_divisor = self.colours().tile_size() / TileFormat::FourBpp.tile_size();
if *self.tiles_dirty() { if *self.tiles_dirty() {
unsafe { unsafe {
dma_copy16( dma_copy16(
self.tiles_mut().as_ptr() as *const u16, self.tiles_mut().as_ptr() as *const u16,
screenblock_memory, screenblock_memory,
self.map_size().num_tiles() / tile_count_divisor, self.map_size().num_tiles(),
); );
} }
} }
let tile_colour_flag: u16 = (tile_count_divisor == 2).into(); let tile_colour_flag: u16 = (self.colours() == TileFormat::EightBpp).into();
let new_bg_control_value = (self.priority() as u16) let new_bg_control_value = (self.priority() as u16)
| ((self.screenblock() as u16) << 8) | ((self.screenblock() as u16) << 8)
@ -134,10 +135,9 @@ impl TiledMapTypes for RegularMap {
} }
impl TiledMapPrivate for RegularMap { impl TiledMapPrivate for RegularMap {
type TileType = Tile;
type AffineMatrix = (); type AffineMatrix = ();
fn tiles_mut(&mut self) -> &mut [Self::TileType] { fn tiles_mut(&mut self) -> &mut [Tile] {
&mut self.tiles &mut self.tiles
} }
fn tiles_dirty(&mut self) -> &mut bool { fn tiles_dirty(&mut self) -> &mut bool {
@ -195,11 +195,12 @@ impl RegularMap {
tileset: &TileSet<'_>, tileset: &TileSet<'_>,
tile_setting: TileSetting, tile_setting: TileSetting,
) { ) {
if tileset.format() != self.colours() { let colours = self.colours();
if tileset.format() != colours {
panic!( panic!(
"Cannot set a {:?} colour tile on a {:?} colour background", "Cannot set a {:?} colour tile on a {:?} colour background",
tileset.format(), tileset.format(),
self.colours() colours
); );
} }
@ -207,7 +208,7 @@ impl RegularMap {
let old_tile = self.tiles_mut()[pos]; let old_tile = self.tiles_mut()[pos];
if old_tile != Tile::default() { if old_tile != Tile::default() {
vram.remove_tile(old_tile.into()); vram.remove_tile(old_tile.tile_index(colours));
} }
let tile_index = tile_setting.index(); let tile_index = tile_setting.index();
@ -254,7 +255,7 @@ pub struct AffineMap {
transform: AffineMatrixBackground, transform: AffineMatrixBackground,
tiles: Vec<u8>, tiles: Vec<Tile>,
tiles_dirty: bool, tiles_dirty: bool,
} }
@ -263,10 +264,9 @@ impl TiledMapTypes for AffineMap {
} }
impl TiledMapPrivate for AffineMap { impl TiledMapPrivate for AffineMap {
type TileType = u8;
type AffineMatrix = AffineMatrixBackground; type AffineMatrix = AffineMatrixBackground;
fn tiles_mut(&mut self) -> &mut [Self::TileType] { fn tiles_mut(&mut self) -> &mut [Tile] {
&mut self.tiles &mut self.tiles
} }
fn tiles_dirty(&mut self) -> &mut bool { fn tiles_dirty(&mut self) -> &mut bool {
@ -320,19 +320,20 @@ impl AffineMap {
tile_id: u8, tile_id: u8,
) { ) {
let pos = self.map_size().gba_offset(pos); let pos = self.map_size().gba_offset(pos);
let colours = self.colours();
let old_tile = self.tiles_mut()[pos]; let old_tile = self.tiles_mut()[pos];
if old_tile != 0 { if old_tile != Tile::default() {
vram.remove_tile(old_tile.into()); vram.remove_tile(old_tile.tile_index(colours));
} }
let tile_index = tile_id as u16; let tile_index = tile_id as u16;
let new_tile = if tile_index != TRANSPARENT_TILE_INDEX { let new_tile = if tile_index != TRANSPARENT_TILE_INDEX {
let new_tile_idx = vram.add_tile(tileset, tile_index); let new_tile_idx = vram.add_tile(tileset, tile_index);
new_tile_idx.raw_index() as u8 Tile::new(new_tile_idx, TileSetting(0))
} else { } else {
0 Tile::default()
}; };
if old_tile == new_tile { if old_tile == new_tile {

View file

@ -156,8 +156,8 @@ impl Tile {
Self(idx.raw_index() | setting.setting()) Self(idx.raw_index() | setting.setting())
} }
fn tile_index(self) -> TileIndex { fn tile_index(self, format: TileFormat) -> TileIndex {
TileIndex::new(self.0 as usize & ((1 << 10) - 1), TileFormat::FourBpp) TileIndex::new(self.0 as usize & ((1 << 10) - 1), format)
} }
} }
@ -165,6 +165,8 @@ impl Tile {
pub struct TileSetting(u16); pub struct TileSetting(u16);
impl TileSetting { impl TileSetting {
pub const BLANK: Self = TileSetting::new(1023, false, false, 0);
#[must_use] #[must_use]
pub const fn new(tile_id: u16, hflip: bool, vflip: bool, palette_id: u8) -> Self { pub const fn new(tile_id: u16, hflip: bool, vflip: bool, palette_id: u8) -> Self {
Self( Self(
@ -180,6 +182,16 @@ impl TileSetting {
Self(raw) Self(raw)
} }
#[must_use]
pub const fn hflip(self, should_flip: bool) -> Self {
Self(self.0 ^ ((should_flip as u16) << 10))
}
#[must_use]
pub const fn vflip(self, should_flip: bool) -> Self {
Self(self.0 ^ ((should_flip as u16) << 11))
}
fn index(self) -> u16 { fn index(self) -> u16 {
self.0 & ((1 << 10) - 1) self.0 & ((1 << 10) - 1)
} }

View file

@ -2,7 +2,6 @@ use core::{alloc::Layout, ptr::NonNull};
use alloc::{slice, vec::Vec}; use alloc::{slice, vec::Vec};
use crate::display::tiled::Tile;
use crate::{ use crate::{
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
display::palette16, display::palette16,
@ -66,21 +65,21 @@ impl<'a> TileSet<'a> {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum TileIndex { pub enum TileIndex {
FourBpp(u16), FourBpp(u16),
EightBpp(u8), EightBpp(u16),
} }
impl TileIndex { impl TileIndex {
pub(crate) const fn new(index: usize, format: TileFormat) -> Self { pub(crate) const fn new(index: usize, format: TileFormat) -> Self {
match format { match format {
TileFormat::FourBpp => Self::FourBpp(index as u16), TileFormat::FourBpp => Self::FourBpp(index as u16),
TileFormat::EightBpp => Self::EightBpp(index as u8), TileFormat::EightBpp => Self::EightBpp(index as u16),
} }
} }
pub(crate) const fn raw_index(self) -> u16 { pub(crate) const fn raw_index(self) -> u16 {
match self { match self {
TileIndex::FourBpp(x) => x, TileIndex::FourBpp(x) => x,
TileIndex::EightBpp(x) => x as u16, TileIndex::EightBpp(x) => x,
} }
} }
@ -99,18 +98,6 @@ impl TileIndex {
} }
} }
impl From<Tile> for TileIndex {
fn from(tile: Tile) -> Self {
tile.tile_index()
}
}
impl From<u8> for TileIndex {
fn from(index: u8) -> TileIndex {
TileIndex::new(usize::from(index), TileFormat::EightBpp)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct TileReference(NonNull<u32>); struct TileReference(NonNull<u32>);
@ -302,7 +289,7 @@ impl VRamManager {
let new_reference: NonNull<u32> = let new_reference: NonNull<u32> =
unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_set.format)) } unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_set.format)) }
.unwrap() .expect("Ran out of video RAM for tiles")
.cast(); .cast();
let tile_reference = TileReference(new_reference); let tile_reference = TileReference(new_reference);
reference.or_insert(tile_reference); reference.or_insert(tile_reference);

View file

@ -33,7 +33,7 @@
//! //!
//! To get started with agb, you should clone the [template repo](https://github.com/agbrs/template) and work from there. //! To get started with agb, you should clone the [template repo](https://github.com/agbrs/template) and work from there.
/// This macro is used to convert a png or bmp into a format usable by the Game Boy Advance. /// This macro is used to convert a png, bmp or aseprite file into a format usable by the Game Boy Advance.
/// ///
/// Suppose you have a file in `examples/water_tiles.png` which contains some tiles you'd like to use. /// Suppose you have a file in `examples/water_tiles.png` which contains some tiles you'd like to use.
/// ///
@ -89,7 +89,7 @@
/// &mut vram, /// &mut vram,
/// (x, y).into(), /// (x, y).into(),
/// &tileset, /// &tileset,
/// TileSetting::new(0, false, false, 0), /// water_tiles::tiles.tile_settings[0],
/// ); /// );
/// } /// }
/// } /// }

View file

@ -52,7 +52,7 @@ dependencies = [
"image", "image",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -61,7 +61,7 @@ version = "0.16.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -71,7 +71,47 @@ dependencies = [
"hound", "hound",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.28",
]
[[package]]
name = "agb_tracker"
version = "0.16.0"
dependencies = [
"agb",
"agb_tracker_interop",
"agb_xm",
]
[[package]]
name = "agb_tracker_interop"
version = "0.16.0"
dependencies = [
"agb_fixnum",
"proc-macro2",
"quote",
]
[[package]]
name = "agb_xm"
version = "0.16.0"
dependencies = [
"agb_xm_core",
"proc-macro-error",
"proc-macro2",
]
[[package]]
name = "agb_xm_core"
version = "0.16.0"
dependencies = [
"agb_fixnum",
"agb_tracker_interop",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.28",
"xmrs",
] ]
[[package]] [[package]]
@ -133,6 +173,12 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "base64"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
[[package]] [[package]]
name = "bilge" name = "bilge"
version = "0.2.0" version = "0.2.0"
@ -153,7 +199,16 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.28",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
] ]
[[package]] [[package]]
@ -199,6 +254,7 @@ dependencies = [
"agb", "agb",
"amplitude", "amplitude",
"hyperspace-roll", "hyperspace-roll",
"the-dungeon-puzzlers-lament",
"the-hat-chooses-the-wizard", "the-hat-chooses-the-wizard",
"the-purple-night", "the-purple-night",
] ]
@ -228,6 +284,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.26" version = "1.0.26"
@ -244,7 +306,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.13.2",
"ttf-parser", "ttf-parser",
] ]
@ -257,6 +319,17 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.13.2" version = "0.13.2"
@ -266,6 +339,12 @@ dependencies = [
"ahash", "ahash",
] ]
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]] [[package]]
name = "hound" name = "hound"
version = "3.5.0" version = "3.5.0"
@ -294,6 +373,16 @@ dependencies = [
"png", "png",
] ]
[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.11.0" version = "0.11.0"
@ -309,6 +398,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]] [[package]]
name = "libflate" name = "libflate"
version = "0.1.27" version = "0.1.27"
@ -321,12 +416,38 @@ dependencies = [
"take_mut", "take_mut",
] ]
[[package]]
name = "libflate"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18"
dependencies = [
"adler32",
"crc32fast",
"libflate_lz77",
]
[[package]]
name = "libflate_lz77"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf"
dependencies = [
"rle-decode-fast",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.20" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.3.7" version = "0.3.7"
@ -392,6 +513,27 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_enum"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.28",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.18.0" version = "1.18.0"
@ -410,6 +552,22 @@ dependencies = [
"miniz_oxide 0.3.7", "miniz_oxide 0.3.7",
] ]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -419,6 +577,7 @@ dependencies = [
"proc-macro-error-attr", "proc-macro-error-attr",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109",
"version_check", "version_check",
] ]
@ -451,6 +610,36 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "rle-decode-fast" name = "rle-decode-fast"
version = "1.0.3" version = "1.0.3"
@ -478,6 +667,15 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-big-array"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.183" version = "1.0.183"
@ -486,7 +684,7 @@ checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -500,6 +698,25 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.28" version = "2.0.28"
@ -517,6 +734,18 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "the-dungeon-puzzlers-lament"
version = "0.1.0"
dependencies = [
"agb",
"agb_tracker",
"proc-macro2",
"quote",
"slotmap",
"tiled 0.11.1",
]
[[package]] [[package]]
name = "the-hat-chooses-the-wizard" name = "the-hat-chooses-the-wizard"
version = "0.1.0" version = "0.1.0"
@ -533,7 +762,7 @@ dependencies = [
"agb", "agb",
"generational-arena", "generational-arena",
"quote", "quote",
"tiled", "tiled 0.9.4",
] ]
[[package]] [[package]]
@ -542,11 +771,39 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2c30aeea9d8159cb461a17dba23ad28980a2a9c217a6784a14e931e4979d6f" checksum = "8d2c30aeea9d8159cb461a17dba23ad28980a2a9c217a6784a14e931e4979d6f"
dependencies = [ dependencies = [
"base64", "base64 0.10.1",
"libflate", "libflate 0.1.27",
"xml-rs", "xml-rs",
] ]
[[package]]
name = "tiled"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "181e2402be287d74e951a8e81c8798d0737cc94b1a089237bb3b838be76c6b5b"
dependencies = [
"base64 0.21.3",
"libflate 1.4.0",
"xml-rs",
]
[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
[[package]]
name = "toml_edit"
version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.15.2" version = "0.15.2"
@ -565,8 +822,37 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winnow"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.16" version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1"
[[package]]
name = "xmrs"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa1ec7c01e6bb4c716f84a418f4ced5f4a735b2ae6364f4bb5850da61321d16"
dependencies = [
"bincode",
"libflate 1.4.0",
"num_enum",
"rand",
"serde",
"serde-big-array",
]

View file

@ -11,6 +11,7 @@ the-purple-night = { path = "../the-purple-night" }
the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" } the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" }
hyperspace-roll = { path = "../hyperspace-roll" } hyperspace-roll = { path = "../hyperspace-roll" }
amplitude = { path = "../amplitude" } amplitude = { path = "../amplitude" }
the-dungeon-puzzlers-lament = { path = "../the-dungeon-puzzlers-lament" }
[profile.dev] [profile.dev]
opt-level = 3 opt-level = 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -8,7 +8,7 @@ use alloc::boxed::Box;
use agb::{ use agb::{
display::{ display::{
tiled::{InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting}, tiled::{InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet},
Priority, Priority,
}, },
fixnum::{Num, Vector2D}, fixnum::{Num, Vector2D},
@ -21,6 +21,7 @@ pub enum Game {
TheHatChoosesTheWizard, TheHatChoosesTheWizard,
ThePurpleNight, ThePurpleNight,
HyperspaceRoll, HyperspaceRoll,
TheDungeonPuzzlersLament,
Amplitude, Amplitude,
} }
@ -30,6 +31,7 @@ impl Game {
Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba), Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba),
Game::ThePurpleNight => the_purple_night::main(gba), Game::ThePurpleNight => the_purple_night::main(gba),
Game::HyperspaceRoll => hyperspace_roll::main(gba), Game::HyperspaceRoll => hyperspace_roll::main(gba),
Game::TheDungeonPuzzlersLament => the_dungeon_puzzlers_lament::entry(gba),
Game::Amplitude => amplitude::main(gba), Game::Amplitude => amplitude::main(gba),
} }
} }
@ -39,7 +41,8 @@ impl Game {
0 => Game::TheHatChoosesTheWizard, 0 => Game::TheHatChoosesTheWizard,
1 => Game::ThePurpleNight, 1 => Game::ThePurpleNight,
2 => Game::HyperspaceRoll, 2 => Game::HyperspaceRoll,
3 => Game::Amplitude, 3 => Game::TheDungeonPuzzlersLament,
4 => Game::Amplitude,
_ => unreachable!("game out of index in an unreachable manner"), _ => unreachable!("game out of index in an unreachable manner"),
} }
} }
@ -47,10 +50,11 @@ impl Game {
include_background_gfx!( include_background_gfx!(
games, "121105", games, "121105",
hat => "gfx/hat.png", hat => 256 deduplicate "gfx/hat.png",
purple => "gfx/purple.png", purple => 256 deduplicate "gfx/purple.png",
hyperspace => "gfx/hyperspace.png", hyperspace => 256 deduplicate "gfx/hyperspace.png",
amplitude => "gfx/amplitude.png" dungeon_puzzler => 256 deduplicate "gfx/dungeon_puzzler.png",
amplitude => 256 deduplicate "gfx/amplitude.png",
); );
fn get_game(gba: &mut agb::Gba) -> Game { fn get_game(gba: &mut agb::Gba) -> Game {
@ -59,18 +63,20 @@ fn get_game(gba: &mut agb::Gba) -> Game {
let (tile, mut vram) = gba.display.video.tiled0(); let (tile, mut vram) = gba.display.video.tiled0();
let hat = TileSet::new(games::hat.tiles, TileFormat::FourBpp); let hat = TileSet::new(games::hat.tiles, TileFormat::EightBpp);
let purple = TileSet::new(games::purple.tiles, TileFormat::FourBpp); let purple = TileSet::new(games::purple.tiles, TileFormat::EightBpp);
let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::FourBpp); let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::EightBpp);
let amplitude = TileSet::new(games::amplitude.tiles, TileFormat::FourBpp); let dungeon_puzzler = TileSet::new(games::dungeon_puzzler.tiles, TileFormat::EightBpp);
let amplitude = TileSet::new(games::amplitude.tiles, TileFormat::EightBpp);
let tiles = [hat, purple, hyperspace, amplitude]; let tiles = [hat, purple, hyperspace, dungeon_puzzler, amplitude];
let palette_assignments = &[ let tile_settings = &[
games::hat.palette_assignments, games::hat.tile_settings,
games::purple.palette_assignments, games::purple.tile_settings,
games::hyperspace.palette_assignments, games::hyperspace.tile_settings,
games::amplitude.palette_assignments, games::dungeon_puzzler.tile_settings,
games::amplitude.tile_settings,
]; ];
vram.set_background_palettes(games::PALETTES); vram.set_background_palettes(games::PALETTES);
@ -79,7 +85,7 @@ fn get_game(gba: &mut agb::Gba) -> Game {
tile.background( tile.background(
Priority::P0, Priority::P0,
RegularBackgroundSize::Background32x32, RegularBackgroundSize::Background32x32,
TileFormat::FourBpp, TileFormat::EightBpp,
), ),
Box::new(|pos| { Box::new(|pos| {
let y = pos.y.rem_euclid(20); let y = pos.y.rem_euclid(20);
@ -87,15 +93,7 @@ fn get_game(gba: &mut agb::Gba) -> Game {
let game = (pos.x).rem_euclid(tiles.len() as i32 * 30) as usize / 30; let game = (pos.x).rem_euclid(tiles.len() as i32 * 30) as usize / 30;
let tile_id = (y * 30 + x) as usize; let tile_id = (y * 30 + x) as usize;
( (&tiles[game], tile_settings[game][tile_id])
&tiles[game],
TileSetting::new(
tile_id as u16,
false,
false,
palette_assignments[game][tile_id],
),
)
}), }),
); );

View file

@ -6,11 +6,11 @@ use agb::{
use crate::sfx::Sfx; use crate::sfx::Sfx;
include_background_gfx!(backgrounds, "121105", include_background_gfx!(backgrounds, "121105",
stars => "gfx/stars.aseprite", stars => deduplicate "gfx/stars.aseprite",
title => "gfx/title-screen.aseprite", title => deduplicate "gfx/title-screen.aseprite",
help => "gfx/help-text.aseprite", help => deduplicate "gfx/help-text.aseprite",
descriptions1 => "gfx/descriptions1.png", descriptions1 => deduplicate "gfx/descriptions1.png",
descriptions2 => "gfx/descriptions2.png", descriptions2 => deduplicate "gfx/descriptions2.png",
); );
pub fn load_palettes(vram: &mut VRamManager) { pub fn load_palettes(vram: &mut VRamManager) {
@ -35,12 +35,7 @@ pub(crate) fn load_help_text(
vram, vram,
(x + at_tile.0, at_tile.1).into(), (x + at_tile.0, at_tile.1).into(),
&help_tileset, &help_tileset,
TileSetting::new( backgrounds::help.tile_settings[tile_id as usize],
tile_id,
false,
false,
backgrounds::help.palette_assignments[tile_id as usize],
),
) )
} }
} }
@ -50,13 +45,13 @@ pub(crate) fn load_description(
descriptions_map: &mut RegularMap, descriptions_map: &mut RegularMap,
vram: &mut VRamManager, vram: &mut VRamManager,
) { ) {
let (tileset, palette_assignments) = if face_id < 10 { let (tileset, tile_settings) = if face_id < 10 {
( (
TileSet::new( TileSet::new(
backgrounds::descriptions1.tiles, backgrounds::descriptions1.tiles,
agb::display::tiled::TileFormat::FourBpp, agb::display::tiled::TileFormat::FourBpp,
), ),
backgrounds::descriptions1.palette_assignments, backgrounds::descriptions1.tile_settings,
) )
} else { } else {
( (
@ -64,7 +59,7 @@ pub(crate) fn load_description(
backgrounds::descriptions2.tiles, backgrounds::descriptions2.tiles,
agb::display::tiled::TileFormat::FourBpp, agb::display::tiled::TileFormat::FourBpp,
), ),
backgrounds::descriptions2.palette_assignments, backgrounds::descriptions2.tile_settings,
) )
}; };
@ -75,7 +70,7 @@ pub(crate) fn load_description(
vram, vram,
(x, y).into(), (x, y).into(),
&tileset, &tileset,
TileSetting::new(tile_id, false, false, palette_assignments[tile_id as usize]), tile_settings[tile_id as usize],
) )
} }
} }
@ -87,16 +82,12 @@ fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_til
for y in 0..32u16 { for y in 0..32u16 {
let blank = rng::gen().rem_euclid(32) < 30; let blank = rng::gen().rem_euclid(32) < 30;
let (tile_id, palette_id) = if blank { let tile_setting = if blank {
((1 << 10) - 1, 0) TileSetting::new((1 << 10) - 1, false, false, 0)
} else { } else {
let tile_id = rng::gen().rem_euclid(64) as u16; let tile_id = rng::gen().rem_euclid(64) as u16;
( backgrounds::stars.tile_settings[tile_id as usize]
tile_id,
backgrounds::stars.palette_assignments[tile_id as usize],
)
}; };
let tile_setting = TileSetting::new(tile_id, false, false, palette_id);
map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting); map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting);
} }
@ -121,12 +112,7 @@ pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sf
vram, vram,
(x, y).into(), (x, y).into(),
&tile_set, &tile_set,
TileSetting::new( backgrounds::title.tile_settings[tile_id as usize],
tile_id,
false,
false,
backgrounds::title.palette_assignments[tile_id as usize],
),
); );
} }

View file

@ -331,12 +331,10 @@ fn export_tiles(map: &tiled::Map, background: TokenStream) -> TokenStream {
tile_tileset_x * 2 + x_offset + tile_tileset_y * 9 * 4 + y_offset * 9 * 2; tile_tileset_x * 2 + x_offset + tile_tileset_y * 9 * 4 + y_offset * 9 * 2;
let gba_tile_id = gba_tile_id as u16; let gba_tile_id = gba_tile_id as u16;
let palette_id = quote! { backgrounds::#background.tile_settings[#gba_tile_id as usize].hflip(#hflip).vflip(#vflip) }
quote! { backgrounds::#background.palette_assignments[#gba_tile_id as usize] };
quote! { TileSetting::new(#gba_tile_id, #hflip, #vflip, #palette_id) }
} }
None => { None => {
quote! { TileSetting::new(1023, false, false, 0) } quote! { TileSetting::BLANK }
} }
} }
}); });
@ -361,12 +359,11 @@ fn export_ui_tiles(map: &tiled::Map, background: TokenStream) -> TokenStream {
let tile_id = tile.id() as u16; let tile_id = tile.id() as u16;
let vflip = tile.flip_v; let vflip = tile.flip_v;
let hflip = tile.flip_h; let hflip = tile.flip_h;
let palette_id =
quote! { backgrounds::#background.palette_assignments[#tile_id as usize] }; quote! { backgrounds::#background.tile_settings[#tile_id as usize].hflip(#hflip).vflip(#vflip) }
quote! { TileSetting::new(#tile_id, #hflip, #vflip, #palette_id) }
} }
None => { None => {
quote! { TileSetting::new(1023, false, false, 0) } quote! { TileSetting::BLANK }
} }
} }
}); });

View file

@ -1,12 +1,12 @@
use agb::{ use agb::{
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, display::tiled::{RegularMap, TileFormat, TileSet, VRamManager},
include_background_gfx, include_background_gfx,
}; };
include_background_gfx!(backgrounds, "1e151b", include_background_gfx!(backgrounds, "1e151b",
ui => "maps/ui_tiles.png", ui => deduplicate "maps/ui_tiles.png",
level => "maps/level.png", level => deduplicate "maps/level.png",
ending => "gfx/ending_page.aseprite", ending => deduplicate "gfx/ending_page.aseprite",
); );
mod tilemaps { mod tilemaps {
@ -56,13 +56,13 @@ pub fn load_ending_page(map: &mut RegularMap, vram_manager: &mut VRamManager) {
for y in 0..20u16 { for y in 0..20u16 {
for x in 0..30u16 { for x in 0..30u16 {
let tile_pos = y * 30 + x; let tile_pos = y * 30 + x;
let tile_setting = TileSetting::new(
tile_pos, map.set_tile(
false, vram_manager,
false, (x, y).into(),
backgrounds::ending.palette_assignments[tile_pos as usize], &ending_tileset,
backgrounds::ending.tile_settings[tile_pos as usize],
); );
map.set_tile(vram_manager, (x, y).into(), &ending_tileset, tile_setting);
} }
} }
} }

View file

@ -3,10 +3,10 @@ use agb::display::{
HEIGHT, WIDTH, HEIGHT, WIDTH,
}; };
const LEVEL_START: u16 = 12 * 28; const LEVEL_START: usize = 12 * 28;
const NUMBERS_START: u16 = 12 * 28 + 3; const NUMBERS_START: usize = 12 * 28 + 3;
const HYPHEN: u16 = 12 * 28 + 11; const HYPHEN: usize = 12 * 28 + 11;
pub const BLANK: u16 = 11 * 28; pub const BLANK: usize = 11 * 28;
pub fn write_level( pub fn write_level(
map: &mut RegularMap, map: &mut RegularMap,
@ -14,25 +14,21 @@ pub fn write_level(
level: u32, level: u32,
tileset: &'_ TileSet<'_>, tileset: &'_ TileSet<'_>,
vram: &mut VRamManager, vram: &mut VRamManager,
tile_settings: &[TileSetting],
) { ) {
for (i, &tile) in [ for (i, &tile) in [
LEVEL_START, LEVEL_START,
LEVEL_START + 1, LEVEL_START + 1,
LEVEL_START + 2, LEVEL_START + 2,
BLANK, BLANK,
world as u16 + NUMBERS_START - 1, world as usize + NUMBERS_START - 1,
HYPHEN, HYPHEN,
level as u16 + NUMBERS_START - 1, level as usize + NUMBERS_START - 1,
] ]
.iter() .iter()
.enumerate() .enumerate()
{ {
map.set_tile( map.set_tile(vram, (i as u16, 0).into(), tileset, tile_settings[tile]);
vram,
(i as u16, 0).into(),
tileset,
TileSetting::from_raw(tile),
);
} }
map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16).into()); map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16).into());

View file

@ -11,7 +11,7 @@ use agb::{
object::{Graphics, OamManaged, Object, Tag, TagMap}, object::{Graphics, OamManaged, Object, Tag, TagMap},
tiled::{ tiled::{
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
TileSetting, TiledMap, VRamManager, TiledMap, VRamManager,
}, },
Priority, HEIGHT, WIDTH, Priority, HEIGHT, WIDTH,
}, },
@ -101,7 +101,7 @@ mod map_tiles {
} }
} }
agb::include_background_gfx!(tile_sheet, "2ce8f4", background => "gfx/tile_sheet.png"); agb::include_background_gfx!(tile_sheet, "2ce8f4", background => deduplicate "gfx/tile_sheet.png");
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();
@ -801,7 +801,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
&mut vram, &mut vram,
(x, y).into(), (x, y).into(),
&tileset, &tileset,
TileSetting::from_raw(level_display::BLANK), tile_sheet::background.tile_settings[level_display::BLANK],
); );
} }
} }
@ -846,6 +846,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
current_level % 8 + 1, current_level % 8 + 1,
&tileset, &tileset,
&mut vram, &mut vram,
tile_sheet::background.tile_settings,
); );
world_display.commit(&mut vram); world_display.commit(&mut vram);
@ -865,12 +866,11 @@ pub fn main(mut agb: agb::Gba) -> ! {
let level = &map_tiles::LEVELS[map_current_level as usize]; let level = &map_tiles::LEVELS[map_current_level as usize];
( (
&tileset, &tileset,
TileSetting::from_raw( tile_sheet::background.tile_settings[*level
*level .background
.background .get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize) .unwrap_or(&0)
.unwrap_or(&0), as usize],
),
) )
}), }),
); );
@ -884,12 +884,11 @@ pub fn main(mut agb: agb::Gba) -> ! {
let level = &map_tiles::LEVELS[map_current_level as usize]; let level = &map_tiles::LEVELS[map_current_level as usize];
( (
&tileset, &tileset,
TileSetting::from_raw( tile_sheet::background.tile_settings[*level
*level .foreground
.foreground .get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize) .unwrap_or(&0)
.unwrap_or(&0), as usize],
),
) )
}), }),
); );

View file

@ -1,9 +1,9 @@
use super::sfx::SfxPlayer; use super::sfx::SfxPlayer;
use agb::display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}; use agb::display::tiled::{RegularMap, TileFormat, TileSet, TiledMap, VRamManager};
agb::include_background_gfx!(splash_screens, agb::include_background_gfx!(splash_screens,
splash => "gfx/splash.png", splash => deduplicate "gfx/splash.png",
thanks_for_playing => "gfx/thanks_for_playing.png", thanks_for_playing => deduplicate "gfx/thanks_for_playing.png",
); );
pub enum SplashScreen { pub enum SplashScreen {
@ -18,12 +18,18 @@ pub fn show_splash_screen(
vram: &mut VRamManager, vram: &mut VRamManager,
) { ) {
map.set_scroll_pos((0i16, 0i16).into()); map.set_scroll_pos((0i16, 0i16).into());
let tileset = match which { let (tileset, settings) = match which {
SplashScreen::Start => TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp), SplashScreen::Start => (
TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp),
splash_screens::splash.tile_settings,
),
SplashScreen::End => TileSet::new( SplashScreen::End => (
splash_screens::thanks_for_playing.tiles, TileSet::new(
TileFormat::FourBpp, splash_screens::thanks_for_playing.tiles,
TileFormat::FourBpp,
),
splash_screens::thanks_for_playing.tile_settings,
), ),
}; };
@ -40,7 +46,7 @@ pub fn show_splash_screen(
vram, vram,
(x, y).into(), (x, y).into(),
&tileset, &tileset,
TileSetting::from_raw(y * 30 + x), settings[(y * 30 + x) as usize],
); );
} }

View file

@ -15,10 +15,7 @@ use alloc::{boxed::Box, vec::Vec};
use agb::{ use agb::{
display::{ display::{
object::{Graphics, OamManaged, Object, Sprite, Tag, TagMap}, object::{Graphics, OamManaged, Object, Sprite, Tag, TagMap},
tiled::{ tiled::{InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, VRamManager},
InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting,
VRamManager,
},
Priority, HEIGHT, WIDTH, Priority, HEIGHT, WIDTH,
}, },
fixnum::{num, FixedNum, Rect, Vector2D}, fixnum::{num, FixedNum, Rect, Vector2D},
@ -57,7 +54,7 @@ const SWORDLESS_JUMP: &Tag = TAG_MAP.get("jump swordless");
const SWORDLESS_ATTACK: &Tag = KNIFE_ATTACK; const SWORDLESS_ATTACK: &Tag = KNIFE_ATTACK;
const SWORDLESS_JUMP_ATTACK: &Tag = KNIFE_JUMP_ATTACK; const SWORDLESS_JUMP_ATTACK: &Tag = KNIFE_JUMP_ATTACK;
agb::include_background_gfx!(background, "53269a", background => "gfx/background.aseprite"); agb::include_background_gfx!(background, "53269a", background => deduplicate "gfx/background.aseprite");
type Number = FixedNum<8>; type Number = FixedNum<8>;
@ -2207,11 +2204,10 @@ fn game_with_level(gba: &mut agb::Gba) {
Box::new(|pos| { Box::new(|pos| {
( (
&tileset, &tileset,
TileSetting::from_raw( background::background.tile_settings[*tilemap::BACKGROUND_MAP
*tilemap::BACKGROUND_MAP .get((pos.x + tilemap::WIDTH * pos.y) as usize)
.get((pos.x + tilemap::WIDTH * pos.y) as usize) .unwrap_or(&0)
.unwrap_or(&0), as usize],
),
) )
}), }),
); );
@ -2225,11 +2221,10 @@ fn game_with_level(gba: &mut agb::Gba) {
Box::new(|pos| { Box::new(|pos| {
( (
&tileset, &tileset,
TileSetting::from_raw( background::background.tile_settings[*tilemap::FOREGROUND_MAP
*tilemap::FOREGROUND_MAP .get((pos.x + tilemap::WIDTH * pos.y) as usize)
.get((pos.x + tilemap::WIDTH * pos.y) as usize) .unwrap_or(&0)
.unwrap_or(&0), as usize],
),
) )
}), }),
); );
@ -2243,11 +2238,10 @@ fn game_with_level(gba: &mut agb::Gba) {
Box::new(|pos| { Box::new(|pos| {
( (
&tileset, &tileset,
TileSetting::from_raw( background::background.tile_settings[*tilemap::CLOUD_MAP
*tilemap::CLOUD_MAP .get((pos.x + tilemap::WIDTH * pos.y) as usize)
.get((pos.x + tilemap::WIDTH * pos.y) as usize) .unwrap_or(&0)
.unwrap_or(&0), as usize],
),
) )
}), }),
); );