diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5ef0378e..3b9016e2 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -33,5 +33,9 @@ jobs: - name: Install gbafix run: cargo install gbafix - uses: extractions/setup-just@v1 + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: '0.4.13' - name: Build and test all crates run: just ci \ No newline at end of file diff --git a/.github/workflows/build-book.yml b/.github/workflows/build-book.yml deleted file mode 100644 index e533deab..00000000 --- a/.github/workflows/build-book.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build book - -on: - push: - branches: [ master ] - paths: - 'book/**' - pull_request: - branches: [ master ] - paths: - 'book/**' - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-20.04 - steps: - - name: Set CARGO_TARGET_DIR - run: echo "CARGO_TARGET_DIR=$HOME/target" >> $GITHUB_ENV - - name: Install build tools - run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi libelf-dev zip -y - - uses: actions/checkout@v3 - - name: Cache - uses: actions/cache@v2.1.7 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - ~/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install mdbook - run: cargo install mdbook - - name: Build the book - run: mdbook build - working-directory: book \ No newline at end of file diff --git a/.github/workflows/publish-agb.yml b/.github/workflows/publish-agb.yml index 2d7a6d29..681564e4 100644 --- a/.github/workflows/publish-agb.yml +++ b/.github/workflows/publish-agb.yml @@ -38,8 +38,10 @@ jobs: tag: ${{ github.ref }} overwrite: true - - name: Install mdbook - run: cargo install mdbook + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: '0.4.13' - name: Build the book run: just build-book - name: Deploy the book diff --git a/agb-fixnum/Cargo.lock b/agb-fixnum/Cargo.lock index e6e394cd..e8c0c4ce 100644 --- a/agb-fixnum/Cargo.lock +++ b/agb-fixnum/Cargo.lock @@ -15,39 +15,9 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "getrandom" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "libc" -version = "0.2.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -66,36 +36,6 @@ dependencies = [ "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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - [[package]] name = "syn" version = "1.0.86" @@ -112,9 +52,3 @@ name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/agb-image-converter/Cargo.lock b/agb-image-converter/Cargo.lock index cfdd885c..aac7a0e5 100644 --- a/agb-image-converter/Cargo.lock +++ b/agb-image-converter/Cargo.lock @@ -18,6 +18,7 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" name = "agb_image_converter" version = "0.6.0" dependencies = [ + "asefile", "image", "proc-macro2", "quote", @@ -26,6 +27,20 @@ dependencies = [ "toml", ] +[[package]] +name = "asefile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5f7de918fd4cb18249819fc4bd27f6a5dbfbc9dcb271727f27dacf17ce880" +dependencies = [ + "bitflags", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -40,9 +55,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -73,18 +88,31 @@ dependencies = [ [[package]] name = "deflate" -version = "1.0.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", ] [[package]] name = "image" -version = "0.24.1" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", @@ -96,14 +124,45 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "libc" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + [[package]] name = "num-integer" version = "0.1.44" @@ -127,9 +186,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -147,14 +206,14 @@ dependencies = [ [[package]] name = "png" -version = "0.17.4" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] [[package]] diff --git a/agb-image-converter/Cargo.toml b/agb-image-converter/Cargo.toml index f26d4630..517b174c 100644 --- a/agb-image-converter/Cargo.toml +++ b/agb-image-converter/Cargo.toml @@ -10,9 +10,10 @@ description = "Library for converting graphics for use on the Game Boy Advance" proc-macro = true [dependencies] -image = { version = "0.24.1", default-features = false, features = [ "png", "bmp" ] } +image = { version = "0.23", default-features = false, features = [ "png", "bmp" ] } toml = "0.5.8" serde = { version = "1.0", features = ["derive"] } syn = "1.0.86" proc-macro2 = "1.0.36" -quote = "1.0.15" \ No newline at end of file +quote = "1.0.15" +asefile = "0.3.2" diff --git a/agb-image-converter/src/aseprite.rs b/agb-image-converter/src/aseprite.rs new file mode 100644 index 00000000..694c0cfd --- /dev/null +++ b/agb-image-converter/src/aseprite.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use asefile::{AsepriteFile, Tag}; +use image::DynamicImage; + +pub fn generate_from_file(filename: &Path) -> (Vec, Vec) { + let ase = AsepriteFile::read_file(filename).expect("Aseprite file should exist"); + + let mut images = Vec::new(); + let mut tags = Vec::new(); + + for frame in 0..ase.num_frames() { + let image = ase.frame(frame).image(); + + images.push(DynamicImage::ImageRgba8(image)) + } + + for tag in 0..ase.num_tags() { + tags.push(ase.tag(tag).clone()) + } + + (images, tags) +} diff --git a/agb-image-converter/src/image_loader.rs b/agb-image-converter/src/image_loader.rs index aa3f5921..52a3b04f 100644 --- a/agb-image-converter/src/image_loader.rs +++ b/agb-image-converter/src/image_loader.rs @@ -13,6 +13,10 @@ pub(crate) struct Image { impl Image { pub fn load_from_file(image_path: &path::Path) -> Self { let img = image::open(image_path).expect("Expected image to exist"); + Self::load_from_dyn_image(img) + } + + pub fn load_from_dyn_image(img: image::DynamicImage) -> Self { let (width, height) = img.dimensions(); let width = width as usize; diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index 7d6df7e8..e4d8e954 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -1,16 +1,22 @@ +use palette16::Palette16OptimisationResults; use proc_macro::TokenStream; -use syn::parse_macro_input; +use proc_macro2::Literal; +use syn::parse::Parser; +use syn::{parse_macro_input, punctuated::Punctuated, LitStr}; -use std::path::Path; +use std::path::PathBuf; +use std::{iter, path::Path, str}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; +mod aseprite; mod colour; mod config; mod image_loader; mod palette16; mod rust_generator; +use image::GenericImageView; use image_loader::Image; use colour::Colour; @@ -56,7 +62,7 @@ pub fn include_gfx(input: TokenStream) -> TokenStream { let images = config.images(); let image_code = images.iter().map(|(image_name, &image)| { - convert_image(image, parent, &image_name, &config.crate_prefix()) + convert_image(image, parent, image_name, &config.crate_prefix()) }); let module = quote! { @@ -70,6 +76,126 @@ pub fn include_gfx(input: TokenStream) -> TokenStream { TokenStream::from(module) } +use quote::TokenStreamExt; +struct ByteString<'a>(&'a [u8]); +impl ToTokens for ByteString<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.append(Literal::byte_string(self.0)); + } +} + +#[proc_macro] +pub fn include_aseprite_inner(input: TokenStream) -> TokenStream { + let parser = Punctuated::::parse_separated_nonempty; + let parsed = match parser.parse(input) { + Ok(e) => e, + Err(e) => return e.to_compile_error().into(), + }; + + let mut optimiser = palette16::Palette16Optimiser::new(); + let mut images = Vec::new(); + let mut tags = Vec::new(); + + let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir"); + + let filenames: Vec = parsed + .iter() + .map(|s| s.value()) + .map(|s| Path::new(&root).join(&*s)) + .collect(); + + for filename in filenames.iter() { + let (frames, tag) = aseprite::generate_from_file(filename); + + tags.push((tag, images.len())); + + for frame in frames { + let width = frame.width(); + assert!(width == frame.height() && width.is_power_of_two() && width <= 32); + + let image = Image::load_from_dyn_image(frame); + add_to_optimiser(&mut optimiser, &image, width as usize); + images.push(image); + } + } + + let optimised_results = optimiser.optimise_palettes(None); + + let (palette_data, tile_data, assignments) = palete_tile_data(&optimised_results, &images); + + let palette_data = palette_data.iter().map(|colours| { + quote! { + Palette16::new([ + #(#colours),* + ]) + } + }); + + let mut pre = 0; + let sprites = images + .iter() + .zip(assignments.iter()) + .map(|(f, assignment)| { + let start: usize = pre; + let end: usize = pre + (f.width / 8) * (f.height / 8) * 32; + let data = ByteString(&tile_data[start..end]); + pre = end; + let width = f.width; + let height = f.height; + quote! { + Sprite::new( + &PALETTES[#assignment], + #data, + Size::from_width_height(#width, #height) + ) + } + }); + + let tags = tags.iter().flat_map(|(tag, num_images)| { + tag.iter().map(move |tag| { + let start = tag.from_frame() as usize + num_images; + let end = tag.to_frame() as usize + num_images; + let direction = tag.animation_direction() as usize; + + let name = tag.name(); + assert!(start <= end, "Tag {} has start > end", name); + + quote! { + (#name, Tag::new(SPRITES, #start, #end, #direction)) + } + }) + }); + + let include_paths = filenames.iter().map(|s| { + let s = s.as_os_str().to_string_lossy(); + quote! { + const _: &[u8] = include_bytes!(#s); + } + }); + + let module = quote! { + #(#include_paths)* + + + const PALETTES: &[Palette16] = &[ + #(#palette_data),* + ]; + + pub const SPRITES: &[Sprite] = &[ + #(#sprites),* + ]; + + const TAGS: &TagMap = &TagMap::new( + &[ + #(#tags),* + ] + ); + + }; + + TokenStream::from(module) +} + fn convert_image( settings: &dyn config::Image, parent: &Path, @@ -98,11 +224,19 @@ fn convert_image( } fn optimiser_for_image(image: &Image, tile_size: usize) -> palette16::Palette16Optimiser { + let mut palette_optimiser = palette16::Palette16Optimiser::new(); + add_to_optimiser(&mut palette_optimiser, image, tile_size); + palette_optimiser +} + +fn add_to_optimiser( + palette_optimiser: &mut palette16::Palette16Optimiser, + image: &Image, + tile_size: usize, +) { let tiles_x = image.width / tile_size; let tiles_y = image.height / tile_size; - let mut palette_optimiser = palette16::Palette16Optimiser::new(); - for y in 0..tiles_y { for x in 0..tiles_x { let mut palette = palette16::Palette16::new(); @@ -118,6 +252,74 @@ fn optimiser_for_image(image: &Image, tile_size: usize) -> palette16::Palette16O palette_optimiser.add_palette(palette); } } - - palette_optimiser +} + +fn palete_tile_data( + optimiser: &Palette16OptimisationResults, + images: &[Image], +) -> (Vec>, Vec, Vec) { + let palette_data: Vec> = optimiser + .optimised_palettes + .iter() + .map(|palette| { + palette + .clone() + .into_iter() + .map(|colour| colour.to_rgb15()) + .chain(iter::repeat(0)) + .take(16) + .map(|colour| colour as u16) + .collect() + }) + .collect(); + + let mut tile_data = Vec::new(); + + for image in images { + let tile_size = image.height; + let tiles_x = image.width / tile_size; + let tiles_y = image.height / tile_size; + + for y in 0..tiles_y { + for x in 0..tiles_x { + let palette_index = optimiser.assignments[y * tiles_x + x]; + let palette = &optimiser.optimised_palettes[palette_index]; + + for inner_y in 0..tile_size / 8 { + for inner_x in 0..tile_size / 8 { + for j in inner_y * 8..inner_y * 8 + 8 { + for i in inner_x * 8..inner_x * 8 + 8 { + let colour = image.colour(x * tile_size + i, y * tile_size + j); + tile_data.push(palette.colour_index(colour)); + } + } + } + } + } + } + } + + let tile_data = tile_data + .chunks(2) + .map(|chunk| chunk[0] | (chunk[1] << 4)) + .collect(); + + let assignments = optimiser.assignments.clone(); + + (palette_data, tile_data, assignments) +} + +#[cfg(test)] +mod tests { + use asefile::AnimationDirection; + + #[test] + // These directions defined in agb and have these values. This is important + // when outputting code for agb. If more animation directions are added then + // we will have to support them there. + fn directions_to_agb() { + assert_eq!(AnimationDirection::Forward as usize, 0); + assert_eq!(AnimationDirection::Reverse as usize, 1); + assert_eq!(AnimationDirection::PingPong as usize, 2); + } } diff --git a/agb-image-converter/src/palette16.rs b/agb-image-converter/src/palette16.rs index 0c7e3451..18adc83c 100644 --- a/agb-image-converter/src/palette16.rs +++ b/agb-image-converter/src/palette16.rs @@ -151,12 +151,12 @@ impl Palette16Optimiser { let mut a_colour_is_used = false; for current_palette in unsatisfied_palettes { - if palette.union_length(¤t_palette) > MAX_COLOURS_PER_PALETTE { + if palette.union_length(current_palette) > MAX_COLOURS_PER_PALETTE { continue; } for colour in ¤t_palette.colours { - if palette.colours.contains(&colour) { + if palette.colours.contains(colour) { continue; } diff --git a/agb-macros/Cargo.lock b/agb-macros/Cargo.lock index 6bfc3ccb..866dacdc 100644 --- a/agb-macros/Cargo.lock +++ b/agb-macros/Cargo.lock @@ -8,39 +8,9 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "getrandom" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "libc" -version = "0.2.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -59,36 +29,6 @@ dependencies = [ "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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - [[package]] name = "syn" version = "1.0.86" @@ -105,9 +45,3 @@ name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/agb-macros/Cargo.toml b/agb-macros/Cargo.toml index 4c8c218a..0e52d5d4 100644 --- a/agb-macros/Cargo.toml +++ b/agb-macros/Cargo.toml @@ -13,4 +13,3 @@ proc-macro = true syn = { version = "1.0.86", features = ["full", "extra-traits"] } proc-macro2 = "1.0.36" quote = "1.0.15" -rand = "0.8.5" \ No newline at end of file diff --git a/agb-macros/src/lib.rs b/agb-macros/src/lib.rs index 486e8e9a..46e491ff 100644 --- a/agb-macros/src/lib.rs +++ b/agb-macros/src/lib.rs @@ -3,9 +3,11 @@ use proc_macro::TokenStream; use proc_macro2::Span; use quote::{quote, ToTokens}; -use rand::Rng; use syn::{FnArg, Ident, ItemFn, Pat, ReturnType, Token, Type, Visibility}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + #[proc_macro_attribute] pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { let f: ItemFn = syn::parse(input).expect("#[agb::entry] must be applied to a function"); @@ -57,7 +59,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { "Must pass no args to #[agb::entry] macro" ); - let fn_name = random_ident(); + let fn_name = hashed_ident(&f); let attrs = f.attrs; let stmts = f.block.stmts; @@ -98,18 +100,13 @@ pub fn num(input: TokenStream) -> TokenStream { quote!((#integer, #fractional)).into() } -fn random_ident() -> Ident { - let mut rng = rand::thread_rng(); - Ident::new( - &(0..16) - .map(|i| { - if i == 0 || rng.gen() { - (b'a' + rng.gen::() % 25) as char - } else { - (b'0' + rng.gen::() % 10) as char - } - }) - .collect::(), - Span::call_site(), - ) +fn hashed_ident(f: &T) -> Ident { + let hash = calculate_hash(f); + Ident::new(&format!("_agb_main_func_{}", hash), Span::call_site()) +} + +fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() } diff --git a/agb/Cargo.lock b/agb/Cargo.lock index 472cef4a..5e7fcd2c 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -24,6 +24,9 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", + "modular-bitfield", + "rustc-hash", ] [[package]] @@ -37,6 +40,7 @@ dependencies = [ name = "agb_image_converter" version = "0.6.0" dependencies = [ + "asefile", "image", "proc-macro2", "quote", @@ -51,7 +55,6 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] @@ -65,6 +68,31 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "asefile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5f7de918fd4cb18249819fc4bd27f6a5dbfbc9dcb271727f27dacf17ce880" +dependencies = [ + "bitflags", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -118,11 +146,24 @@ dependencies = [ [[package]] name = "deflate" -version = "1.0.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", ] [[package]] @@ -136,6 +177,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -144,9 +194,9 @@ checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" [[package]] name = "image" -version = "0.24.1" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", @@ -164,14 +214,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "log" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + [[package]] name = "num-integer" version = "0.1.44" @@ -195,9 +291,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -214,23 +310,23 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.5" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -250,34 +346,10 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.8.5" +name = "rustc-hash" +version = "1.1.0" 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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "serde" @@ -299,6 +371,12 @@ dependencies = [ "syn", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.86" @@ -325,6 +403,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index dbd7d298..b46ba012 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -15,8 +15,7 @@ lto = true debug = true [features] -default = ["alloc"] -alloc = [] +default = [] freq18157 = ["agb_sound_converter/freq18157"] [dependencies] @@ -26,6 +25,9 @@ agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } agb_fixnum = { version = "0.1.0", path = "../agb-fixnum" } bare-metal = "1.0" +hashbrown = "0.12.0" +modular-bitfield = "0.11.2" +rustc-hash = { version = "1.0", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 4d5d2e14..e2bc4794 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -3,7 +3,11 @@ use agb::{ display::tiled::{TileFormat, TileSet, TileSetting}, - display::{object::ObjectStandard, HEIGHT, WIDTH}, + display::{ + object::{Object, ObjectController, Size, Sprite}, + palette16::Palette16, + HEIGHT, WIDTH, + }, input::Button, }; use core::convert::TryInto; @@ -16,7 +20,7 @@ enum State { } struct Character<'a> { - object: ObjectStandard<'a>, + object: Object<'a, 'a>, position: Vector2D, velocity: Vector2D, } @@ -31,8 +35,8 @@ fn tile_is_collidable(tile: u16) -> bool { masked == 0 || masked == 4 } -fn frame_ranger(count: u32, start: u32, end: u32, delay: u32) -> u16 { - (((count / delay) % (end + 1 - start)) + start) as u16 +fn frame_ranger(count: u32, start: u32, end: u32, delay: u32) -> usize { + (((count / delay) % (end + 1 - start)) + start) as usize } #[agb::entry] @@ -66,14 +70,11 @@ fn main(mut gba: agb::Gba) -> ! { background.show(); background.commit(); - let mut object = gba.display.object.get(); + let object = gba.display.object.get(); - object.set_sprite_palette_raw(&CHICKEN_PALETTE); - object.set_sprite_tilemap(&CHICKEN_TILES); - - object.enable(); + let sprite = object.get_sprite(&ChickenSprites[0]).unwrap(); let mut chicken = Character { - object: object.get_object_standard(), + object: object.get_object(sprite).unwrap(), position: Vector2D { x: (6 * 8) << 8, y: ((7 * 8) - 4) << 8, @@ -81,7 +82,6 @@ fn main(mut gba: agb::Gba) -> ! { velocity: Vector2D { x: 0, y: 0 }, }; - chicken.object.set_tile_id(0); chicken .object .set_x((chicken.position.x >> 8).try_into().unwrap()); @@ -132,14 +132,19 @@ fn main(mut gba: agb::Gba) -> ! { } restrict_to_screen(&mut chicken); - update_chicken_object(&mut chicken, state, frame_count); + update_chicken_object(&mut chicken, &object, state, frame_count); // Commit the chicken to vram chicken.object.commit(); } } -fn update_chicken_object(chicken: &mut Character, state: State, frame_count: u32) { +fn update_chicken_object<'a>( + chicken: &'_ mut Character<'a>, + object: &'a ObjectController, + state: State, + frame_count: u32, +) { if chicken.velocity.x > 1 { chicken.object.set_hflip(false); } else if chicken.velocity.x < -1 { @@ -148,18 +153,24 @@ fn update_chicken_object(chicken: &mut Character, state: State, frame_count: u32 match state { State::Ground => { if chicken.velocity.x.abs() > 1 << 4 { + chicken.object.set_sprite( + object + .get_sprite(&ChickenSprites[frame_ranger(frame_count, 1, 3, 10)]) + .unwrap(), + ); + } else { chicken .object - .set_tile_id(frame_ranger(frame_count, 1, 3, 10)); - } else { - chicken.object.set_tile_id(0); + .set_sprite(object.get_sprite(&ChickenSprites[0]).unwrap()); } } State::Upwards => {} State::Flapping => { - chicken - .object - .set_tile_id(frame_ranger(frame_count, 4, 5, 5)); + chicken.object.set_sprite( + object + .get_sprite(&ChickenSprites[frame_ranger(frame_count, 4, 5, 5)]) + .unwrap(), + ); } } @@ -244,16 +255,65 @@ fn handle_collision( // Below is the data for the sprites -static CHICKEN_TILES: [u32; 8 * 6] = [ - 0x01100000, 0x11100000, 0x01100010, 0x01111110, 0x01111110, 0x00001000, 0x00001000, 0x00011000, - 0x01100000, 0x11100000, 0x01100010, 0x01111110, 0x01111110, 0x00010100, 0x00100100, 0x00000010, - 0x01100000, 0x11100000, 0x01100010, 0x01111110, 0x01111110, 0x00011000, 0x00100110, 0x00100000, - 0x01100000, 0x11100000, 0x01100010, 0x01111110, 0x01111110, 0x00011000, 0x00011100, 0x00001000, - 0x01100000, 0x11111100, 0x01111010, 0x01111110, 0x01111110, 0x00011000, 0x00010000, 0x00000000, - 0x01100000, 0x11100000, 0x01111110, 0x01111110, 0x01111110, 0x00011000, 0x00010000, 0x00000000, -]; +static ChickenPalette: Palette16 = + Palette16::new([0x7C1E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); -static CHICKEN_PALETTE: [u16; 1] = [0x7C1E]; +static ChickenSprites: &[Sprite] = &[ + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, 0x11, 0x10, 0x00, 0x10, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x10, 0x01, 0x00, + ], + Size::S8x8, + ), + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, 0x11, 0x10, 0x00, 0x10, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x10, 0x00, + 0x10, 0x00, 0x00, 0x00, + ], + Size::S8x8, + ), + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, 0x11, 0x10, 0x00, 0x10, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x10, 0x01, 0x00, 0x10, 0x01, 0x10, 0x00, + 0x00, 0x00, 0x10, 0x00, + ], + Size::S8x8, + ), + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, 0x11, 0x10, 0x00, 0x10, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x10, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, + 0x00, 0x10, 0x00, 0x00, + ], + Size::S8x8, + ), + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x11, 0x11, 0x11, 0x10, 0x10, 0x11, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + Size::S8x8, + ), + Sprite::new( + &ChickenPalette, + &[ + 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x10, 0x11, 0x10, 0x11, 0x11, 0x01, 0x10, 0x11, + 0x11, 0x01, 0x10, 0x11, 0x11, 0x01, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + Size::S8x8, + ), +]; static MAP_TILES: [u32; 8 * 17] = [ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs new file mode 100644 index 00000000..7342032b --- /dev/null +++ b/agb/examples/sprites.rs @@ -0,0 +1,109 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use agb::display::object::{Graphics, ObjectController, Sprite, TagMap}; +use alloc::vec::Vec; + +const GRAPHICS: &Graphics = agb::include_aseprite!( + "../examples/the-purple-night/gfx/objects.aseprite", + "../examples/the-purple-night/gfx/boss.aseprite" +); +const SPRITES: &[Sprite] = GRAPHICS.sprites(); +const TAG_MAP: &TagMap = GRAPHICS.tags(); + +fn all_sprites(gfx: &ObjectController) { + let mut input = agb::input::ButtonController::new(); + let mut objs = Vec::new(); + + for y in 0..9 { + for x in 0..14 { + let mut obj = gfx + .get_object(gfx.get_sprite(&SPRITES[0]).unwrap()) + .unwrap(); + obj.show(); + obj.set_position((x * 16 + 8, y * 16 + 8).into()); + objs.push(obj); + } + } + + let mut count = 0; + let mut image = 0; + + let vblank = agb::interrupt::VBlank::get(); + + loop { + vblank.wait_for_vblank(); + input.update(); + + if input.is_just_pressed(agb::input::Button::A) { + break; + } + + count += 1; + + if count % 5 == 0 { + image += 1; + image %= SPRITES.len(); + let objs_len = objs.len(); + for (i, obj) in objs.iter_mut().enumerate() { + let this_image = (image + i * SPRITES.len() / objs_len) % SPRITES.len(); + obj.set_sprite(gfx.get_sprite(&SPRITES[this_image]).unwrap()); + obj.commit(); + } + } + } +} + +fn all_tags(gfx: &ObjectController) { + let mut input = agb::input::ButtonController::new(); + let mut objs = Vec::new(); + + for (i, v) in TAG_MAP.values().enumerate() { + let x = (i % 7) as i32; + let y = (i / 7) as i32; + let sprite = v.get_sprite(0); + let (size_x, size_y) = sprite.size().to_width_height(); + let (size_x, size_y) = (size_x as i32, size_y as i32); + let mut obj = gfx.get_object(gfx.get_sprite(sprite).unwrap()).unwrap(); + obj.show(); + obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()); + objs.push((obj, v)); + } + + let mut count = 0; + let mut image = 0; + + let vblank = agb::interrupt::VBlank::get(); + + loop { + vblank.wait_for_vblank(); + + input.update(); + + if input.is_just_pressed(agb::input::Button::A) { + break; + } + + count += 1; + + if count % 5 == 0 { + image += 1; + for (obj, tag) in objs.iter_mut() { + obj.set_sprite(gfx.get_sprite(tag.get_animation_sprite(image)).unwrap()); + obj.commit(); + } + } + } +} + +#[agb::entry] +fn main(mut gba: agb::Gba) -> ! { + let gfx = gba.display.object.get(); + + loop { + all_tags(&gfx); + all_sprites(&gfx); + } +} diff --git a/agb/src/agb_alloc/block_allocator.rs b/agb/src/agb_alloc/block_allocator.rs index 6dfb154b..c029522e 100644 --- a/agb/src/agb_alloc/block_allocator.rs +++ b/agb/src/agb_alloc/block_allocator.rs @@ -12,7 +12,7 @@ use core::ptr::NonNull; use crate::interrupt::free; use bare_metal::{CriticalSection, Mutex}; -use super::bump_allocator::BumpAllocator; +use super::bump_allocator::{BumpAllocator, StartEnd}; use super::SendNonNull; struct Block { @@ -49,9 +49,9 @@ pub(crate) struct BlockAllocator { } impl BlockAllocator { - pub(super) const unsafe fn new() -> Self { + pub const unsafe fn new(start: StartEnd) -> Self { Self { - inner_allocator: BumpAllocator::new(), + inner_allocator: BumpAllocator::new(start), state: Mutex::new(RefCell::new(BlockAllocatorState { first_free_block: None, })), @@ -76,7 +76,7 @@ impl BlockAllocator { } /// Requests a brand new block from the inner bump allocator - fn new_block(&self, layout: Layout, cs: &CriticalSection) -> *mut u8 { + fn new_block(&self, layout: Layout, cs: &CriticalSection) -> Option> { let overall_layout = Block::either_layout(layout); self.inner_allocator.alloc_critical(overall_layout, cs) } @@ -111,10 +111,8 @@ impl BlockAllocator { } }); } -} -unsafe impl GlobalAlloc for BlockAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + pub unsafe fn alloc(&self, layout: Layout) -> Option> { // find a block that this current request fits in let full_layout = Block::either_layout(layout); @@ -133,7 +131,7 @@ unsafe impl GlobalAlloc for BlockAllocator { let curr_block = curr.as_mut(); if curr_block.size == full_layout.size() { *list_ptr = curr_block.next; - return curr.as_ptr().cast(); + return Some(curr.cast()); } else if curr_block.size >= block_after_layout.size() { // can split block let split_block = Block { @@ -148,7 +146,7 @@ unsafe impl GlobalAlloc for BlockAllocator { *split_ptr = split_block; *list_ptr = NonNull::new(split_ptr).map(SendNonNull); - return curr.as_ptr().cast(); + return Some(curr.cast()); } current_block = curr_block.next; list_ptr = &mut curr_block.next; @@ -158,7 +156,7 @@ unsafe impl GlobalAlloc for BlockAllocator { }) } - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + pub unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { let new_layout = Block::either_layout(layout).pad_to_align(); free(|key| { let mut state = self.state.borrow(*key).borrow_mut(); @@ -200,3 +198,16 @@ unsafe impl GlobalAlloc for BlockAllocator { self.normalise(); } } + +unsafe impl GlobalAlloc for BlockAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + match self.alloc(layout) { + None => core::ptr::null_mut(), + Some(p) => p.as_ptr(), + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dealloc(ptr, layout); + } +} diff --git a/agb/src/agb_alloc/bump_allocator.rs b/agb/src/agb_alloc/bump_allocator.rs index d1ddac3a..daafb3c1 100644 --- a/agb/src/agb_alloc/bump_allocator.rs +++ b/agb/src/agb_alloc/bump_allocator.rs @@ -6,26 +6,33 @@ use super::SendNonNull; use crate::interrupt::free; use bare_metal::{CriticalSection, Mutex}; +pub(crate) struct StartEnd { + pub start: fn() -> usize, + pub end: fn() -> usize, +} + pub(crate) struct BumpAllocator { current_ptr: Mutex>>>, + start_end: Mutex, } impl BumpAllocator { - pub const fn new() -> Self { + pub const fn new(start_end: StartEnd) -> Self { Self { current_ptr: Mutex::new(RefCell::new(None)), + start_end: Mutex::new(start_end), } } } impl BumpAllocator { - pub fn alloc_critical(&self, layout: Layout, cs: &CriticalSection) -> *mut u8 { + pub fn alloc_critical(&self, layout: Layout, cs: &CriticalSection) -> Option> { let mut current_ptr = self.current_ptr.borrow(*cs).borrow_mut(); let ptr = if let Some(c) = *current_ptr { c.as_ptr() as usize } else { - get_data_end() + (self.start_end.borrow(*cs).start)() }; let alignment_bitmask = layout.align() - 1; @@ -36,53 +43,26 @@ impl BumpAllocator { let resulting_ptr = ptr + amount_to_add; let new_current_ptr = resulting_ptr + layout.size(); - if new_current_ptr as usize >= super::EWRAM_END { - return core::ptr::null_mut(); + if new_current_ptr as usize >= (self.start_end.borrow(*cs).end)() { + return None; } *current_ptr = NonNull::new(new_current_ptr as *mut _).map(SendNonNull); - resulting_ptr as *mut _ + NonNull::new(resulting_ptr as *mut _) } - pub fn alloc_safe(&self, layout: Layout) -> *mut u8 { + pub fn alloc_safe(&self, layout: Layout) -> Option> { free(|key| self.alloc_critical(layout, key)) } } unsafe impl GlobalAlloc for BumpAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - self.alloc_safe(layout) + match self.alloc_safe(layout) { + None => core::ptr::null_mut(), + Some(p) => p.as_ptr(), + } } unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } - -fn get_data_end() -> usize { - extern "C" { - static __ewram_data_end: usize; - } - - // TODO: This seems completely wrong, but without the &, rust generates - // a double dereference :/. Maybe a bug in nightly? - (unsafe { &__ewram_data_end }) as *const _ as usize -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test_case] - fn should_return_data_end_somewhere_in_ewram(_gba: &mut crate::Gba) { - let data_end = get_data_end(); - - assert!( - 0x0200_0000 <= data_end, - "data end should be bigger than 0x0200_0000, got {}", - data_end - ); - assert!( - 0x0204_0000 > data_end, - "data end should be smaller than 0x0203_0000" - ); - } -} diff --git a/agb/src/agb_alloc/mod.rs b/agb/src/agb_alloc/mod.rs index 4084f3af..9a2c6a94 100644 --- a/agb/src/agb_alloc/mod.rs +++ b/agb/src/agb_alloc/mod.rs @@ -2,11 +2,13 @@ use core::alloc::Layout; use core::ops::{Deref, DerefMut}; use core::ptr::NonNull; -mod block_allocator; -mod bump_allocator; +pub(crate) mod block_allocator; +pub(crate) mod bump_allocator; use block_allocator::BlockAllocator; +use self::bump_allocator::StartEnd; + struct SendNonNull(NonNull); unsafe impl Send for SendNonNull {} @@ -33,7 +35,12 @@ impl DerefMut for SendNonNull { const EWRAM_END: usize = 0x0204_0000; #[global_allocator] -static GLOBAL_ALLOC: BlockAllocator = unsafe { BlockAllocator::new() }; +static GLOBAL_ALLOC: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: get_data_end, + end: || EWRAM_END, + }) +}; #[cfg(test)] pub unsafe fn number_of_blocks() -> u32 { @@ -49,6 +56,16 @@ fn alloc_error(layout: Layout) -> ! { ); } +fn get_data_end() -> usize { + extern "C" { + static __ewram_data_end: usize; + } + + // TODO: This seems completely wrong, but without the &, rust generates + // a double dereference :/. Maybe a bug in nightly? + (unsafe { &__ewram_data_end }) as *const _ as usize +} + #[cfg(test)] mod test { const EWRAM_START: usize = 0x0200_0000; @@ -118,4 +135,19 @@ mod test { assert_eq!(v1[40], 137); assert_eq!(v2[78], 1075); } + + #[test_case] + fn should_return_data_end_somewhere_in_ewram(_gba: &mut crate::Gba) { + let data_end = get_data_end(); + + assert!( + 0x0200_0000 <= data_end, + "data end should be bigger than 0x0200_0000, got {}", + data_end + ); + assert!( + 0x0204_0000 > data_end, + "data end should be smaller than 0x0203_0000" + ); + } } diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index fe6b5579..c18ec604 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -1,9 +1,10 @@ use crate::memory_mapped::MemoryMapped; use bitflags::bitflags; +use modular_bitfield::BitfieldSpecifier; use video::Video; -use self::object::ObjectControl; +use self::object::ObjectController; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. pub mod bitmap3; @@ -69,8 +70,8 @@ pub struct Display { pub struct ObjectDistribution {} impl ObjectDistribution { - pub fn get(&mut self) -> ObjectControl { - ObjectControl::new() + pub fn get(&mut self) -> ObjectController { + ObjectController::new() } } @@ -109,7 +110,7 @@ pub fn busy_wait_for_vblank() { while VCOUNT.get() < 160 {} } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(BitfieldSpecifier, Clone, Copy)] pub enum Priority { P0 = 0, P1 = 1, diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 4dea6e6f..a2b4c01c 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,124 +1,50 @@ +use alloc::vec::Vec; +use core::alloc::Layout; use core::cell::RefCell; +use core::hash::BuildHasherDefault; +use core::ptr::NonNull; +use core::slice; +use modular_bitfield::prelude::{B10, B2, B3, B4, B5, B8, B9}; +use modular_bitfield::{bitfield, BitfieldSpecifier}; +use rustc_hash::FxHasher; -use super::{palette16, Priority, DISPLAY_CONTROL}; -use crate::bitarray::Bitarray; +use hashbrown::HashMap; + +const BYTES_PER_TILE_4BPP: usize = 32; + +use super::palette16::Palette16; +use super::{Priority, DISPLAY_CONTROL}; +use crate::agb_alloc::block_allocator::BlockAllocator; +use crate::agb_alloc::bump_allocator::StartEnd; use crate::fixnum::Vector2D; -use crate::memory_mapped::MemoryMapped1DArray; -type AffineLoan<'a> = crate::arena::Loan<'a, 32>; -type AffineArena = crate::arena::Arena<32>; +use attributes::*; -const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0700_0000) }; -const PALETTE_SPRITE: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0500_0200) }; -const TILE_SPRITE: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x06010000) }; +static SPRITE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || TILE_SPRITE, + end: || TILE_SPRITE + 1024 * 8 * 4, + }) +}; -/// Handles distributing objects and matrices along with operations that effect all objects. -/// You can create an instance of this using the Gba struct. -/// -/// This handles distribution of sprites, ensuring that object ids are not reused and are -/// returned to the pool once you're done handling them. -/// -/// # Examples -/// -/// ``` -/// # #![no_std] -/// # #![no_main] -/// # -/// # use agb::Gba; -/// # -/// # #[agb::entry] -/// # fn main() -> ! { -/// let mut gba = Gba::new(); -/// let mut object = gba.display.object.get(); -/// # -/// # loop {} -/// # } -/// ``` -pub struct ObjectControl { - objects: RefCell>, - affines: AffineArena, +static PALETTE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || PALETTE_SPRITE, + end: || PALETTE_SPRITE + 0x200, + }) +}; + +const PALETTE_SPRITE: usize = 0x0500_0200; +const TILE_SPRITE: usize = 0x06010000; +const OBJECT_ATTRIBUTE_MEMORY: usize = 0x0700_0000; + +pub struct Sprite { + palette: &'static Palette16, + data: &'static [u8], + size: Size, } -struct ObjectLoan<'a> { - index: u8, - objects: &'a RefCell>, -} - -/// The standard object, without rotation. -/// -/// You should create this from an instance of ObjectControl created using the Gba struct. Note that -/// no changes made to this will be visible until `commit()` is called. You should call `commit()` during -/// vblank to ensure that you get no visual artifacts. -/// -/// This struct implements a sort of builder pattern, allowing you to chain settings together. -/// -/// # Examples -/// -/// ``` -/// # #![no_std] -/// # #![no_main] -/// # -/// # use agb::Gba; -/// use agb::display::object::Size; -/// -/// # #[agb::entry] -/// # fn main() -> ! { -/// # let mut gba = Gba::new(); -/// let mut object = gba.display.object.get(); -/// -/// let mut my_new_object = object.get_object_standard(); -/// my_new_object.set_x(50) -/// .set_y(50) -/// .set_sprite_Size(Size::S8x8) -/// .set_tile_id(7) -/// .show(); -/// -/// // some time later in vblank -/// my_new_object.commit(); -/// # loop {} -/// # } -/// ``` -pub struct ObjectStandard<'a> { - attributes: ObjectAttribute, - loan: ObjectLoan<'a>, -} - -/// The affine object, with potential for using a transformation matrix to alter -/// how the sprite is rendered to screen. -pub struct ObjectAffine<'a> { - attributes: ObjectAttribute, - loan: ObjectLoan<'a>, - aff_loan: Option>, -} - -/// Refers to an affine matrix in the OAM. Includes both an index and the -/// components of the affine matrix. -pub struct AffineMatrix<'a> { - pub attributes: AffineMatrixAttributes, - loan: AffineLoan<'a>, -} - -/// The components of the affine matrix. The components are fixed point 8:8. -/// TODO is a type that can handle fixed point arithmetic. -pub struct AffineMatrixAttributes { - pub p_a: i16, - pub p_b: i16, - pub p_c: i16, - pub p_d: i16, -} - -#[allow(dead_code)] -enum Mode { - Normal = 0, - Affine = 1, - Hidden = 2, - AffineDouble = 3, -} - -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum Size { // stored as attr0 attr1 S8x8 = 0b00_00, @@ -137,401 +63,670 @@ pub enum Size { S32x64 = 0b10_11, } -impl ObjectStandard<'_> { - /// Commits the object to OAM such that the updated version is displayed on - /// screen. Recommend to do this during VBlank. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.index) } +#[macro_export] +macro_rules! include_aseprite { + ($($aseprite_path: expr),*) => {{ + use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; + use $crate::display::palette16::Palette16; + + $crate::include_aseprite_inner!($($aseprite_path),*); + + &Graphics::new(SPRITES, TAGS) + }}; +} + +pub struct Graphics { + sprites: &'static [Sprite], + tag_map: &'static TagMap, +} + +impl Graphics { + pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self { + Self { sprites, tag_map } } - - /// Sets the x coordinate of the sprite on screen. - pub fn set_x(&mut self, x: u16) -> &mut Self { - self.attributes.set_x(x); - - self + pub const fn tags(&self) -> &TagMap { + self.tag_map } - - /// Sets the y coordinate of the sprite on screen. - pub fn set_y(&mut self, y: u16) -> &mut Self { - self.attributes.set_y(y); - - self - } - - /// Sets the index of the tile to use as the sprite. Potentially a temporary function. - pub fn set_tile_id(&mut self, id: u16) -> &mut Self { - self.attributes.set_tile_id(id); - - self - } - - /// Sets whether the sprite is horizontally mirrored or not. - pub fn set_hflip(&mut self, hflip: bool) -> &mut Self { - self.attributes.set_hflip(hflip); - - self - } - - /// Sets the sprite size, will read tiles in x major order to construct this. - pub fn set_sprite_size(&mut self, size: Size) -> &mut Self { - self.attributes.set_size(size); - - self - } - - /// Show the object on screen. - pub fn show(&mut self) -> &mut Self { - self.attributes.set_mode(Mode::Normal); - - self - } - - /// Hide the object and do not render. - pub fn hide(&mut self) -> &mut Self { - self.attributes.set_mode(Mode::Hidden); - - self - } - - /// Sets the palette to use for this sprite - pub fn set_palette(&mut self, palette: u16) -> &mut Self { - self.attributes.set_palette(palette); - - self - } - - /// Sets the x and y position of the object, performing casts as nessesary - /// to fit within the bits allocated for this purpose. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - let x = position.x as u16; - let y = position.y as u16; - self.attributes.set_x(x); - self.attributes.set_y(y); - - self - } - - /// Sets the priority (used for z ordering) of this sprite - pub fn set_priority(&mut self, p: Priority) -> &mut Self { - self.attributes.set_priority(p); - - self + pub const fn sprites(&self) -> &[Sprite] { + self.sprites } } -impl<'a> ObjectAffine<'a> { - /// Commits the object to OAM such that the updated version is displayed on - /// screen. Recommend to do this during VBlank. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.index) } +pub struct TagMap { + tags: &'static [(&'static str, Tag)], +} + +const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; } - /// Sets the x coordinate of the sprite on screen. - pub fn set_x(&mut self, x: u16) { - self.attributes.set_x(x) - } - /// Sets the y coordinate of the sprite on screen. - pub fn set_y(&mut self, y: u16) { - self.attributes.set_y(y) - } - /// Sets the index of the tile to use as the sprite. Potentially a temporary function. - pub fn set_tile_id(&mut self, id: u16) { - self.attributes.set_tile_id(id) - } - /// Sets the sprite size, will read tiles in x major order to construct this. - pub fn set_sprite_size(&mut self, size: Size) { - self.attributes.set_size(size); - } - - /// Show the object on screen. Panics if affine matrix has not been set. - pub fn show(&mut self) { - if self.aff_loan.is_none() { - panic!("affine matrix should be set") + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; } - self.attributes.set_mode(Mode::Affine) - } - /// Hide the object and do not render the sprite. - pub fn hide(&mut self) { - self.attributes.set_mode(Mode::Hidden) - } - - /// Sets the affine matrix to use. Changing the affine matrix will change - /// how the sprite is rendered. - pub fn set_affine_mat(&mut self, aff: &AffineMatrix<'a>) { - self.attributes.set_affine(aff.loan.my_index); - self.aff_loan = Some(aff.loan.clone()); - } - - /// Sets the x and y position of the object, performing casts as nessesary - /// to fit within the bits allocated for this purpose. - pub fn set_position(&mut self, position: Vector2D) { - let x = position.x as u16; - let y = position.y as u16; - self.attributes.set_x(x); - self.attributes.set_y(y); - } - - pub fn set_priority(&mut self, p: Priority) { - self.attributes.set_priority(p) + i += 1; } + true } -fn set_bits(current: u16, value: u16, length: u16, shift: u16) -> u16 { - let mask: u16 = (1 << length) - 1; - (current & !(mask << shift)) | ((value & mask) << shift) -} +impl TagMap { + pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap { + Self { tags } + } + pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> { + let mut i = 0; + while i < self.tags.len() { + let s = self.tags[i].0; + if const_byte_compare(s.as_bytes(), tag.as_bytes()) { + return Some(&self.tags[i].1); + } -impl Drop for ObjectLoan<'_> { - fn drop(&mut self) { - let attributes = ObjectAttribute::new(); - unsafe { - attributes.commit(self.index); + i += 1; } - let mut objs = self.objects.borrow_mut(); - objs.set(self.index as usize, false); + + None + } + pub const fn get(&'static self, tag: &str) -> &'static Tag { + let t = self.try_get(tag); + match t { + Some(t) => t, + None => panic!("The requested tag does not exist"), + } + } + pub fn values(&self) -> impl Iterator { + self.tags.iter().map(|x| &x.1) } } -struct ObjectAttribute { - a0: u16, - a1: u16, - a2: u16, +#[derive(Clone, Copy)] +enum Direction { + Forward, + Backward, + Pingpong, } -impl ObjectAttribute { - unsafe fn commit(&self, index: u8) { - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4, self.a0); - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 1, self.a1); - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 2, self.a2); - } - - fn set_hflip(&mut self, hflip: bool) { - self.a1 = set_bits(self.a1, hflip as u16, 1, 0xC); - } - - fn set_size(&mut self, size: Size) { - let a1 = size as u16 & 0b11; - let a0 = (size as u16 >> 2) & 0b11; - - self.a0 = set_bits(self.a0, a0, 2, 0xE); - self.a1 = set_bits(self.a1, a1, 2, 0xE); - } - - fn set_palette(&mut self, palette: u16) { - self.a2 = set_bits(self.a2, palette, 4, 0xC); - } - - fn set_x(&mut self, x: u16) { - self.a1 = set_bits(self.a1, x, 9, 0); - } - - fn set_y(&mut self, y: u16) { - self.a0 = set_bits(self.a0, y, 8, 0) - } - - fn set_tile_id(&mut self, id: u16) { - self.a2 = set_bits(self.a2, id, 10, 0); - } - - fn set_mode(&mut self, mode: Mode) { - self.a0 = set_bits(self.a0, mode as u16, 2, 8); - } - - fn set_affine(&mut self, aff_id: u8) { - self.a1 = set_bits(self.a1, aff_id as u16, 5, 0x9); - } - - fn set_priority(&mut self, p: Priority) { - self.a2 = set_bits(self.a2, p as u16, 2, 0x0A); +impl Direction { + const fn from_usize(a: usize) -> Self { + match a { + 0 => Direction::Forward, + 1 => Direction::Backward, + 2 => Direction::Pingpong, + _ => panic!("Invalid direction, this is a bug in image converter or agb"), + } } } -impl AffineMatrix<'_> { - /// Commits matrix to OAM, will cause any objects using this matrix to be updated. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.my_index) }; +pub struct Tag { + sprites: *const Sprite, + len: usize, + direction: Direction, +} + +impl Tag { + pub fn get_sprites(&self) -> &'static [Sprite] { + unsafe { slice::from_raw_parts(self.sprites, self.len) } + } + + pub fn get_sprite(&self, idx: usize) -> &'static Sprite { + &self.get_sprites()[idx] + } + + #[inline] + pub fn get_animation_sprite(&self, idx: usize) -> &'static Sprite { + let len_sub_1 = self.len - 1; + match self.direction { + Direction::Forward => self.get_sprite(idx % self.len), + Direction::Backward => self.get_sprite(len_sub_1 - (idx % self.len)), + Direction::Pingpong => self.get_sprite( + (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize).abs() + as usize, + ), + } + } + + #[doc(hidden)] + pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self { + assert!(from <= to); + assert!(to < sprites.len()); + Self { + sprites: &sprites[from] as *const Sprite, + len: to - from + 1, + direction: Direction::from_usize(direction), + } } } -impl AffineMatrixAttributes { - #[allow(clippy::identity_op)] - unsafe fn commit(&self, index: u8) { - let index = index as usize * 4; - OBJECT_ATTRIBUTE_MEMORY.set((index + 0) * 4 + 3, self.p_a as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 1) * 4 + 3, self.p_b as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 2) * 4 + 3, self.p_c as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 3) * 4 + 3, self.p_d as u16); +impl Size { + const fn number_of_tiles(self) -> usize { + match self { + Size::S8x8 => 1, + Size::S16x16 => 4, + Size::S32x32 => 16, + Size::S64x64 => 64, + Size::S16x8 => 2, + Size::S32x8 => 4, + Size::S32x16 => 8, + Size::S64x32 => 32, + Size::S8x16 => 2, + Size::S8x32 => 4, + Size::S16x32 => 8, + Size::S32x64 => 32, + } + } + const fn shape_size(self) -> (u8, u8) { + (self as u8 >> 2, self as u8 & 0b11) + } + + pub const fn from_width_height(width: usize, height: usize) -> Self { + match (width, height) { + (8, 8) => Size::S8x8, + (16, 16) => Size::S16x16, + (32, 32) => Size::S32x32, + (64, 64) => Size::S64x64, + (16, 8) => Size::S16x8, + (32, 8) => Size::S32x8, + (32, 16) => Size::S32x16, + (64, 32) => Size::S64x32, + (8, 16) => Size::S8x16, + (8, 32) => Size::S8x32, + (16, 32) => Size::S16x32, + (32, 64) => Size::S32x64, + (_, _) => panic!("Bad width and height!"), + } + } + + pub const fn to_width_height(self) -> (usize, usize) { + match self { + Size::S8x8 => (8, 8), + Size::S16x16 => (16, 16), + Size::S32x32 => (32, 32), + Size::S64x64 => (64, 64), + Size::S16x8 => (16, 8), + Size::S32x8 => (32, 8), + Size::S32x16 => (32, 16), + Size::S64x32 => (64, 32), + Size::S8x16 => (8, 16), + Size::S8x32 => (8, 32), + Size::S16x32 => (16, 32), + Size::S32x64 => (32, 64), + } } } -impl ObjectAttribute { +pub struct SpriteBorrow<'a> { + id: SpriteId, + sprite_location: u16, + palette_location: u16, + controller: &'a RefCell, +} + +#[derive(Clone, Copy)] +struct Storage { + location: u16, + count: u16, +} + +impl Storage { + fn from_sprite_ptr(d: NonNull) -> Self { + Self { + location: (((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP) as u16, + count: 1, + } + } + fn from_palette_ptr(d: NonNull) -> Self { + Self { + location: ((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size()) as u16, + count: 1, + } + } + fn as_palette_ptr(&self) -> *mut u8 { + (self.location as usize * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8 + } + fn as_sprite_ptr(&self) -> *mut u8 { + (self.location as usize * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8 + } +} + +struct Attributes { + a0: ObjectAttribute0, + a1s: ObjectAttribute1Standard, + a1a: ObjectAttribute1Affine, + a2: ObjectAttribute2, +} + +impl Attributes { fn new() -> Self { - let mut o = ObjectAttribute { - a0: 0, - a1: 0, - a2: 0, - }; - o.set_mode(Mode::Hidden); - o + Self { + a0: ObjectAttribute0::new(), + a1s: ObjectAttribute1Standard::new(), + a1a: ObjectAttribute1Affine::new(), + a2: ObjectAttribute2::new(), + } } } -impl ObjectControl { +pub struct Object<'a, 'b> { + sprite: SpriteBorrow<'a>, + previous_sprite: SpriteBorrow<'a>, + loan: Loan<'b>, + attrs: Attributes, +} + +struct SpriteControllerInner { + palette: HashMap>, + sprite: HashMap>, +} + +pub struct SpriteController { + inner: RefCell, +} + +struct Loan<'a> { + index: u8, + free_list: &'a RefCell>, +} + +impl Drop for Loan<'_> { + fn drop(&mut self) { + let mut list = self.free_list.borrow_mut(); + list.push(self.index); + } +} + +pub struct ObjectController { + free_affine_matricies: RefCell>, + free_objects: RefCell>, + sprite_controller: SpriteController, +} + +impl ObjectController { pub(crate) fn new() -> Self { - let o = ObjectAttribute::new(); - for index in 0..128 { - unsafe { o.commit(index) }; - } - ObjectControl { - objects: RefCell::new(Bitarray::new()), - affines: AffineArena::new(), - } - } + DISPLAY_CONTROL.set_bits(1, 1, 0x6); + DISPLAY_CONTROL.set_bits(1, 1, 0xC); + DISPLAY_CONTROL.set_bits(0, 1, 0x7); - fn set_sprite_tilemap_entry(&self, index: usize, data: u32) { - TILE_SPRITE.set(index, data); - } - - /// Copies raw palettes to the background palette without any checks. - pub fn set_sprite_palette_raw(&self, colour: &[u16]) { - for (index, &entry) in colour.iter().enumerate() { - self.set_sprite_palette_entry(index, entry) - } - } - fn set_sprite_palette_entry(&self, index: usize, colour: u16) { - PALETTE_SPRITE.set(index, colour) - } - - fn set_sprite_palette(&self, pal_index: u8, palette: &palette16::Palette16) { - for (colour_index, &colour) in palette.colours.iter().enumerate() { - PALETTE_SPRITE.set(pal_index as usize * 16 + colour_index, colour); - } - } - - pub fn set_sprite_palettes(&self, palettes: &[palette16::Palette16]) { - for (palette_index, entry) in palettes.iter().enumerate() { - self.set_sprite_palette(palette_index as u8, entry) - } - } - - /// Copies tiles to the sprite tilemap without any checks. - pub fn set_sprite_tilemap(&self, tiles: &[u32]) { - for (index, &tile) in tiles.iter().enumerate() { - self.set_sprite_tilemap_entry(index, tile) - } - } - - pub fn set_sprite_tilemap_at_idx(&self, idx: usize, tiles: &[u32]) { - for (index, &tile) in tiles.iter().enumerate() { - self.set_sprite_tilemap_entry(index + idx, tile) - } - } - - /// Enable objects on the GBA. - pub fn enable(&mut self) { - let disp = DISPLAY_CONTROL.get(); - let disp = disp | (1 << 0x0C); - DISPLAY_CONTROL.set(disp); - } - - /// Disable objects, objects won't be rendered. - pub fn disable(&mut self) { - let disp = DISPLAY_CONTROL.get(); - let disp = disp & !(1 << 0x0C); - DISPLAY_CONTROL.set(disp); - } - - fn get_unused_object_index(&self) -> u8 { - let mut objects = self.objects.borrow_mut(); - for index in 0..128 { - if !objects.get(index).unwrap() { - objects.set(index, true); - return index as u8; + for i in 0..128 { + unsafe { + (OBJECT_ATTRIBUTE_MEMORY as *mut u16) + .add(i * 4) + .write_volatile(0b10 << 8) } } - panic!("object id must be less than 128"); - } - /// Get an unused standard object. Panics if more than 128 objects are - /// obtained. - pub fn get_object_standard(&self) -> ObjectStandard { - let id = self.get_unused_object_index(); - ObjectStandard { - attributes: ObjectAttribute::new(), - loan: ObjectLoan { - objects: &self.objects, - index: id, - }, + Self { + free_objects: RefCell::new((0..128).collect()), + free_affine_matricies: RefCell::new((0..32).collect()), + sprite_controller: SpriteController::new(), } } - /// Get an unused affine object. Panics if more than 128 objects are - /// obtained. - pub fn get_object_affine(&self) -> ObjectAffine { - let id = self.get_unused_object_index(); - ObjectAffine { - attributes: ObjectAttribute::new(), - loan: ObjectLoan { - objects: &self.objects, - index: id, - }, - aff_loan: None, - } + pub fn get_object<'a, 'b>(&'a self, sprite: SpriteBorrow<'b>) -> Option> { + let mut inner = self.free_objects.borrow_mut(); + let loan = Loan { + index: inner.pop()?, + free_list: &self.free_objects, + }; + + let mut attrs = Attributes::new(); + + attrs.a2.set_tile_index(sprite.sprite_location); + let shape_size = sprite.id.get_sprite().size.shape_size(); + attrs.a2.set_palete_bank(sprite.palette_location as u8); + attrs.a0.set_shape(shape_size.0); + attrs.a1a.set_size(shape_size.1); + attrs.a1s.set_size(shape_size.1); + + Some(Object { + previous_sprite: sprite.clone(), + sprite, + loan, + attrs, + }) } - /// Get an unused affine matrix. Panics if more than 32 affine matricies are - /// obtained. - pub fn get_affine(&self) -> AffineMatrix { - AffineMatrix { - attributes: AffineMatrixAttributes { - p_a: 0, - p_b: 0, - p_c: 0, - p_d: 0, - }, - loan: self - .affines - .get_next_free() - .expect("there are no affines avaliable"), + pub fn get_sprite(&self, sprite: &'static Sprite) -> Option { + self.sprite_controller.get_sprite(sprite) + } +} + +impl Drop for Object<'_, '_> { + fn drop(&mut self) { + self.attrs.a0.set_object_mode(ObjectMode::Disabled); + self.commit(); + } +} + +impl<'a, 'b> Object<'a, 'b> { + pub fn set_sprite(&'_ mut self, sprite: SpriteBorrow<'a>) { + self.attrs.a2.set_tile_index(sprite.sprite_location); + let shape_size = sprite.id.get_sprite().size.shape_size(); + self.attrs.a2.set_palete_bank(sprite.palette_location as u8); + self.attrs.a0.set_shape(shape_size.0); + self.attrs.a1a.set_size(shape_size.1); + self.attrs.a1s.set_size(shape_size.1); + self.sprite = sprite; + } + + pub fn show(&mut self) -> &mut Self { + self.attrs.a0.set_object_mode(ObjectMode::Normal); + + self + } + + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.attrs.a1s.set_horizontal_flip(flip); + self + } + + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.attrs.a1s.set_vertical_flip(flip); + self + } + + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16); + self.attrs.a1s.set_x(x.rem_euclid(1 << 9) as u16); + self + } + + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.attrs.a2.set_priority(priority); + self + } + + pub fn hide(&mut self) -> &mut Self { + self.attrs.a0.set_object_mode(ObjectMode::Disabled); + self + } + + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.attrs.a0.set_y(y as u8); + + self + } + + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + self.attrs.a0.set_y(position.y as u8); + self.attrs.a1a.set_x(position.x.rem_euclid(1 << 9) as u16); + self.attrs.a1s.set_x(position.x.rem_euclid(1 << 9) as u16); + self + } + + pub fn commit(&mut self) { + let mode = self.attrs.a0.object_mode(); + let attrs: [[u8; 2]; 3] = match mode { + ObjectMode::Normal => [ + self.attrs.a0.into_bytes(), + self.attrs.a1s.into_bytes(), + self.attrs.a2.into_bytes(), + ], + _ => [ + self.attrs.a0.into_bytes(), + self.attrs.a1a.into_bytes(), + self.attrs.a2.into_bytes(), + ], + }; + + unsafe { + let attrs: [u16; 3] = core::mem::transmute(attrs); + let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(self.loan.index as usize * 4); + + ptr.add(0).write_volatile(attrs[0]); + ptr.add(1).write_volatile(attrs[1]); + ptr.add(2).write_volatile(attrs[2]); + }; + self.previous_sprite = self.sprite.clone(); + } +} + +/// The Sprite Id is a thin wrapper around the pointer to the sprite in +/// rom and is therefore a unique identifier to a sprite +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct SpriteId(usize); + +impl SpriteId { + fn get_sprite(self) -> &'static Sprite { + // # Safety + // This must be constructed using the get_id of a sprite, so + // they are always valid and always static + unsafe { (self.0 as *const Sprite).as_ref().unwrap_unchecked() } + } +} + +/// The palette id is a thin wrapper around the pointer to the palette in rom +/// and is therefore a unique reference to a palette +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct PaletteId(usize); + +impl PaletteId { + fn get_palette(self) -> &'static Palette16 { + unsafe { (self.0 as *const Palette16).as_ref().unwrap_unchecked() } + } +} + +impl Palette16 { + fn get_id(&'static self) -> PaletteId { + PaletteId(self as *const _ as usize) + } + const fn layout() -> Layout { + Layout::new::() + } +} + +impl Sprite { + fn get_id(&'static self) -> SpriteId { + SpriteId(self as *const _ as usize) + } + fn layout(&self) -> Layout { + Layout::from_size_align(self.size.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() + } + pub const fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { + Self { + palette, + data, + size, + } + } + pub const fn size(&self) -> Size { + self.size + } +} + +impl SpriteController { + fn new() -> Self { + Self { + inner: RefCell::new(SpriteControllerInner::new()), + } + } + fn get_sprite(&self, sprite: &'static Sprite) -> Option { + let mut inner = self.inner.borrow_mut(); + let id = sprite.get_id(); + if let Some(storage) = inner.sprite.get_mut(&id) { + storage.count += 1; + let location = storage.location; + let palette_location = inner.get_palette(sprite.palette).unwrap(); + Some(SpriteBorrow { + id, + palette_location, + sprite_location: location, + controller: &self.inner, + }) + } else { + // layout is non zero sized, so this is safe to call + + let dest = unsafe { SPRITE_ALLOCATOR.alloc(sprite.layout())? }; + let palette_location = inner.get_palette(sprite.palette); + let palette_location = match palette_location { + Some(a) => a, + None => { + unsafe { SPRITE_ALLOCATOR.dealloc(dest.as_ptr(), sprite.layout()) } + return None; + } + }; + + unsafe { + dest.as_ptr() + .copy_from_nonoverlapping(sprite.data.as_ptr(), sprite.data.len()) + } + + let storage = Storage::from_sprite_ptr(dest); + inner.sprite.insert(id, storage); + + Some(SpriteBorrow { + id, + controller: &self.inner, + palette_location, + sprite_location: storage.location, + }) } } } -#[cfg(test)] -mod tests { - #[test_case] - fn get_and_release_object(gba: &mut crate::Gba) { - let objs = gba.display.object.get(); +impl SpriteControllerInner { + fn new() -> Self { + Self { + palette: HashMap::default(), + sprite: HashMap::default(), + } + } + fn get_palette(&mut self, palette: &'static Palette16) -> Option { + let id = palette.get_id(); + if let Some(storage) = self.palette.get_mut(&id) { + storage.count += 1; + Some(storage.location) + } else { + let dest = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout())? }; - let _o1 = { - let o0 = objs.get_object_standard(); - let o1 = objs.get_object_standard(); - assert_eq!(o0.loan.index, 0); - assert_eq!(o1.loan.index, 1); - o1 - }; + unsafe { + dest.as_ptr() + .cast::() + .copy_from_nonoverlapping(palette.colours.as_ptr(), palette.colours.len()) + } - let o0 = objs.get_object_standard(); - assert_eq!(o0.loan.index, 0); - let o2 = objs.get_object_affine(); - assert_eq!(o2.loan.index, 2); + let storage = Storage::from_palette_ptr(dest); + self.palette.insert(id, storage); + + Some(storage.location) + } } - #[test_case] - fn get_and_release_affine(gba: &mut crate::Gba) { - let objs = gba.display.object.get(); + fn return_sprite(&mut self, sprite: &'static Sprite) { + self.sprite + .entry(sprite.get_id()) + .and_replace_entry_with(|_, mut storage| { + storage.count -= 1; + if storage.count == 0 { + unsafe { SPRITE_ALLOCATOR.dealloc(storage.as_sprite_ptr(), sprite.layout()) } + None + } else { + Some(storage) + } + }); - let _a1 = { - let a0 = objs.get_affine(); - let a1 = objs.get_affine(); - assert_eq!(a0.loan.my_index, 0); - assert_eq!(a1.loan.my_index, 1); - a1 - }; + self.return_palette(sprite.palette) + } - let a0 = objs.get_affine(); - assert_eq!(a0.loan.my_index, 0); - let a2 = objs.get_affine(); - assert_eq!(a2.loan.my_index, 2); + fn return_palette(&mut self, palette: &'static Palette16) { + let id = palette.get_id(); + self.palette + .entry(id) + .and_replace_entry_with(|_, mut storage| { + storage.count -= 1; + if storage.count == 0 { + unsafe { + PALETTE_ALLOCATOR.dealloc(storage.as_palette_ptr(), Palette16::layout()); + } + None + } else { + Some(storage) + } + }); + } +} + +impl<'a> Drop for SpriteBorrow<'a> { + fn drop(&mut self) { + let mut inner = self.controller.borrow_mut(); + inner.return_sprite(self.id.get_sprite()) + } +} + +impl<'a> Clone for SpriteBorrow<'a> { + fn clone(&self) -> Self { + let mut inner = self.controller.borrow_mut(); + inner.sprite.entry(self.id).and_modify(|a| a.count += 1); + let _ = inner.get_palette(self.id.get_sprite().palette).unwrap(); + Self { + id: self.id, + sprite_location: self.sprite_location, + palette_location: self.palette_location, + controller: self.controller, + } + } +} + +#[derive(BitfieldSpecifier, Clone, Copy)] +enum ObjectMode { + Normal, + Affine, + Disabled, + AffineDouble, +} + +#[derive(BitfieldSpecifier, Clone, Copy)] +#[bits = 2] +enum GraphicsMode { + Normal, + AlphaBlending, + Window, +} + +#[derive(BitfieldSpecifier, Clone, Copy)] +enum ColourMode { + Four, + Eight, +} + +#[allow(dead_code)] +mod attributes { + use super::*; + #[bitfield] + #[derive(Clone, Copy)] + pub(super) struct ObjectAttribute0 { + pub y: B8, + pub object_mode: ObjectMode, + pub graphics_mode: GraphicsMode, + pub mosaic: bool, + pub colour_mode: ColourMode, + pub shape: B2, + } + + #[bitfield] + #[derive(Clone, Copy)] + pub(super) struct ObjectAttribute1Standard { + pub x: B9, + #[skip] + __: B3, + pub horizontal_flip: bool, + pub vertical_flip: bool, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy)] + pub(super) struct ObjectAttribute1Affine { + pub x: B9, + pub affine_index: B5, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy)] + pub(super) struct ObjectAttribute2 { + pub tile_index: B10, + pub priority: Priority, + pub palete_bank: B4, } } diff --git a/agb/src/display/palette16.rs b/agb/src/display/palette16.rs index f8a4bab2..9dc79687 100644 --- a/agb/src/display/palette16.rs +++ b/agb/src/display/palette16.rs @@ -1,3 +1,4 @@ +#[repr(C)] #[derive(Clone)] pub struct Palette16 { pub(crate) colours: [u16; 16], diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 86a54434..9eb98eea 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -112,6 +112,8 @@ /// ``` pub use agb_image_converter::include_gfx; +pub use agb_image_converter::include_aseprite_inner; + /// This macro declares the entry point to your game written using `agb`. /// /// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns. @@ -133,9 +135,7 @@ pub use agb_macros::entry; pub use agb_sound_converter::include_wav; -#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "alloc")] mod agb_alloc; mod arena; diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index bf271981..805e2469 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -1,6 +1,6 @@ use core::arch::asm; -use crate::display::object::AffineMatrixAttributes; +// use crate::display::object::AffineMatrixAttributes; use crate::fixnum::Num; #[allow(non_snake_case)] @@ -113,55 +113,55 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 { result } -pub fn affine_matrix( - x_scale: Num, - y_scale: Num, - rotation: u8, -) -> AffineMatrixAttributes { - let mut result = AffineMatrixAttributes { - p_a: 0, - p_b: 0, - p_c: 0, - p_d: 0, - }; +// pub fn affine_matrix( +// x_scale: Num, +// y_scale: Num, +// rotation: u8, +// ) -> AffineMatrixAttributes { +// let mut result = AffineMatrixAttributes { +// p_a: 0, +// p_b: 0, +// p_c: 0, +// p_d: 0, +// }; - #[allow(dead_code)] - #[repr(C, packed)] - struct Input { - x_scale: i16, - y_scale: i16, - rotation: u16, - } +// #[allow(dead_code)] +// #[repr(C, packed)] +// struct Input { +// x_scale: i16, +// y_scale: i16, +// rotation: u16, +// } - let input = Input { - y_scale: x_scale.to_raw(), - x_scale: y_scale.to_raw(), - rotation: rotation as u16, - }; +// let input = Input { +// y_scale: x_scale.to_raw(), +// x_scale: y_scale.to_raw(), +// rotation: rotation as u16, +// }; - unsafe { - asm!("swi 0x0F", - in("r0") &input as *const Input as usize, - in("r1") &mut result as *mut AffineMatrixAttributes as usize, - in("r2") 1, - in("r3") 2, - ) - } +// unsafe { +// asm!("swi 0x0F", +// in("r0") &input as *const Input as usize, +// in("r1") &mut result as *mut AffineMatrixAttributes as usize, +// in("r2") 1, +// in("r3") 2, +// ) +// } - result -} +// result +// } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test_case] - fn affine(_gba: &mut crate::Gba) { - // expect identity matrix - let one: Num = 1.into(); +// #[test_case] +// fn affine(_gba: &mut crate::Gba) { +// // expect identity matrix +// let one: Num = 1.into(); - let aff = affine_matrix(one, one, 0); - assert_eq!(aff.p_a, one.to_raw()); - assert_eq!(aff.p_d, one.to_raw()); - } -} +// let aff = affine_matrix(one, one, 0); +// assert_eq!(aff.p_a, one.to_raw()); +// assert_eq!(aff.p_d, one.to_raw()); +// } +// } diff --git a/book/games/pong/Cargo.lock b/book/games/pong/Cargo.lock index ad7beaa5..6f006849 100644 --- a/book/games/pong/Cargo.lock +++ b/book/games/pong/Cargo.lock @@ -24,6 +24,9 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", + "modular-bitfield", + "rustc-hash", ] [[package]] @@ -37,6 +40,7 @@ dependencies = [ name = "agb_image_converter" version = "0.6.0" dependencies = [ + "asefile", "image", "proc-macro2", "quote", @@ -51,7 +55,6 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] @@ -65,6 +68,31 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "asefile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5f7de918fd4cb18249819fc4bd27f6a5dbfbc9dcb271727f27dacf17ce880" +dependencies = [ + "bitflags", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -85,9 +113,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -118,11 +146,24 @@ dependencies = [ [[package]] name = "deflate" -version = "1.0.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", ] [[package]] @@ -136,6 +177,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -144,9 +194,9 @@ checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" [[package]] name = "image" -version = "0.24.1" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", @@ -164,14 +214,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "log" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + [[package]] name = "num-integer" version = "0.1.44" @@ -195,9 +291,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -214,15 +310,21 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.4" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] [[package]] @@ -232,12 +334,6 @@ dependencies = [ "agb", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -257,34 +353,10 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.8.5" +name = "rustc-hash" +version = "1.1.0" 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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "serde" @@ -306,6 +378,12 @@ dependencies = [ "syn", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.86" @@ -332,6 +410,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/book/games/pong/gfx/sprites.aseprite b/book/games/pong/gfx/sprites.aseprite index 1c5bb9dc..5363cd3d 100644 Binary files a/book/games/pong/gfx/sprites.aseprite and b/book/games/pong/gfx/sprites.aseprite differ diff --git a/book/games/pong/gfx/sprites.png b/book/games/pong/gfx/sprites.png deleted file mode 100644 index 9964bf0b..00000000 Binary files a/book/games/pong/gfx/sprites.png and /dev/null differ diff --git a/book/games/pong/gfx/sprites.toml b/book/games/pong/gfx/sprites.toml deleted file mode 100644 index 39c68850..00000000 --- a/book/games/pong/gfx/sprites.toml +++ /dev/null @@ -1,6 +0,0 @@ -version = "1.0" - -[image.sprites] -filename = "sprites.png" -tile_size = "16x16" -transparent_colour = "ff0044" \ No newline at end of file diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index c5fa5e78..5ccb9dfa 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -10,42 +10,27 @@ // which won't be a particularly clear error message. #![no_main] -use agb::display::object::Size; +use agb::display::object::{Graphics, Tag}; use agb::Gba; -// Put all the graphics related code in the gfx module -mod gfx { - use agb::display::object::ObjectControl; - - // Import the sprites into this module. This will create a `sprites` module - // and within that will be a constant called `sprites` which houses all the - // palette and tile data. - agb::include_gfx!("gfx/sprites.toml"); - - // Loads the sprites tile data and palette data into VRAM - pub fn load_sprite_data(object: &mut ObjectControl) { - object.set_sprite_palettes(sprites::sprites.palettes); - object.set_sprite_tilemap(sprites::sprites.tiles); - } -} +const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); // The main function must take 0 arguments and never return. The agb::entry decorator // ensures that everything is in order. `agb` will call this after setting up the stack // and interrupt handlers correctly. #[agb::entry] fn main(mut gba: Gba) -> ! { - let _tiled = gba.display.video.tiled0(); - let mut object = gba.display.object.get(); - gfx::load_sprite_data(&mut object); - object.enable(); + let object = gba.display.object.get(); - let mut ball = object.get_object_standard(); + const BALL: &Tag = GRAPHICS.tags().get("Ball"); + let ball_sprite = object + .get_sprite(BALL.get_sprite(0)) + .expect("We should be able to load a sprite"); + let mut ball = object + .get_object(ball_sprite) + .expect("We should have enoguh space to store an object"); - ball.set_x(50) - .set_y(50) - .set_sprite_size(Size::S16x16) - .set_tile_id(4 * 2) - .show(); + ball.set_x(50).set_y(50).show(); let mut ball_x = 50; let mut ball_y = 50; diff --git a/examples/the-hat-chooses-the-wizard/.vscode/settings.json b/examples/the-hat-chooses-the-wizard/.vscode/settings.json index a3c76e3a..e58f9587 100644 --- a/examples/the-hat-chooses-the-wizard/.vscode/settings.json +++ b/examples/the-hat-chooses-the-wizard/.vscode/settings.json @@ -2,5 +2,10 @@ "files.associations": { "*.tsx": "xml", "*.tmx": "xml" - } + }, + "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.checkOnSave.extraArgs": [ + "--target", + "thumbv4t-none-eabi" + ] } \ No newline at end of file diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index 5b924d8d..c0f73bf9 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -24,6 +24,9 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", + "modular-bitfield", + "rustc-hash", ] [[package]] @@ -37,6 +40,7 @@ dependencies = [ name = "agb_image_converter" version = "0.6.0" dependencies = [ + "asefile", "image", "proc-macro2", "quote", @@ -51,7 +55,6 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] @@ -65,6 +68,31 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "asefile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5f7de918fd4cb18249819fc4bd27f6a5dbfbc9dcb271727f27dacf17ce880" +dependencies = [ + "bitflags", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -118,11 +146,24 @@ dependencies = [ [[package]] name = "deflate" -version = "1.0.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", ] [[package]] @@ -136,6 +177,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -144,9 +194,9 @@ checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" [[package]] name = "image" -version = "0.24.1" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", @@ -170,14 +220,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "log" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + [[package]] name = "num-integer" version = "0.1.44" @@ -201,9 +297,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -220,23 +316,23 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.5" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -256,34 +352,10 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.8.5" +name = "rustc-hash" +version = "1.1.0" 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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" @@ -322,6 +394,12 @@ dependencies = [ "serde", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.86" @@ -357,6 +435,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/examples/the-hat-chooses-the-wizard/gfx/object_sheet.png b/examples/the-hat-chooses-the-wizard/gfx/object_sheet.png deleted file mode 100644 index 5ba18647..00000000 Binary files a/examples/the-hat-chooses-the-wizard/gfx/object_sheet.png and /dev/null differ diff --git a/examples/the-hat-chooses-the-wizard/gfx/object_sheet.toml b/examples/the-hat-chooses-the-wizard/gfx/object_sheet.toml deleted file mode 100644 index 279107be..00000000 --- a/examples/the-hat-chooses-the-wizard/gfx/object_sheet.toml +++ /dev/null @@ -1,6 +0,0 @@ -version = "1.0" - -[image.object_sheet] -filename = "object_sheet.png" -tile_size = "8x8" -transparent_colour = "2ce8f4" diff --git a/examples/the-hat-chooses-the-wizard/gfx/sprites.aseprite b/examples/the-hat-chooses-the-wizard/gfx/sprites.aseprite new file mode 100644 index 00000000..dc34f940 Binary files /dev/null and b/examples/the-hat-chooses-the-wizard/gfx/sprites.aseprite differ diff --git a/examples/the-hat-chooses-the-wizard/src/enemies.rs b/examples/the-hat-chooses-the-wizard/src/enemies.rs index 399474f3..da9df438 100644 --- a/examples/the-hat-chooses-the-wizard/src/enemies.rs +++ b/examples/the-hat-chooses-the-wizard/src/enemies.rs @@ -1,9 +1,20 @@ -use super::{object_tiles, sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level}; +use crate::TAG_MAP; + +use super::{sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level}; use agb::{ - display::object::{ObjectControl, Size}, + display::object::{ObjectController, Size, Tag}, fixnum::Vector2D, }; +const SLIME_IDLE: &Tag = TAG_MAP.get("Slime Idle"); +const SLIME_JUMP: &Tag = TAG_MAP.get("Slime Jump"); +const SLIME_SPLAT: &Tag = TAG_MAP.get("Slime splat"); + +const SNAIL_EMERGE: &Tag = TAG_MAP.get("Snail Emerge"); +const SNAIL_MOVE: &Tag = TAG_MAP.get("Snail Move"); +const SNAIL_DEATH: &Tag = TAG_MAP.get("Snail Death"); +const SNAIL_IDLE: &Tag = TAG_MAP.get("Snail Idle"); + enum UpdateState { Nothing, KillPlayer, @@ -28,11 +39,11 @@ pub enum EnemyUpdateState { } impl<'a> Enemy<'a> { - pub fn new_slime(object: &'a ObjectControl, start_pos: Vector2D) -> Self { + pub fn new_slime(object: &'a ObjectController, start_pos: Vector2D) -> Self { Enemy::Slime(Slime::new(object, start_pos + (0, 1).into())) } - pub fn new_snail(object: &'a ObjectControl, start_pos: Vector2D) -> Self { + pub fn new_snail(object: &'a ObjectController, start_pos: Vector2D) -> Self { Enemy::Snail(Snail::new(object, start_pos)) } @@ -45,6 +56,7 @@ impl<'a> Enemy<'a> { pub fn update( &mut self, + controller: &'a ObjectController, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -52,8 +64,12 @@ impl<'a> Enemy<'a> { sfx_player: &mut SfxPlayer, ) -> EnemyUpdateState { let update_state = match self { - Enemy::Slime(slime) => slime.update(level, player_pos, hat_state, timer, sfx_player), - Enemy::Snail(snail) => snail.update(level, player_pos, hat_state, timer, sfx_player), + Enemy::Slime(slime) => { + slime.update(controller, level, player_pos, hat_state, timer, sfx_player) + } + Enemy::Snail(snail) => { + snail.update(controller, level, player_pos, hat_state, timer, sfx_player) + } Enemy::Empty => UpdateState::Nothing, }; @@ -82,7 +98,7 @@ struct EnemyInfo<'a> { impl<'a> EnemyInfo<'a> { fn new( - object: &'a ObjectControl, + object: &'a ObjectController, start_pos: Vector2D, collision: Vector2D, ) -> Self { @@ -123,19 +139,18 @@ pub struct Slime<'a> { } impl<'a> Slime<'a> { - fn new(object: &'a ObjectControl, start_pos: Vector2D) -> Self { + fn new(object: &'a ObjectController, start_pos: Vector2D) -> Self { let mut slime = Slime { enemy_info: EnemyInfo::new(object, start_pos, (14u16, 14u16).into()), state: SlimeState::Idle, }; - slime.enemy_info.entity.sprite.set_sprite_size(Size::S16x16); - slime } fn update( &mut self, + controller: &'a ObjectController, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -147,11 +162,12 @@ impl<'a> Slime<'a> { match self.state { SlimeState::Idle => { - let offset = (timer / 16 % 2) * 4; - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SLIME_IDLE_START + offset as u16); + let offset = (timer / 16) as usize; + + let frame = SLIME_IDLE.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); if (self.enemy_info.entity.position - player_pos).magnitude_squared() < (64 * 64).into() @@ -178,7 +194,7 @@ impl<'a> Slime<'a> { } } SlimeState::Jumping(jumping_start_frame) => { - let offset = (timer - jumping_start_frame) / 4; + let offset = (timer - jumping_start_frame) as usize / 4; if timer == jumping_start_frame + 1 { sfx_player.slime_jump(); @@ -188,12 +204,10 @@ impl<'a> Slime<'a> { self.enemy_info.entity.velocity = (0, 0).into(); self.state = SlimeState::Idle; } else { - let sprite_offset = if offset >= 4 { 7 - offset } else { offset }; + let frame = SLIME_JUMP.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SLIME_JUMP_START + (sprite_offset * 4) as u16); + self.enemy_info.entity.sprite.set_sprite(sprite); } if player_has_collided { @@ -209,17 +223,17 @@ impl<'a> Slime<'a> { sfx_player.slime_death(); } - let offset = (timer - dying_start_frame) / 4; + let offset = (timer - dying_start_frame) as usize / 4; self.enemy_info.entity.velocity = (0, 0).into(); if offset >= 4 { return UpdateState::Remove; } - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SLIME_SPLAT_START + (offset * 4) as u16); + let frame = SLIME_SPLAT.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); } } @@ -247,14 +261,12 @@ pub struct Snail<'a> { } impl<'a> Snail<'a> { - fn new(object: &'a ObjectControl, start_pos: Vector2D) -> Self { + fn new(object: &'a ObjectController, start_pos: Vector2D) -> Self { let mut snail = Snail { enemy_info: EnemyInfo::new(object, start_pos, (16u16, 16u16).into()), state: SnailState::Idle(0), }; - snail.enemy_info.entity.sprite.set_sprite_size(Size::S16x16); - snail } @@ -264,6 +276,7 @@ impl<'a> Snail<'a> { fn update( &mut self, + controller: &'a ObjectController, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -288,10 +301,10 @@ impl<'a> Snail<'a> { } } - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SNAIL_IDLE_START); + let frame = SNAIL_IDLE.get_animation_sprite(0); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); if player_has_collided { if hat_state != HatState::WizardTowards { return UpdateState::KillPlayer; @@ -301,17 +314,17 @@ impl<'a> Snail<'a> { } } SnailState::Emerging(time) => { - let offset = (timer - time) / 4; + let offset = (timer - time) as usize / 4; if offset >= 5 { self.state = SnailState::Moving(timer); } self.enemy_info.entity.velocity = (0, 0).into(); - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SNAIL_EMERGE_START + (offset * 4) as u16); + let frame = SNAIL_EMERGE.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); if player_has_collided { if hat_state != HatState::WizardTowards { @@ -328,12 +341,12 @@ impl<'a> Snail<'a> { sfx_player.snail_retreat(); } - let offset = (timer - time) / 8 % 2; + let offset = (timer - time) as usize / 8; - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SNAIL_MOVE + (offset * 4) as u16); + let frame = SNAIL_MOVE.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); if timer % 32 == 0 { let x_vel: FixedNumberType = @@ -358,16 +371,16 @@ impl<'a> Snail<'a> { } } SnailState::Retreating(time) => { - let offset = 5 - (timer - time) / 4; + let offset = 5 - (timer - time) as usize / 4; if offset == 0 { self.state = SnailState::Idle(timer); } - self.enemy_info - .entity - .sprite - .set_tile_id(object_tiles::SNAIL_EMERGE_START + (offset * 4) as u16); + let frame = SNAIL_EMERGE.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); self.enemy_info.entity.velocity = (0, 0).into(); if player_has_collided { @@ -383,18 +396,20 @@ impl<'a> Snail<'a> { sfx_player.snail_death(); } - let offset = (timer - time) / 4; - let tile_id = if offset < 5 { - object_tiles::SNAIL_EMERGE_START + ((5 - offset) * 4) as u16 + let offset = (timer - time) as usize / 4; + let frame = if offset < 5 { + SNAIL_EMERGE.get_animation_sprite(5 - offset) } else if offset == 5 { - object_tiles::SNAIL_IDLE_START + SNAIL_IDLE.get_animation_sprite(0) } else if offset < 5 + 7 { - object_tiles::SNAIL_DEATH_START + ((offset - 5) * 4) as u16 + SNAIL_DEATH.get_animation_sprite(offset - 5) } else { return UpdateState::Remove; }; - self.enemy_info.entity.sprite.set_tile_id(tile_id); + let sprite = controller.get_sprite(frame).unwrap(); + + self.enemy_info.entity.sprite.set_sprite(sprite); self.enemy_info.entity.velocity = (0, 0).into(); } } diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 14f0e54c..158fc66b 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -5,7 +5,7 @@ extern crate alloc; use agb::{ display::{ - object::{ObjectControl, ObjectStandard, Size}, + object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, tiled::{ InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, }, @@ -33,27 +33,6 @@ pub struct Level { start_pos: (i32, i32), } -mod object_tiles { - pub const WIZARD_TILE_START: u16 = 0; - pub const WIZARD_JUMP: u16 = 4 * 4; - pub const WIZARD_FALL_START: u16 = 5 * 4; - - pub const HAT_TILE_START: u16 = 9 * 4; - pub const HAT_TILE_START_SECOND: u16 = 28 * 4; - pub const HAT_TILE_START_THIRD: u16 = 38 * 4; - - pub const SLIME_IDLE_START: u16 = 19 * 4; - pub const SLIME_JUMP_START: u16 = 20 * 4; - pub const SLIME_SPLAT_START: u16 = 24 * 4; - - pub const SNAIL_IDLE_START: u16 = 48 * 4; - pub const SNAIL_EMERGE_START: u16 = 49 * 4; - pub const SNAIL_MOVE: u16 = 54 * 4; - pub const SNAIL_DEATH_START: u16 = 56 * 4; -} - -agb::include_gfx!("gfx/object_sheet.toml"); - mod map_tiles { use super::Level; @@ -118,18 +97,30 @@ mod map_tiles { agb::include_gfx!("gfx/tile_sheet.toml"); +const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); +const TAG_MAP: &TagMap = GRAPHICS.tags(); + +const WALKING: &Tag = TAG_MAP.get("Walking"); +const JUMPING: &Tag = TAG_MAP.get("Jumping"); +const FALLING: &Tag = TAG_MAP.get("Falling"); +const PLAYER_DEATH: &Tag = TAG_MAP.get("Player Death"); +const HAT_SPIN_1: &Tag = TAG_MAP.get("HatSpin"); +const HAT_SPIN_2: &Tag = TAG_MAP.get("HatSpin2"); +const HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3"); + type FixedNumberType = FixedNum<10>; pub struct Entity<'a> { - sprite: ObjectStandard<'a>, + sprite: Object<'a, 'a>, position: Vector2D, velocity: Vector2D, collision_mask: Vector2D, } impl<'a> Entity<'a> { - pub fn new(object: &'a ObjectControl, collision_mask: Vector2D) -> Self { - let mut sprite = object.get_object_standard(); + pub fn new(object: &'a ObjectController, collision_mask: Vector2D) -> Self { + let dummy_sprite = object.get_sprite(WALKING.get_sprite(0)).unwrap(); + let mut sprite = object.get_object(dummy_sprite).unwrap(); sprite.set_priority(Priority::P1); Entity { sprite, @@ -352,14 +343,16 @@ fn ping_pong(i: i32, n: i32) -> i32 { } impl<'a> Player<'a> { - fn new(controller: &'a ObjectControl, start_position: Vector2D) -> Self { - let mut hat = Entity::new(controller, (6_u16, 6_u16).into()); + fn new(controller: &'a ObjectController, start_position: Vector2D) -> Self { let mut wizard = Entity::new(controller, (6_u16, 14_u16).into()); + let mut hat = Entity::new(controller, (6_u16, 6_u16).into()); + + wizard + .sprite + .set_sprite(controller.get_sprite(HAT_SPIN_1.get_sprite(0)).unwrap()); + hat.sprite + .set_sprite(controller.get_sprite(HAT_SPIN_1.get_sprite(0)).unwrap()); - wizard.sprite.set_tile_id(object_tiles::WIZARD_TILE_START); - hat.sprite.set_tile_id(object_tiles::HAT_TILE_START); - wizard.sprite.set_sprite_size(Size::S16x16); - hat.sprite.set_sprite_size(Size::S16x16); wizard.sprite.show(); hat.sprite.show(); @@ -385,6 +378,7 @@ impl<'a> Player<'a> { fn update_frame( &mut self, input: &ButtonController, + controller: &'a ObjectController, timer: i32, level: &Level, enemies: &[enemies::Enemy], @@ -459,23 +453,27 @@ impl<'a> Player<'a> { self.wizard.velocity = self.wizard.update_position(level); if self.wizard.velocity.x.abs() > 0.into() { - let offset = (ping_pong(timer / 16, 4)) as u16; + let offset = (ping_pong(timer / 16, 4)) as usize; self.wizard_frame = offset as u8; - self.wizard - .sprite - .set_tile_id(object_tiles::WIZARD_TILE_START + offset * 4); + let frame = WALKING.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.wizard.sprite.set_sprite(sprite); } if self.wizard.velocity.y < -FixedNumberType::new(1) / 16 { // going up self.wizard_frame = 5; - self.wizard.sprite.set_tile_id(object_tiles::WIZARD_JUMP); + let frame = JUMPING.get_animation_sprite(0); + let sprite = controller.get_sprite(frame).unwrap(); + + self.wizard.sprite.set_sprite(sprite); } else if self.wizard.velocity.y > FixedNumberType::new(1) / 16 { // going down let offset = if self.wizard.velocity.y * 2 > 3.into() { - ((timer / 4) % 4) as u16 + (timer / 4) as usize } else { // Don't flap beard unless going quickly 0 @@ -483,9 +481,10 @@ impl<'a> Player<'a> { self.wizard_frame = 0; - self.wizard - .sprite - .set_tile_id(object_tiles::WIZARD_FALL_START + offset * 4); + let frame = FALLING.get_animation_sprite(offset); + let sprite = controller.get_sprite(frame).unwrap(); + + self.wizard.sprite.set_sprite(sprite); } if input.x_tri() != agb::input::Tri::Zero { @@ -494,19 +493,23 @@ impl<'a> Player<'a> { } let hat_base_tile = match self.num_recalls { - 0 => object_tiles::HAT_TILE_START, - 1 => object_tiles::HAT_TILE_START_SECOND, - _ => object_tiles::HAT_TILE_START_THIRD, + 0 => HAT_SPIN_1, + 1 => HAT_SPIN_2, + _ => HAT_SPIN_3, }; match self.facing { agb::input::Tri::Negative => { self.wizard.sprite.set_hflip(true); - self.hat.sprite.set_tile_id(hat_base_tile + 4 * 5); + self.hat + .sprite + .set_sprite(controller.get_sprite(hat_base_tile.get_sprite(5)).unwrap()); } agb::input::Tri::Positive => { self.wizard.sprite.set_hflip(false); - self.hat.sprite.set_tile_id(hat_base_tile); + self.hat + .sprite + .set_sprite(controller.get_sprite(hat_base_tile.get_sprite(0)).unwrap()); } _ => {} } @@ -535,11 +538,13 @@ impl<'a> Player<'a> { _ => 4, }; - let hat_sprite_offset = timer / hat_sprite_divider % 10; + let hat_sprite_offset = (timer / hat_sprite_divider) as usize; - self.hat - .sprite - .set_tile_id(hat_base_tile + (hat_sprite_offset * 4) as u16); + self.hat.sprite.set_sprite( + controller + .get_sprite(hat_base_tile.get_animation_sprite(hat_sprite_offset)) + .unwrap(), + ); if self.hat_slow_counter < 30 && self.hat.velocity.magnitude() < 2.into() { self.hat.velocity = (0, 0).into(); @@ -570,9 +575,11 @@ impl<'a> Player<'a> { self.hat.position = self.wizard.position - hat_resting_position; } HatState::WizardTowards => { - self.hat - .sprite - .set_tile_id(hat_base_tile + 4 * (timer / 2 % 10) as u16); + self.hat.sprite.set_sprite( + controller + .get_sprite(hat_base_tile.get_animation_sprite(timer as usize / 2)) + .unwrap(), + ); let distance_vector = self.hat.position - self.wizard.position + hat_resting_position; let distance = distance_vector.magnitude(); @@ -609,7 +616,7 @@ enum UpdateState { impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn open_level( level: &'a Level, - object_control: &'a ObjectControl, + object_control: &'a ObjectController, background: &'a mut InfiniteScrolledMap<'b>, foreground: &'a mut InfiniteScrolledMap<'b>, input: ButtonController, @@ -670,15 +677,15 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { self.player.wizard.sprite.set_priority(Priority::P0); } - fn dead_update(&mut self) -> bool { + fn dead_update(&mut self, controller: &'a ObjectController) -> bool { self.timer += 1; + let frame = PLAYER_DEATH.get_animation_sprite(self.timer as usize / 8); + let sprite = controller.get_sprite(frame).unwrap(); + self.player.wizard.velocity += (0.into(), FixedNumberType::new(1) / 32).into(); self.player.wizard.position += self.player.wizard.velocity; - self.player - .wizard - .sprite - .set_tile_id((self.timer / 8 % 2 * 4 + 63 * 4) as u16); + self.player.wizard.sprite.set_sprite(sprite); self.player.wizard.commit_position(self.background.position); @@ -689,6 +696,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { &mut self, sfx_player: &mut sfx::SfxPlayer, vram: &mut VRamManager, + controller: &'a ObjectController, ) -> UpdateState { self.timer += 1; self.input.update(); @@ -697,6 +705,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { self.player.update_frame( &self.input, + controller, self.timer, self.background.level, &self.enemies, @@ -705,6 +714,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { for enemy in self.enemies.iter_mut() { match enemy.update( + controller, self.background.level, self.player.wizard.position, self.player.hat_state, @@ -811,11 +821,6 @@ fn main(mut agb: agb::Gba) -> ! { let mut timer_controller = agb.timers.timers(); let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); - object.set_sprite_palettes(object_sheet::object_sheet.palettes); - object.set_sprite_tilemap(object_sheet::object_sheet.tiles); - - object.enable(); - mixer.enable(); let mut music_box = sfx::MusicBox::new(); @@ -913,13 +918,15 @@ fn main(mut agb: agb::Gba) -> ! { world_display.hide(); loop { - match level - .update_frame(&mut sfx::SfxPlayer::new(&mut mixer, &music_box), &mut vram) - { + match level.update_frame( + &mut sfx::SfxPlayer::new(&mut mixer, &music_box), + &mut vram, + &object, + ) { UpdateState::Normal => {} UpdateState::Dead => { level.dead_start(); - while level.dead_update() { + while level.dead_update(&object) { music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index cbb2dcc7..b0de5db9 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -24,6 +24,9 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", + "modular-bitfield", + "rustc-hash", ] [[package]] @@ -37,6 +40,7 @@ dependencies = [ name = "agb_image_converter" version = "0.6.0" dependencies = [ + "asefile", "image", "proc-macro2", "quote", @@ -51,7 +55,6 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "rand", "syn", ] @@ -65,6 +68,31 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "asefile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5f7de918fd4cb18249819fc4bd27f6a5dbfbc9dcb271727f27dacf17ce880" +dependencies = [ + "bitflags", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -133,11 +161,24 @@ dependencies = [ [[package]] name = "deflate" -version = "1.0.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", ] [[package]] @@ -160,6 +201,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -168,9 +218,9 @@ checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" [[package]] name = "image" -version = "0.24.1" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", @@ -200,14 +250,60 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "log" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + [[package]] name = "num-integer" version = "0.1.44" @@ -231,9 +327,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -250,23 +346,23 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.5" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -285,42 +381,18 @@ dependencies = [ "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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - [[package]] name = "rle-decode-fast" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "serde" version = "1.0.136" @@ -341,6 +413,12 @@ dependencies = [ "syn", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.86" @@ -394,6 +472,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/examples/the-purple-night/gfx/boss.aseprite b/examples/the-purple-night/gfx/boss.aseprite index 57487ec7..935c9784 100644 Binary files a/examples/the-purple-night/gfx/boss.aseprite and b/examples/the-purple-night/gfx/boss.aseprite differ diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 462ad01c..59640239 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -14,7 +14,7 @@ use rng::get_random; use agb::{ display::{ - object::{ObjectControl, ObjectStandard}, + object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, Priority, HEIGHT, WIDTH, }, @@ -25,7 +25,33 @@ use agb::{ use generational_arena::Arena; use sfx::Sfx; -agb::include_gfx!("gfx/objects.toml"); +const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite"); +const TAG_MAP: &TagMap = GRAPHICS.tags(); + +const LONGSWORD_IDLE: &Tag = TAG_MAP.get("Idle - longsword"); +const LONGSWORD_WALK: &Tag = TAG_MAP.get("Walk - longsword"); +const LONGSWORD_JUMP: &Tag = TAG_MAP.get("Jump - longsword"); +const LONGSWORD_ATTACK: &Tag = TAG_MAP.get("Attack - longsword"); +const LONGSWORD_JUMP_ATTACK: &Tag = TAG_MAP.get("Jump attack - longsword"); + +const SHORTSWORD_IDLE: &Tag = TAG_MAP.get("Idle - shortsword"); +const SHORTSWORD_WALK: &Tag = TAG_MAP.get("Walk - shortsword"); +const SHORTSWORD_JUMP: &Tag = TAG_MAP.get("jump - shortsword"); +const SHORTSWORD_ATTACK: &Tag = TAG_MAP.get("attack - shortsword"); +const SHORTSWORD_JUMP_ATTACK: &Tag = TAG_MAP.get("jump attack - shortsword"); + +const KNIFE_IDLE: &Tag = TAG_MAP.get("idle - knife"); +const KNIFE_WALK: &Tag = TAG_MAP.get("walk - knife"); +const KNIFE_JUMP: &Tag = TAG_MAP.get("jump - knife"); +const KNIFE_ATTACK: &Tag = TAG_MAP.get("attack - knife"); +const KNIFE_JUMP_ATTACK: &Tag = TAG_MAP.get("jump attack - knife"); + +const SWORDLESS_IDLE: &Tag = TAG_MAP.get("idle swordless"); +const SWORDLESS_WALK: &Tag = TAG_MAP.get("walk swordless"); +const SWORDLESS_JUMP: &Tag = TAG_MAP.get("jump swordless"); +const SWORDLESS_ATTACK: &Tag = KNIFE_ATTACK; +const SWORDLESS_JUMP_ATTACK: &Tag = KNIFE_JUMP_ATTACK; + agb::include_gfx!("gfx/background.toml"); type Number = FixedNum<8>; @@ -126,7 +152,7 @@ impl<'a> Level<'a> { } struct Entity<'a> { - sprite: ObjectStandard<'a>, + sprite: Object<'a, 'a>, position: Vector2D, velocity: Vector2D, collision_mask: Rect, @@ -134,8 +160,11 @@ struct Entity<'a> { } impl<'a> Entity<'a> { - fn new(object_controller: &'a ObjectControl, collision_mask: Rect) -> Self { - let mut sprite = object_controller.get_object_standard(); + fn new(object_controller: &'a ObjectController, collision_mask: Rect) -> Self { + let s = object_controller + .get_sprite(LONGSWORD_IDLE.get_sprite(0)) + .unwrap(); + let mut sprite = object_controller.get_object(s).unwrap(); sprite.set_priority(Priority::P1); Entity { sprite, @@ -320,34 +349,30 @@ impl SwordState { SwordState::Swordless => Number::new(6) / 256, } } - fn idle_animation(self, counter: &mut u16) -> u16 { - if *counter >= 4 * 8 { - *counter = 0; - } + fn idle_animation(self, counter: u16) -> &'static Sprite { + let counter = counter as usize; match self { - SwordState::LongSword => (*counter / 8) * 4, - SwordState::ShortSword => (41 + *counter / 8) * 4, - SwordState::Dagger => (96 + *counter / 8) * 4, - SwordState::Swordless => (154 + *counter / 8) * 4, + SwordState::LongSword => LONGSWORD_IDLE.get_animation_sprite(counter / 8), + SwordState::ShortSword => SHORTSWORD_IDLE.get_animation_sprite(counter / 8), + SwordState::Dagger => KNIFE_IDLE.get_animation_sprite(counter / 8), + SwordState::Swordless => SWORDLESS_IDLE.get_animation_sprite(counter / 8), } } - fn jump_offset(self) -> u16 { + fn jump_tag(self) -> &'static Tag { match self { - SwordState::LongSword => 10, - SwordState::ShortSword => 51, - SwordState::Dagger => 106, - SwordState::Swordless => 164, + SwordState::LongSword => LONGSWORD_JUMP, + SwordState::ShortSword => SHORTSWORD_JUMP, + SwordState::Dagger => KNIFE_JUMP, + SwordState::Swordless => SWORDLESS_JUMP, } } - fn walk_animation(self, counter: &mut u16) -> u16 { - if *counter >= 6 * 4 { - *counter = 0; - } + fn walk_animation(self, counter: u16) -> &'static Sprite { + let counter = counter as usize; match self { - SwordState::LongSword => (4 + *counter / 4) * 4, - SwordState::ShortSword => (45 + *counter / 4) * 4, - SwordState::Dagger => (100 + *counter / 4) * 4, - SwordState::Swordless => (158 + *counter / 4) * 4, + SwordState::LongSword => LONGSWORD_WALK.get_animation_sprite(counter / 4), + SwordState::ShortSword => SHORTSWORD_WALK.get_animation_sprite(counter / 4), + SwordState::Dagger => KNIFE_WALK.get_animation_sprite(counter / 4), + SwordState::Swordless => SWORDLESS_WALK.get_animation_sprite(counter / 4), } } fn attack_duration(self) -> u16 { @@ -374,20 +399,20 @@ impl SwordState { SwordState::Swordless => (self.attack_duration() - timer) / 8, } } + fn jump_attack_tag(self) -> &'static Tag { + match self { + SwordState::LongSword => LONGSWORD_JUMP_ATTACK, + SwordState::ShortSword => SHORTSWORD_JUMP_ATTACK, + SwordState::Dagger => KNIFE_JUMP_ATTACK, + SwordState::Swordless => SWORDLESS_JUMP_ATTACK, + } + } fn jump_attack_frame(self, timer: u16) -> u16 { (self.jump_attack_duration().saturating_sub(timer)) / 8 } fn hold_frame(self) -> u16 { 7 } - fn jump_attack_hold_frame(self) -> u16 { - match self { - SwordState::LongSword => 13, - SwordState::ShortSword => 54, - SwordState::Dagger => 109, - SwordState::Swordless => 0, - } - } fn cooldown_time(self) -> u16 { match self { @@ -397,26 +422,15 @@ impl SwordState { SwordState::Swordless => 0, } } - fn to_sprite_id(self, frame: u16) -> u16 { + fn attack_tag(self) -> &'static Tag { match self { - SwordState::LongSword => (16 + frame) * 4, - SwordState::ShortSword => (57 + frame) * 4, - SwordState::Dagger => (112 + frame) * 4, - SwordState::Swordless => 0, - } - } - fn to_jump_sprite_id(self, frame: u16) -> u16 { - if frame == self.jump_attack_hold_frame() { - frame * 4 - } else { - match self { - SwordState::LongSword => (24 + frame) * 4, - SwordState::ShortSword => (65 + frame) * 4, - SwordState::Dagger => (120 + frame) * 4, - SwordState::Swordless => 0, - } + SwordState::LongSword => LONGSWORD_ATTACK, + SwordState::ShortSword => SHORTSWORD_ATTACK, + SwordState::Dagger => KNIFE_ATTACK, + SwordState::Swordless => SWORDLESS_ATTACK, } } + fn fudge(self, frame: u16) -> i32 { match self { SwordState::LongSword => long_sword_fudge(frame), @@ -519,15 +533,15 @@ struct Player<'a> { } impl<'a> Player<'a> { - fn new(object_controller: &'a ObjectControl) -> Player { + fn new(object_controller: &'a ObjectController) -> Player { let mut entity = Entity::new( object_controller, Rect::new((0_u16, 0_u16).into(), (4_u16, 12_u16).into()), ); - entity - .sprite - .set_sprite_size(agb::display::object::Size::S16x16); - entity.sprite.set_tile_id(0); + let s = object_controller + .get_sprite(LONGSWORD_IDLE.get_sprite(0)) + .unwrap(); + entity.sprite.set_sprite(s); entity.sprite.show(); entity.position = (144, 0).into(); entity.sprite.commit(); @@ -548,6 +562,7 @@ impl<'a> Player<'a> { fn update( &mut self, + controller: &'a ObjectController, buttons: &ButtonController, level: &Level, sfx: &mut sfx::Sfx, @@ -579,13 +594,15 @@ impl<'a> Player<'a> { self.entity.sprite.set_hflip(self.facing == Tri::Negative); self.entity.velocity.x += self.sword.ground_walk_force() * x as i32; if self.entity.velocity.x.abs() > Number::new(1) / 10 { - self.entity - .sprite - .set_tile_id(self.sword.walk_animation(&mut self.sprite_offset)); + let sprite = controller + .get_sprite(self.sword.walk_animation(self.sprite_offset)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); } else { - self.entity - .sprite - .set_tile_id(self.sword.idle_animation(&mut self.sprite_offset)); + let sprite = controller + .get_sprite(self.sword.idle_animation(self.sprite_offset)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); } if b_press && self.sword != SwordState::Swordless { @@ -603,9 +620,11 @@ impl<'a> Player<'a> { *a -= 1; let frame = self.sword.attack_frame(*a); self.fudge_factor.x = self.sword.fudge(frame) * self.facing as i32; - self.entity - .sprite - .set_tile_id(self.sword.to_sprite_id(frame)); + let tag = self.sword.attack_tag(); + let sprite = controller + .get_sprite(tag.get_animation_sprite(frame as usize)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); hurtbox = self.sword.ground_attack_hurtbox(frame); @@ -617,9 +636,11 @@ impl<'a> Player<'a> { *a -= 1; let frame = self.sword.hold_frame(); self.fudge_factor.x = self.sword.fudge(frame) * self.facing as i32; - self.entity - .sprite - .set_tile_id(self.sword.to_sprite_id(frame)); + let tag = self.sword.attack_tag(); + let sprite = controller + .get_sprite(tag.get_animation_sprite(frame as usize)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); if *a == 0 { self.attack_timer = AttackTimer::Idle; } @@ -631,7 +652,7 @@ impl<'a> Player<'a> { match &mut self.attack_timer { AttackTimer::Idle => { - let sprite = if self.sprite_offset < 3 * 4 { + let frame = if self.sprite_offset < 3 * 4 { self.sprite_offset / 4 } else if self.entity.velocity.y.abs() < Number::new(1) / 5 { 3 @@ -642,9 +663,11 @@ impl<'a> Player<'a> { } else { 2 }; - self.entity - .sprite - .set_tile_id((sprite + self.sword.jump_offset()) * 4); + let tag = self.sword.jump_tag(); + let sprite = controller + .get_sprite(tag.get_animation_sprite(frame as usize)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); if x != Tri::Zero { self.facing = x; @@ -664,9 +687,11 @@ impl<'a> Player<'a> { AttackTimer::Attack(a) => { *a -= 1; let frame = self.sword.jump_attack_frame(*a); - self.entity - .sprite - .set_tile_id(self.sword.to_jump_sprite_id(frame)); + let tag = self.sword.jump_attack_tag(); + let sprite = controller + .get_sprite(tag.get_animation_sprite(frame as usize)) + .unwrap(); + self.entity.sprite.set_sprite(sprite); hurtbox = self.sword.air_attack_hurtbox(frame); @@ -798,9 +823,10 @@ impl BatData { } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, level: &Level, sfx: &mut sfx::Sfx, @@ -813,6 +839,8 @@ impl BatData { .unwrap_or(false); let should_damage = entity.collider().touches(player.entity.collider()); + const BAT_IDLE: &Tag = TAG_MAP.get("bat"); + match &mut self.bat_state { BatState::Idle => { self.sprite_offset += 1; @@ -824,7 +852,10 @@ impl BatData { sfx.bat_flap(); } - entity.sprite.set_tile_id((78 + self.sprite_offset / 8) * 4); + let sprite = BAT_IDLE.get_sprite(self.sprite_offset as usize / 8); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); if (entity.position - player.entity.position).manhattan_distance() < 50.into() { self.bat_state = BatState::Chasing(300); @@ -855,7 +886,11 @@ impl BatData { if self.sprite_offset >= 9 * 2 { self.sprite_offset = 0; } - entity.sprite.set_tile_id((78 + self.sprite_offset / 2) * 4); + + let sprite = BAT_IDLE.get_sprite(self.sprite_offset as usize / 2); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); if self.sprite_offset == 2 * 5 { sfx.bat_flap(); @@ -878,7 +913,12 @@ impl BatData { } } BatState::Dead => { - entity.sprite.set_tile_id(87 * 4); + const BAT_DEAD: &Tag = TAG_MAP.get("bat dead"); + let sprite = BAT_DEAD.get_sprite(0); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); + let gravity: Number = 1.into(); let gravity = gravity / 16; entity.velocity.x = 0.into(); @@ -916,9 +956,10 @@ impl SlimeData { } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, level: &Level, sfx: &mut sfx::Sfx, @@ -939,9 +980,12 @@ impl SlimeData { self.sprite_offset = 0; } - entity - .sprite - .set_tile_id((29 + self.sprite_offset / 16) * 4); + const IDLE: &Tag = TAG_MAP.get("slime idle"); + + let sprite = IDLE.get_sprite(self.sprite_offset as usize / 16); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); if (player.entity.position - entity.position).manhattan_distance() < 40.into() { let direction = match player.entity.position.x.cmp(&entity.position.x) { @@ -976,7 +1020,12 @@ impl SlimeData { sfx.slime_boing(); } - entity.sprite.set_tile_id((frame + 31) * 4); + const CHASE: &Tag = TAG_MAP.get("Slime jump"); + + let sprite = CHASE.get_sprite(frame as usize); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); entity.velocity.x = match frame { 2 | 3 | 4 => (Number::new(1) / 5) * Number::new(*direction as i32), @@ -1002,7 +1051,11 @@ impl SlimeData { } SlimeState::Dead(count) => { if *count < 5 * 4 { - entity.sprite.set_tile_id((36 + *count / 4) * 4); + const DEATH: &Tag = TAG_MAP.get("Slime death"); + let sprite = DEATH.get_sprite(*count as usize / 4); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); *count += 1; } else { return UpdateInstruction::Remove; @@ -1032,9 +1085,10 @@ impl MiniFlameData { } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, _level: &Level, sfx: &mut sfx::Sfx, @@ -1050,6 +1104,8 @@ impl MiniFlameData { self.sprite_offset += 1; + const ANGRY: &Tag = TAG_MAP.get("angry boss"); + match &mut self.state { MiniFlameState::Idle(frames) => { *frames -= 1; @@ -1064,13 +1120,9 @@ impl MiniFlameData { entity.velocity = resulting_direction.normalise() * Number::new(2); } } else { - if self.sprite_offset >= 12 * 8 { - self.sprite_offset = 0; - } - - entity - .sprite - .set_tile_id((137 + self.sprite_offset / 8) * 4); + let sprite = ANGRY.get_animation_sprite(self.sprite_offset as usize / 8); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); entity.velocity = (0.into(), Number::new(-1) / Number::new(4)).into(); } @@ -1112,17 +1164,13 @@ impl MiniFlameData { instruction = UpdateInstruction::DamagePlayer; } - if self.sprite_offset >= 12 * 2 { - self.sprite_offset = 0; - } - if entity.velocity.manhattan_distance() < Number::new(1) / Number::new(4) { self.state = MiniFlameState::Idle(90); } - entity - .sprite - .set_tile_id((137 + self.sprite_offset / 2) * 4); + let sprite = ANGRY.get_animation_sprite(self.sprite_offset as usize / 2); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); } MiniFlameState::Dead => { entity.velocity = (0, 0).into(); @@ -1130,9 +1178,11 @@ impl MiniFlameData { instruction = UpdateInstruction::Remove; } - entity - .sprite - .set_tile_id((148 + self.sprite_offset / 12) * 4); + const DEATH: &Tag = TAG_MAP.get("angry boss dead"); + + let sprite = DEATH.get_animation_sprite(self.sprite_offset as usize / 12); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); self.sprite_offset += 1; } @@ -1164,9 +1214,10 @@ impl EmuData { } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, level: &Level, sfx: &mut sfx::Sfx, @@ -1188,9 +1239,11 @@ impl EmuData { self.sprite_offset = 0; } - entity - .sprite - .set_tile_id((170 + self.sprite_offset / 16) * 4); + const IDLE: &Tag = TAG_MAP.get("emu - idle"); + + let sprite = IDLE.get_sprite(self.sprite_offset as usize / 16); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); if (entity.position.y - player.entity.position.y).abs() < 10.into() { let velocity = Number::new(1) @@ -1233,9 +1286,11 @@ impl EmuData { sfx.emu_step(); } - entity - .sprite - .set_tile_id((173 + self.sprite_offset / 2) * 4); + const WALK: &Tag = TAG_MAP.get("emu-walk"); + + let sprite = WALK.get_sprite(self.sprite_offset as usize / 2); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); let gravity: Number = 1.into(); let gravity = gravity / 16; @@ -1286,9 +1341,12 @@ impl EmuData { instruction = UpdateInstruction::Remove; } - entity - .sprite - .set_tile_id((177 + self.sprite_offset / 4) * 4); + const DEATH: &Tag = TAG_MAP.get("emu - die"); + + let sprite = DEATH.get_animation_sprite(self.sprite_offset as usize / 4); + let sprite = controller.get_sprite(sprite).unwrap(); + entity.sprite.set_sprite(sprite); + self.sprite_offset += 1; } } @@ -1316,27 +1374,32 @@ impl EnemyData { } } - fn tile_id(&self) -> u16 { + fn sprite(&self) -> &'static Sprite { + const SLIME: &Tag = TAG_MAP.get("slime idle"); + const BAT: &Tag = TAG_MAP.get("bat"); + const MINI_FLAME: &Tag = TAG_MAP.get("angry boss"); + const EMU: &Tag = TAG_MAP.get("emu - idle"); match self { - EnemyData::Slime(_) => 29, - EnemyData::Bat(_) => 78, - EnemyData::MiniFlame(_) => 137, - EnemyData::Emu(_) => 170, + EnemyData::Slime(_) => SLIME.get_sprite(0), + EnemyData::Bat(_) => BAT.get_sprite(0), + EnemyData::MiniFlame(_) => MINI_FLAME.get_sprite(0), + EnemyData::Emu(_) => EMU.get_sprite(0), } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, level: &Level, sfx: &mut sfx::Sfx, ) -> UpdateInstruction { match self { - EnemyData::Slime(data) => data.update(entity, player, level, sfx), - EnemyData::Bat(data) => data.update(entity, player, level, sfx), - EnemyData::MiniFlame(data) => data.update(entity, player, level, sfx), - EnemyData::Emu(data) => data.update(entity, player, level, sfx), + EnemyData::Slime(data) => data.update(controller, entity, player, level, sfx), + EnemyData::Bat(data) => data.update(controller, entity, player, level, sfx), + EnemyData::MiniFlame(data) => data.update(controller, entity, player, level, sfx), + EnemyData::Emu(data) => data.update(controller, entity, player, level, sfx), } } } @@ -1347,13 +1410,13 @@ struct Enemy<'a> { } impl<'a> Enemy<'a> { - fn new(object_controller: &'a ObjectControl, enemy_data: EnemyData) -> Self { + fn new(object_controller: &'a ObjectController, enemy_data: EnemyData) -> Self { let mut entity = Entity::new(object_controller, enemy_data.collision_mask()); - entity - .sprite - .set_sprite_size(agb::display::object::Size::S16x16); - entity.sprite.set_tile_id(enemy_data.tile_id()); + let sprite = enemy_data.sprite(); + let sprite = object_controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); entity.sprite.show(); entity.sprite.commit(); @@ -1361,8 +1424,15 @@ impl<'a> Enemy<'a> { Self { entity, enemy_data } } - fn update(&mut self, player: &Player, level: &Level, sfx: &mut sfx::Sfx) -> UpdateInstruction { - self.enemy_data.update(&mut self.entity, player, level, sfx) + fn update( + &mut self, + controller: &'a ObjectController, + player: &Player, + level: &Level, + sfx: &mut sfx::Sfx, + ) -> UpdateInstruction { + self.enemy_data + .update(controller, &mut self.entity, player, level, sfx) } } @@ -1393,9 +1463,10 @@ impl ParticleData { } } - fn update( + fn update<'a>( &mut self, - entity: &mut Entity, + controller: &'a ObjectController, + entity: &mut Entity<'a>, player: &Player, _level: &Level, ) -> UpdateInstruction { @@ -1405,7 +1476,11 @@ impl ParticleData { return UpdateInstruction::Remove; } - entity.sprite.set_tile_id((70 + *frame / 3) * 4); + const DUST: &Tag = TAG_MAP.get("dust"); + let sprite = DUST.get_sprite(*frame as usize / 3); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); *frame += 1; UpdateInstruction::None @@ -1415,7 +1490,11 @@ impl ParticleData { return UpdateInstruction::Remove; // have played the animation 6 times } - entity.sprite.set_tile_id((88 + (*frame / 3) % 8) * 4); + const HEALTH: &Tag = TAG_MAP.get("Heath"); + let sprite = HEALTH.get_animation_sprite(*frame as usize / 3); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); if *frame < 8 * 3 * 3 { entity.velocity.y = Number::new(-1) / 2; @@ -1437,7 +1516,11 @@ impl ParticleData { UpdateInstruction::None } ParticleData::BossHealer(frame, target) => { - entity.sprite.set_tile_id((88 + (*frame / 3) % 8) * 4); + const HEALTH: &Tag = TAG_MAP.get("Heath"); + let sprite = HEALTH.get_animation_sprite(*frame as usize / 3); + let sprite = controller.get_sprite(sprite).unwrap(); + + entity.sprite.set_sprite(sprite); if *frame < 8 * 3 * 3 { entity.velocity.y = Number::new(-1) / 2; @@ -1470,7 +1553,7 @@ struct Particle<'a> { impl<'a> Particle<'a> { fn new( - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, particle_data: ParticleData, position: Vector2D, ) -> Self { @@ -1479,11 +1562,6 @@ impl<'a> Particle<'a> { Rect::new((0u16, 0u16).into(), (0u16, 0u16).into()), ); - entity - .sprite - .set_sprite_size(agb::display::object::Size::S16x16); - entity.sprite.set_tile_id(particle_data.tile_id() * 4); - entity.sprite.show(); entity.position = position; Self { @@ -1492,8 +1570,15 @@ impl<'a> Particle<'a> { } } - fn update(&mut self, player: &Player, level: &Level) -> UpdateInstruction { - self.particle_data.update(&mut self.entity, player, level) + fn update( + &mut self, + controller: &'a ObjectController, + player: &Player, + level: &Level, + ) -> UpdateInstruction { + self.entity.sprite.show(); + self.particle_data + .update(controller, &mut self.entity, player, level) } } @@ -1514,14 +1599,14 @@ impl<'a> BossState<'a> { fn update( &mut self, enemies: &mut Arena>, - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, player: &Player, sfx: &mut sfx::Sfx, ) -> BossInstruction { match self { BossState::Active(boss) => boss.update(enemies, object_controller, player, sfx), BossState::Following(boss) => { - boss.update(player); + boss.update(object_controller, player); BossInstruction::None } BossState::NotSpawned => BossInstruction::None, @@ -1549,15 +1634,13 @@ struct FollowingBoss<'a> { } impl<'a> FollowingBoss<'a> { - fn new(object_controller: &'a ObjectControl, position: Vector2D) -> Self { + fn new(object_controller: &'a ObjectController, position: Vector2D) -> Self { let mut entity = Entity::new( object_controller, Rect::new((0_u16, 0_u16).into(), (0_u16, 0_u16).into()), ); entity.position = position; - entity - .sprite - .set_sprite_size(agb::display::object::Size::S16x16); + Self { entity, following: true, @@ -1566,11 +1649,11 @@ impl<'a> FollowingBoss<'a> { gone: false, } } - fn update(&mut self, player: &Player) { + fn update(&mut self, controller: &'a ObjectController, player: &Player) { let difference = player.entity.position - self.entity.position; self.timer += 1; - if self.to_hole { + let frame = if self.to_hole { let target: Vector2D = (17 * 8, -3 * 8).into(); let difference = target - self.entity.position; if difference.manhattan_distance() < 1.into() { @@ -1579,26 +1662,30 @@ impl<'a> FollowingBoss<'a> { self.entity.velocity = difference.normalise() * 2; } - let frame = (self.timer / 8) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4); + self.timer / 8 } else if self.timer < 120 { - let frame = (self.timer / 20) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4); + self.timer / 20 } else if self.following { self.entity.velocity = difference / 16; if difference.manhattan_distance() < 20.into() { self.following = false; } - let frame = (self.timer / 8) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4); + self.timer / 8 } else { self.entity.velocity = (0, 0).into(); if difference.manhattan_distance() > 60.into() { self.following = true; } - let frame = (self.timer / 16) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4); - } + self.timer / 16 + }; + + const BOSS: &Tag = TAG_MAP.get("happy boss"); + + let sprite = BOSS.get_animation_sprite(frame as usize); + let sprite = controller.get_sprite(sprite).unwrap(); + + self.entity.sprite.set_sprite(sprite); + self.entity.update_position_without_collision(); } @@ -1631,15 +1718,11 @@ enum BossInstruction { } impl<'a> Boss<'a> { - fn new(object_controller: &'a ObjectControl, screen_coords: Vector2D) -> Self { + fn new(object_controller: &'a ObjectController, screen_coords: Vector2D) -> Self { let mut entity = Entity::new( object_controller, Rect::new((0_u16, 0_u16).into(), (28_u16, 28_u16).into()), ); - entity - .sprite - .set_sprite_size(agb::display::object::Size::S32x32); - entity.sprite.set_palette(1); entity.position = screen_coords + (144, 136).into(); Self { entity, @@ -1654,7 +1737,7 @@ impl<'a> Boss<'a> { fn update( &mut self, enemies: &mut Arena>, - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, player: &Player, sfx: &mut sfx::Sfx, ) -> BossInstruction { @@ -1730,8 +1813,14 @@ impl<'a> Boss<'a> { BossActiveState::WaitUntilKilled => 3.into(), }; self.timer += 1; - let frame = (self.timer / animation_rate) % 12; - self.entity.sprite.set_tile_id(784 + (frame as u16) * 16); + let frame = self.timer / animation_rate; + + const BOSS: &Tag = TAG_MAP.get("Boss"); + + let sprite = BOSS.get_animation_sprite(frame as usize); + let sprite = object_controller.get_sprite(sprite).unwrap(); + + self.entity.sprite.set_sprite(sprite); self.entity.update_position_without_collision(); instruction @@ -1752,7 +1841,7 @@ impl<'a> Boss<'a> { self.entity .commit_with_size(offset + shake, (32, 32).into()); } - fn explode(&self, enemies: &mut Arena>, object_controller: &'a ObjectControl) { + fn explode(&self, enemies: &mut Arena>, object_controller: &'a ObjectController) { for _ in 0..(6 - self.health) { let x_offset: Number = Number::from_raw(get_random()).rem_euclid(2.into()) - 1; let y_offset: Number = Number::from_raw(get_random()).rem_euclid(2.into()) - 1; @@ -1826,7 +1915,7 @@ impl<'a> Game<'a> { fn advance_frame( &mut self, - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, vram: &mut VRamManager, sfx: &mut sfx::Sfx, ) -> GameStatus { @@ -1923,7 +2012,8 @@ impl<'a> Game<'a> { self.input.update(); if let UpdateInstruction::CreateParticle(data, position) = - self.player.update(&self.input, &self.level, sfx) + self.player + .update(object_controller, &self.input, &self.level, sfx) { let new_particle = Particle::new(object_controller, data, position); @@ -1937,7 +2027,7 @@ impl<'a> Game<'a> { continue; } - match enemy.update(&self.player, &self.level, sfx) { + match enemy.update(object_controller, &self.player, &self.level, sfx) { UpdateInstruction::Remove => { remove.push(idx); } @@ -1984,7 +2074,7 @@ impl<'a> Game<'a> { let mut remove = Vec::with_capacity(10); for (idx, particle) in self.particles.iter_mut() { - match particle.update(&self.player, &self.level) { + match particle.update(object_controller, &self.player, &self.level) { UpdateInstruction::Remove => remove.push(idx), UpdateInstruction::HealBossAndRemove => { sfx.sunrise(); @@ -2039,7 +2129,7 @@ impl<'a> Game<'a> { } } - fn load_enemies(&mut self, object_controller: &'a ObjectControl) { + fn load_enemies(&mut self, object_controller: &'a ObjectController) { if self.slime_load < self.level.slime_spawns.len() { for (idx, slime_spawn) in self .level @@ -2109,7 +2199,7 @@ impl<'a> Game<'a> { vram.set_background_palettes(&modified_palettes); } - fn new(object: &'a ObjectControl, level: Level<'a>, 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 { @@ -2138,16 +2228,6 @@ impl<'a> Game<'a> { } fn game_with_level(gba: &mut agb::Gba) { - { - let object = gba.display.object.get(); - object.set_sprite_palettes(&[ - objects::objects.palettes[0].clone(), - objects::boss.palettes[0].clone(), - ]); - object.set_sprite_tilemap(objects::objects.tiles); - object.set_sprite_tilemap_at_idx(8192 - objects::boss.tiles.len(), objects::boss.tiles); - } - let vblank = agb::interrupt::VBlank::get(); vblank.wait_for_vblank(); @@ -2170,8 +2250,7 @@ fn game_with_level(gba: &mut agb::Gba) { TileFormat::FourBpp, )); - let mut object = gba.display.object.get(); - object.enable(); + let object = gba.display.object.get(); let backdrop = InfiniteScrolledMap::new( background.background(Priority::P2), diff --git a/justfile b/justfile index 0229f453..82e36a06 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,7 @@ ci: && build-roms just _all-crates _test-debug just _all-crates _test-release just _all-crates _clippy + just build-book build-roms: just _build-rom "examples/the-purple-night" "PURPLENIGHT" diff --git a/mgba-test-runner/Cargo.lock b/mgba-test-runner/Cargo.lock index 689ab620..ff033e0d 100644 --- a/mgba-test-runner/Cargo.lock +++ b/mgba-test-runner/Cargo.lock @@ -86,9 +86,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -363,9 +363,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "png" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", @@ -428,9 +428,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ]