From 9dca54c9bbb02eb4c0f5c49860d6c6411502a508 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 6 Mar 2022 02:54:52 +0000 Subject: [PATCH] update to use new sprite system --- examples/the-purple-night/Cargo.lock | 170 ++++++- examples/the-purple-night/gfx/boss.aseprite | Bin 6026 -> 6027 bytes examples/the-purple-night/src/main.rs | 463 ++++++++++++-------- 3 files changed, 428 insertions(+), 205 deletions(-) diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 5566eb7e..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", @@ -64,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" @@ -132,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]] @@ -148,6 +190,26 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "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" @@ -156,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", @@ -169,6 +231,12 @@ dependencies = [ "png", ] +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + [[package]] name = "libflate" version = "0.1.27" @@ -182,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" @@ -213,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", @@ -232,15 +346,21 @@ 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]] @@ -267,6 +387,12 @@ 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" @@ -287,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" @@ -340,6 +472,18 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "xml-rs" version = "0.8.4" diff --git a/examples/the-purple-night/gfx/boss.aseprite b/examples/the-purple-night/gfx/boss.aseprite index 57487ec7f0aa6822324ddd497887bb3c79b5970c..935c9784319c97e561eb565aa1cd91d7b2e99f30 100644 GIT binary patch delta 33 pcmeCu@78DRp2%3wXt1%-osm&}@<~PoHWmh_{Nm!x?-=KZ0syM}3RM6A delta 32 ocmeCy@6u=Nn#fqssK2q%osm&(@<~PoR%V8f#PrSY8Rv)s0H9C_)&Kwi diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index ea00124f..3178947e 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -15,7 +15,7 @@ use rng::get_random; use agb::{ display::{ background::{BackgroundDistributor, BackgroundRegular}, - object::{ObjectControl, ObjectStandard}, + object::{Object, ObjectController, Sprite, Tag, TagMap}, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Rect, Vector2D}, @@ -23,7 +23,34 @@ use agb::{ }; use generational_arena::Arena; -agb::include_gfx!("gfx/objects.toml"); +const SPRITE_TAGS: (&[Sprite], &TagMap) = + agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite"); +const TAG_MAP: &TagMap = SPRITE_TAGS.1; + +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>; @@ -127,7 +154,7 @@ impl Level { } struct Entity<'a> { - sprite: ObjectStandard<'a>, + sprite: Object<'a, 'a>, position: Vector2D, velocity: Vector2D, collision_mask: Rect, @@ -135,8 +162,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, @@ -321,34 +351,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 { @@ -375,20 +401,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() - 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 { @@ -398,26 +424,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), @@ -520,15 +535,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(); @@ -549,6 +564,7 @@ impl<'a> Player<'a> { fn update( &mut self, + controller: &'a ObjectController, buttons: &ButtonController, level: &Level, sfx: &mut sfx::Sfx, @@ -580,13 +596,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 { @@ -604,9 +622,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); @@ -618,9 +638,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; } @@ -632,7 +654,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 @@ -643,9 +665,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; @@ -665,9 +689,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); @@ -799,9 +825,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, @@ -814,6 +841,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; @@ -825,7 +854,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); @@ -856,7 +888,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(); @@ -879,7 +915,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(); @@ -917,9 +958,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, @@ -940,9 +982,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) { @@ -977,7 +1022,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), @@ -1003,7 +1053,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; @@ -1033,9 +1087,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, @@ -1051,6 +1106,8 @@ impl MiniFlameData { self.sprite_offset += 1; + const ANGRY: &Tag = TAG_MAP.get("angry boss"); + match &mut self.state { MiniFlameState::Idle(frames) => { *frames -= 1; @@ -1065,13 +1122,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(); } @@ -1113,17 +1166,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(); @@ -1131,9 +1180,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; } @@ -1165,9 +1216,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, @@ -1189,9 +1241,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) @@ -1234,9 +1288,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; @@ -1287,9 +1343,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; } } @@ -1317,27 +1376,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), } } } @@ -1348,13 +1412,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(); @@ -1362,8 +1426,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) } } @@ -1394,9 +1465,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 { @@ -1406,7 +1478,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 @@ -1416,7 +1492,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; @@ -1438,7 +1518,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; @@ -1471,7 +1555,7 @@ struct Particle<'a> { impl<'a> Particle<'a> { fn new( - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, particle_data: ParticleData, position: Vector2D, ) -> Self { @@ -1480,11 +1564,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 { @@ -1493,8 +1572,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) } } @@ -1515,14 +1601,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, @@ -1550,15 +1636,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, @@ -1567,11 +1651,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() { @@ -1580,26 +1664,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(); } @@ -1632,15 +1720,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, @@ -1655,7 +1739,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 { @@ -1731,8 +1815,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 @@ -1753,7 +1843,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; @@ -1825,7 +1915,7 @@ impl<'a> Game<'a> { fn advance_frame( &mut self, - object_controller: &'a ObjectControl, + object_controller: &'a ObjectController, sfx: &mut sfx::Sfx, ) -> GameStatus { let mut state = GameStatus::Continue; @@ -1921,7 +2011,7 @@ 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); @@ -1935,7 +2025,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); } @@ -1989,7 +2079,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(); @@ -2040,7 +2130,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 @@ -2111,7 +2201,7 @@ impl<'a> Game<'a> { } fn new( - object: &'a ObjectControl, + object: &'a ObjectController, level: Level, background_distributor: &'a mut BackgroundDistributor, start_at_boss: bool, @@ -2146,16 +2236,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(); @@ -2172,8 +2252,7 @@ fn game_with_level(gba: &mut agb::Gba) { let mut background = gba.display.video.tiled0(); background.set_background_palettes(background::background.palettes); background.set_background_tilemap(0, background::background.tiles); - let mut object = gba.display.object.get(); - object.enable(); + let object = gba.display.object.get(); let mut game = Game::new( &object,