mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-22 23:26:33 +11:00
Merge pull request #209 from corwinkuiper/z-ordered-sprites
Z ordered sprites
This commit is contained in:
commit
f7ef25f3f0
6 changed files with 392 additions and 150 deletions
|
@ -20,7 +20,7 @@ enum State {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Character<'a> {
|
struct Character<'a> {
|
||||||
object: Object<'a, 'a>,
|
object: Object<'a>,
|
||||||
position: Vector2D,
|
position: Vector2D,
|
||||||
velocity: Vector2D,
|
velocity: Vector2D,
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,8 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
.object
|
.object
|
||||||
.set_y((chicken.position.y >> 8).try_into().unwrap());
|
.set_y((chicken.position.y >> 8).try_into().unwrap());
|
||||||
chicken.object.show();
|
chicken.object.show();
|
||||||
chicken.object.commit();
|
|
||||||
|
object.commit();
|
||||||
|
|
||||||
let acceleration = 1 << 4;
|
let acceleration = 1 << 4;
|
||||||
let gravity = 1 << 4;
|
let gravity = 1 << 4;
|
||||||
|
@ -133,8 +134,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
restrict_to_screen(&mut chicken);
|
restrict_to_screen(&mut chicken);
|
||||||
update_chicken_object(&mut chicken, &object, state, frame_count);
|
update_chicken_object(&mut chicken, &object, state, frame_count);
|
||||||
|
|
||||||
// Commit the chicken to vram
|
object.commit();
|
||||||
chicken.object.commit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ extern crate alloc;
|
||||||
use agb::display::object::{Graphics, ObjectController, Sprite, TagMap};
|
use agb::display::object::{Graphics, ObjectController, Sprite, TagMap};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
|
||||||
const GRAPHICS: &Graphics = agb::include_aseprite!(
|
const GRAPHICS: &Graphics = agb::include_aseprite!(
|
||||||
"../examples/the-purple-night/gfx/objects.aseprite",
|
"../examples/the-purple-night/gfx/objects.aseprite",
|
||||||
"../examples/the-purple-night/gfx/boss.aseprite"
|
"../examples/the-purple-night/gfx/boss.aseprite"
|
||||||
|
@ -49,7 +48,7 @@ fn all_sprites(gfx: &ObjectController) {
|
||||||
for (i, obj) in objs.iter_mut().enumerate() {
|
for (i, obj) in objs.iter_mut().enumerate() {
|
||||||
let this_image = (image + i * SPRITES.len() / objs_len) % SPRITES.len();
|
let this_image = (image + i * SPRITES.len() / objs_len) % SPRITES.len();
|
||||||
obj.set_sprite(gfx.sprite(&SPRITES[this_image]));
|
obj.set_sprite(gfx.sprite(&SPRITES[this_image]));
|
||||||
obj.commit();
|
gfx.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +90,8 @@ fn all_tags(gfx: &ObjectController) {
|
||||||
image += 1;
|
image += 1;
|
||||||
for (obj, tag) in objs.iter_mut() {
|
for (obj, tag) in objs.iter_mut() {
|
||||||
obj.set_sprite(gfx.sprite(tag.animation_sprite(image)));
|
obj.set_sprite(gfx.sprite(tag.animation_sprite(image)));
|
||||||
obj.commit();
|
|
||||||
}
|
}
|
||||||
|
gfx.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,10 +100,6 @@ fn all_tags(gfx: &ObjectController) {
|
||||||
fn main(mut gba: agb::Gba) -> ! {
|
fn main(mut gba: agb::Gba) -> ! {
|
||||||
let gfx = gba.display.object.get();
|
let gfx = gba.display.object.get();
|
||||||
|
|
||||||
let mut timers = gba.timers.timers();
|
|
||||||
|
|
||||||
let _a = agb::interrupt::profiler(&mut timers.timer0, 5000);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
all_tags(&gfx);
|
all_tags(&gfx);
|
||||||
all_sprites(&gfx);
|
all_sprites(&gfx);
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::alloc::Layout;
|
use core::alloc::Layout;
|
||||||
use core::cell::RefCell;
|
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use core::ops::{Deref, DerefMut};
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
use core::slice;
|
use core::slice;
|
||||||
use modular_bitfield::prelude::{B10, B2, B3, B4, B5, B8, B9};
|
use modular_bitfield::prelude::{B10, B2, B3, B4, B5, B8, B9};
|
||||||
|
@ -16,9 +19,82 @@ use crate::agb_alloc::bump_allocator::StartEnd;
|
||||||
use crate::dma;
|
use crate::dma;
|
||||||
use crate::fixnum::Vector2D;
|
use crate::fixnum::Vector2D;
|
||||||
use crate::hash_map::HashMap;
|
use crate::hash_map::HashMap;
|
||||||
|
use crate::interrupt::free;
|
||||||
|
|
||||||
|
use bare_metal::Mutex;
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
use attributes::*;
|
use attributes::*;
|
||||||
|
|
||||||
|
static mut OBJECT_CONTROLLER: MaybeUninit<ObjectControllerStatic> = MaybeUninit::uninit();
|
||||||
|
|
||||||
|
unsafe fn init_object_controller() {
|
||||||
|
OBJECT_CONTROLLER.write(ObjectControllerStatic::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn uninit_object_controller() {
|
||||||
|
OBJECT_CONTROLLER.assume_init_drop()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectControllerRef {}
|
||||||
|
|
||||||
|
impl Deref for ObjectControllerRef {
|
||||||
|
type Target = ObjectControllerStatic;
|
||||||
|
fn deref(&self) -> &'static ObjectControllerStatic {
|
||||||
|
unsafe { OBJECT_CONTROLLER.assume_init_ref() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for ObjectControllerRef {
|
||||||
|
fn deref_mut(&mut self) -> &'static mut ObjectControllerStatic {
|
||||||
|
unsafe { OBJECT_CONTROLLER.assume_init_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
static OBJECT_REFS_CURRENT: Mutex<RefCell<i32>> = Mutex::new(RefCell::new(0));
|
||||||
|
|
||||||
|
impl ObjectControllerRef {
|
||||||
|
fn new() -> Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let a = free(|c| {
|
||||||
|
let mut b = OBJECT_REFS_CURRENT.borrow(*c).borrow_mut();
|
||||||
|
let a = *b;
|
||||||
|
*b += 1;
|
||||||
|
a
|
||||||
|
});
|
||||||
|
assert_eq!(a, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn very_unsafe_borrow(&self) -> &'static mut ObjectControllerStatic {
|
||||||
|
OBJECT_CONTROLLER.assume_init_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
impl Drop for ObjectControllerRef {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
free(|c| {
|
||||||
|
let mut b = OBJECT_REFS_CURRENT.borrow(*c).borrow_mut();
|
||||||
|
*b -= 1;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_object_controller(_r: &ObjectControllerReference) -> ObjectControllerRef {
|
||||||
|
ObjectControllerRef::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Include this type if you call `get_object_controller` in impl block. This
|
||||||
|
/// helps you use the right lifetimes and doesn't impl Sync (using from two
|
||||||
|
/// "threads" without syncronisation is not safe), but sending to another
|
||||||
|
/// "thread" is safe.
|
||||||
|
type ObjectControllerReference<'a> = PhantomData<&'a UnsafeCell<()>>;
|
||||||
|
|
||||||
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
|
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
|
||||||
BlockAllocator::new(StartEnd {
|
BlockAllocator::new(StartEnd {
|
||||||
start: || TILE_SPRITE,
|
start: || TILE_SPRITE,
|
||||||
|
@ -258,7 +334,7 @@ pub struct SpriteBorrow<'a> {
|
||||||
id: SpriteId,
|
id: SpriteId,
|
||||||
sprite_location: u16,
|
sprite_location: u16,
|
||||||
palette_location: u16,
|
palette_location: u16,
|
||||||
controller: &'a RefCell<SpriteControllerInner>,
|
phantom: ObjectControllerReference<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -288,6 +364,7 @@ impl Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
struct Attributes {
|
struct Attributes {
|
||||||
a0: ObjectAttribute0,
|
a0: ObjectAttribute0,
|
||||||
a1s: ObjectAttribute1Standard,
|
a1s: ObjectAttribute1Standard,
|
||||||
|
@ -304,13 +381,35 @@ impl Attributes {
|
||||||
a2: ObjectAttribute2::new(),
|
a2: ObjectAttribute2::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn commit(&self, location: usize) {
|
||||||
|
let mode = self.a0.object_mode();
|
||||||
|
let attrs: [[u8; 2]; 3] = match mode {
|
||||||
|
ObjectMode::Normal => [
|
||||||
|
self.a0.into_bytes(),
|
||||||
|
self.a1s.into_bytes(),
|
||||||
|
self.a2.into_bytes(),
|
||||||
|
],
|
||||||
|
_ => [
|
||||||
|
self.a0.into_bytes(),
|
||||||
|
self.a1a.into_bytes(),
|
||||||
|
self.a2.into_bytes(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let attrs: [u16; 3] = core::mem::transmute(attrs);
|
||||||
|
let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(location * 4);
|
||||||
|
|
||||||
|
ptr.add(0).write_volatile(attrs[0]);
|
||||||
|
ptr.add(1).write_volatile(attrs[1]);
|
||||||
|
ptr.add(2).write_volatile(attrs[2]);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Object<'a, 'b> {
|
pub struct Object<'a> {
|
||||||
sprite: SpriteBorrow<'a>,
|
loan: Loan<'a>,
|
||||||
previous_sprite: SpriteBorrow<'a>,
|
|
||||||
loan: Loan<'b>,
|
|
||||||
attrs: Attributes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpriteControllerInner {
|
struct SpriteControllerInner {
|
||||||
|
@ -318,28 +417,113 @@ struct SpriteControllerInner {
|
||||||
sprite: HashMap<SpriteId, Storage>,
|
sprite: HashMap<SpriteId, Storage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpriteController {
|
|
||||||
inner: RefCell<SpriteControllerInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Loan<'a> {
|
struct Loan<'a> {
|
||||||
index: u8,
|
index: u8,
|
||||||
free_list: &'a RefCell<Vec<u8>>,
|
phantom: ObjectControllerReference<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Loan<'_> {
|
impl Drop for Loan<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let mut list = self.free_list.borrow_mut();
|
let mut s = unsafe { get_object_controller(&self.phantom) };
|
||||||
list.push(self.index);
|
|
||||||
|
unsafe {
|
||||||
|
s.shadow_oam[self.index as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.destroy = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectInner {
|
||||||
|
attrs: Attributes,
|
||||||
|
sprite: SpriteBorrow<'static>,
|
||||||
|
previous_sprite: SpriteBorrow<'static>,
|
||||||
|
destroy: bool,
|
||||||
|
z: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectControllerStatic {
|
||||||
|
free_affine_matricies: Vec<u8>,
|
||||||
|
free_object: Vec<u8>,
|
||||||
|
shadow_oam: Vec<Option<ObjectInner>>,
|
||||||
|
z_order: Vec<u8>,
|
||||||
|
sprite_controller: SpriteControllerInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectControllerStatic {
|
||||||
|
unsafe fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
shadow_oam: (0..128).map(|_| None).collect(),
|
||||||
|
z_order: (0..128).collect(),
|
||||||
|
free_object: (0..128).collect(),
|
||||||
|
free_affine_matricies: (0..32).collect(),
|
||||||
|
sprite_controller: SpriteControllerInner::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_z_ordering(&mut self) {
|
||||||
|
let shadow_oam = &self.shadow_oam;
|
||||||
|
self.z_order.sort_by_key(|&a| {
|
||||||
|
shadow_oam[a as usize]
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.z)
|
||||||
|
.unwrap_or(i32::MAX)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ObjectController {
|
pub struct ObjectController {
|
||||||
free_objects: RefCell<Vec<u8>>,
|
phantom: ObjectControllerReference<'static>,
|
||||||
sprite_controller: SpriteController,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for ObjectController {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
uninit_object_controller();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HIDDEN_VALUE: u16 = 0b10 << 8;
|
||||||
|
|
||||||
impl ObjectController {
|
impl ObjectController {
|
||||||
|
pub fn commit(&self) {
|
||||||
|
let mut s = unsafe { get_object_controller(&self.phantom) };
|
||||||
|
|
||||||
|
let s = &mut *s;
|
||||||
|
|
||||||
|
for (i, &z) in s.z_order.iter().enumerate() {
|
||||||
|
if let Some(o) = &mut s.shadow_oam[z as usize] {
|
||||||
|
if o.destroy {
|
||||||
|
s.free_object.push(z);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
||||||
|
.add((i as usize) * 4)
|
||||||
|
.write_volatile(HIDDEN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = unsafe { s.shadow_oam[z as usize].take().unwrap_unchecked() };
|
||||||
|
a.previous_sprite.drop(&mut s.sprite_controller);
|
||||||
|
a.sprite.drop(&mut s.sprite_controller);
|
||||||
|
} else {
|
||||||
|
o.attrs.commit(i);
|
||||||
|
|
||||||
|
let mut a = o.sprite.clone(&mut s.sprite_controller);
|
||||||
|
core::mem::swap(&mut o.previous_sprite, &mut a);
|
||||||
|
a.drop(&mut s.sprite_controller);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
||||||
|
.add(i * 4)
|
||||||
|
.write_volatile(HIDDEN_VALUE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
DISPLAY_CONTROL.set_bits(1, 1, 0x6);
|
DISPLAY_CONTROL.set_bits(1, 1, 0x6);
|
||||||
DISPLAY_CONTROL.set_bits(1, 1, 0xC);
|
DISPLAY_CONTROL.set_bits(1, 1, 0xC);
|
||||||
|
@ -349,26 +533,22 @@ impl ObjectController {
|
||||||
unsafe {
|
unsafe {
|
||||||
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
||||||
.add(i * 4)
|
.add(i * 4)
|
||||||
.write_volatile(0b10 << 8)
|
.write_volatile(HIDDEN_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe { init_object_controller() };
|
||||||
Self {
|
Self {
|
||||||
free_objects: RefCell::new((0..128).collect()),
|
phantom: PhantomData,
|
||||||
sprite_controller: SpriteController::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn object<'a, 'b>(&'a self, sprite: SpriteBorrow<'b>) -> Object<'b, 'a> {
|
pub fn object<'a>(&'a self, sprite: SpriteBorrow<'a>) -> Object<'a> {
|
||||||
self.try_get_object(sprite).expect("No object available")
|
self.try_get_object(sprite).expect("No object available")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_object<'a, 'b>(&'a self, sprite: SpriteBorrow<'b>) -> Option<Object<'b, 'a>> {
|
pub fn try_get_object<'a>(&'a self, sprite: SpriteBorrow<'a>) -> Option<Object<'a>> {
|
||||||
let mut inner = self.free_objects.borrow_mut();
|
let mut s = unsafe { get_object_controller(&self.phantom) };
|
||||||
let loan = Loan {
|
|
||||||
index: inner.pop()?,
|
|
||||||
free_list: &self.free_objects,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut attrs = Attributes::new();
|
let mut attrs = Attributes::new();
|
||||||
|
|
||||||
|
@ -379,113 +559,134 @@ impl ObjectController {
|
||||||
attrs.a1a.set_size(shape_size.1);
|
attrs.a1a.set_size(shape_size.1);
|
||||||
attrs.a1s.set_size(shape_size.1);
|
attrs.a1s.set_size(shape_size.1);
|
||||||
|
|
||||||
Some(Object {
|
let index = s.free_object.pop()?;
|
||||||
previous_sprite: sprite.clone(),
|
|
||||||
sprite,
|
let new_sprite: SpriteBorrow<'static> = unsafe { core::mem::transmute(sprite) };
|
||||||
loan,
|
|
||||||
|
s.shadow_oam[index as usize] = Some(ObjectInner {
|
||||||
attrs,
|
attrs,
|
||||||
})
|
z: 0,
|
||||||
|
previous_sprite: new_sprite.clone(&mut s.sprite_controller),
|
||||||
|
destroy: false,
|
||||||
|
sprite: new_sprite,
|
||||||
|
});
|
||||||
|
|
||||||
|
let loan = Loan {
|
||||||
|
index: index as u8,
|
||||||
|
phantom: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
s.update_z_ordering();
|
||||||
|
|
||||||
|
Some(Object { loan })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sprite(&self, sprite: &'static Sprite) -> SpriteBorrow {
|
pub fn sprite(&self, sprite: &'static Sprite) -> SpriteBorrow {
|
||||||
self.sprite_controller
|
self.try_get_sprite(sprite)
|
||||||
.try_get_sprite(sprite)
|
|
||||||
.expect("No slot for sprite available")
|
.expect("No slot for sprite available")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_sprite(&self, sprite: &'static Sprite) -> Option<SpriteBorrow> {
|
pub fn try_get_sprite(&self, sprite: &'static Sprite) -> Option<SpriteBorrow> {
|
||||||
self.sprite_controller.try_get_sprite(sprite)
|
let s = unsafe { get_object_controller(&self.phantom) };
|
||||||
|
unsafe {
|
||||||
|
s.very_unsafe_borrow()
|
||||||
|
.sprite_controller
|
||||||
|
.try_get_sprite(sprite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Object<'_, '_> {
|
impl<'a> Object<'a> {
|
||||||
fn drop(&mut self) {
|
#[inline(always)]
|
||||||
self.attrs.a0.set_object_mode(ObjectMode::Disabled);
|
unsafe fn object_inner(&mut self) -> &mut ObjectInner {
|
||||||
self.commit();
|
let s = get_object_controller(&self.loan.phantom);
|
||||||
|
s.very_unsafe_borrow().shadow_oam[self.loan.index as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_unchecked()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> Object<'a, 'b> {
|
|
||||||
pub fn set_sprite(&'_ mut self, sprite: SpriteBorrow<'a>) {
|
pub fn set_sprite(&'_ mut self, sprite: SpriteBorrow<'a>) {
|
||||||
self.attrs.a2.set_tile_index(sprite.sprite_location);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a2.set_tile_index(sprite.sprite_location);
|
||||||
let shape_size = sprite.id.sprite().size.shape_size();
|
let shape_size = sprite.id.sprite().size.shape_size();
|
||||||
self.attrs.a2.set_palete_bank(sprite.palette_location as u8);
|
object_inner
|
||||||
self.attrs.a0.set_shape(shape_size.0);
|
.attrs
|
||||||
self.attrs.a1a.set_size(shape_size.1);
|
.a2
|
||||||
self.attrs.a1s.set_size(shape_size.1);
|
.set_palete_bank(sprite.palette_location as u8);
|
||||||
self.sprite = sprite;
|
object_inner.attrs.a0.set_shape(shape_size.0);
|
||||||
|
object_inner.attrs.a1a.set_size(shape_size.1);
|
||||||
|
object_inner.attrs.a1s.set_size(shape_size.1);
|
||||||
|
object_inner.sprite = unsafe { core::mem::transmute(sprite) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self) -> &mut Self {
|
pub fn show(&mut self) -> &mut Self {
|
||||||
self.attrs.a0.set_object_mode(ObjectMode::Normal);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a0.set_object_mode(ObjectMode::Normal);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
|
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
|
||||||
self.attrs.a1s.set_horizontal_flip(flip);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a1s.set_horizontal_flip(flip);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
|
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
|
||||||
self.attrs.a1s.set_vertical_flip(flip);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a1s.set_vertical_flip(flip);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
||||||
self.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
self.attrs.a1s.set_x(x.rem_euclid(1 << 9) as u16);
|
object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16);
|
||||||
|
object_inner.attrs.a1s.set_x(x.rem_euclid(1 << 9) as u16);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
|
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
|
||||||
self.attrs.a2.set_priority(priority);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a2.set_priority(priority);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide(&mut self) -> &mut Self {
|
pub fn hide(&mut self) -> &mut Self {
|
||||||
self.attrs.a0.set_object_mode(ObjectMode::Disabled);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a0.set_object_mode(ObjectMode::Disabled);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_y(&mut self, y: u16) -> &mut Self {
|
pub fn set_y(&mut self, y: u16) -> &mut Self {
|
||||||
self.attrs.a0.set_y(y as u8);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.attrs.a0.set_y(y as u8);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_z(&mut self, z: i32) -> &mut Self {
|
||||||
|
let object_inner = unsafe { self.object_inner() };
|
||||||
|
object_inner.z = z;
|
||||||
|
unsafe {
|
||||||
|
get_object_controller(&self.loan.phantom).update_z_ordering();
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
|
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
|
||||||
self.attrs.a0.set_y(position.y as u8);
|
let object_inner = unsafe { self.object_inner() };
|
||||||
self.attrs.a1a.set_x(position.x.rem_euclid(1 << 9) as u16);
|
object_inner.attrs.a0.set_y(position.y as u8);
|
||||||
self.attrs.a1s.set_x(position.x.rem_euclid(1 << 9) as u16);
|
object_inner
|
||||||
|
.attrs
|
||||||
|
.a1a
|
||||||
|
.set_x(position.x.rem_euclid(1 << 9) as u16);
|
||||||
|
object_inner
|
||||||
|
.attrs
|
||||||
|
.a1s
|
||||||
|
.set_x(position.x.rem_euclid(1 << 9) as u16);
|
||||||
self
|
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
|
/// The Sprite Id is a thin wrapper around the pointer to the sprite in
|
||||||
|
@ -535,30 +736,25 @@ impl Sprite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteController {
|
impl SpriteControllerInner {
|
||||||
fn new() -> Self {
|
fn try_get_sprite(&mut self, sprite: &'static Sprite) -> Option<SpriteBorrow> {
|
||||||
Self {
|
|
||||||
inner: RefCell::new(SpriteControllerInner::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn try_get_sprite(&self, sprite: &'static Sprite) -> Option<SpriteBorrow> {
|
|
||||||
let mut inner = self.inner.borrow_mut();
|
|
||||||
let id = sprite.id();
|
let id = sprite.id();
|
||||||
if let Some(storage) = inner.sprite.get_mut(&id) {
|
if let Some(storage) = self.sprite.get_mut(&id) {
|
||||||
storage.count += 1;
|
storage.count += 1;
|
||||||
let location = storage.location;
|
let location = storage.location;
|
||||||
let palette_location = inner.palette(sprite.palette).unwrap();
|
let palette_location = self.palette(sprite.palette).unwrap();
|
||||||
Some(SpriteBorrow {
|
Some(SpriteBorrow {
|
||||||
id,
|
id,
|
||||||
palette_location,
|
palette_location,
|
||||||
sprite_location: location,
|
sprite_location: location,
|
||||||
controller: &self.inner,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// layout is non zero sized, so this is safe to call
|
// layout is non zero sized, so this is safe to call
|
||||||
|
|
||||||
let dest = unsafe { SPRITE_ALLOCATOR.alloc(sprite.layout())? };
|
let dest = unsafe { SPRITE_ALLOCATOR.alloc(sprite.layout())? };
|
||||||
let palette_location = inner.palette(sprite.palette);
|
|
||||||
|
let palette_location = self.palette(sprite.palette);
|
||||||
let palette_location = match palette_location {
|
let palette_location = match palette_location {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => {
|
None => {
|
||||||
|
@ -576,13 +772,13 @@ impl SpriteController {
|
||||||
}
|
}
|
||||||
|
|
||||||
let storage = Storage::from_sprite_ptr(dest);
|
let storage = Storage::from_sprite_ptr(dest);
|
||||||
inner.sprite.insert(id, storage);
|
self.sprite.insert(id, storage);
|
||||||
|
|
||||||
Some(SpriteBorrow {
|
Some(SpriteBorrow {
|
||||||
id,
|
id,
|
||||||
controller: &self.inner,
|
|
||||||
palette_location,
|
palette_location,
|
||||||
sprite_location: storage.location,
|
sprite_location: storage.location,
|
||||||
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -649,22 +845,33 @@ impl SpriteControllerInner {
|
||||||
|
|
||||||
impl<'a> Drop for SpriteBorrow<'a> {
|
impl<'a> Drop for SpriteBorrow<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let mut inner = self.controller.borrow_mut();
|
let mut s = unsafe { get_object_controller(&self.phantom) };
|
||||||
inner.return_sprite(self.id.sprite())
|
s.sprite_controller.return_sprite(self.id.sprite())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SpriteBorrow<'a> {
|
||||||
|
fn drop(self, s: &mut SpriteControllerInner) {
|
||||||
|
s.return_sprite(self.id.sprite());
|
||||||
|
core::mem::forget(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone(&self, s: &mut SpriteControllerInner) -> Self {
|
||||||
|
s.sprite.entry(self.id).and_modify(|a| a.count += 1);
|
||||||
|
let _ = s.palette(self.id.sprite().palette).unwrap();
|
||||||
|
Self {
|
||||||
|
id: self.id,
|
||||||
|
sprite_location: self.sprite_location,
|
||||||
|
palette_location: self.palette_location,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Clone for SpriteBorrow<'a> {
|
impl<'a> Clone for SpriteBorrow<'a> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
let mut inner = self.controller.borrow_mut();
|
let mut s = unsafe { get_object_controller(&self.phantom) };
|
||||||
inner.sprite.entry(self.id).and_modify(|a| a.count += 1);
|
self.clone(&mut s.sprite_controller)
|
||||||
let _ = inner.palette(self.id.sprite().palette).unwrap();
|
|
||||||
Self {
|
|
||||||
id: self.id,
|
|
||||||
sprite_location: self.sprite_location,
|
|
||||||
palette_location: self.palette_location,
|
|
||||||
controller: self.controller,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,7 +901,7 @@ enum ColourMode {
|
||||||
mod attributes {
|
mod attributes {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[bitfield]
|
#[bitfield]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(super) struct ObjectAttribute0 {
|
pub(super) struct ObjectAttribute0 {
|
||||||
pub y: B8,
|
pub y: B8,
|
||||||
pub object_mode: ObjectMode,
|
pub object_mode: ObjectMode,
|
||||||
|
@ -705,7 +912,7 @@ mod attributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitfield]
|
#[bitfield]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(super) struct ObjectAttribute1Standard {
|
pub(super) struct ObjectAttribute1Standard {
|
||||||
pub x: B9,
|
pub x: B9,
|
||||||
#[skip]
|
#[skip]
|
||||||
|
@ -716,7 +923,7 @@ mod attributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitfield]
|
#[bitfield]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(super) struct ObjectAttribute1Affine {
|
pub(super) struct ObjectAttribute1Affine {
|
||||||
pub x: B9,
|
pub x: B9,
|
||||||
pub affine_index: B5,
|
pub affine_index: B5,
|
||||||
|
@ -724,10 +931,53 @@ mod attributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitfield]
|
#[bitfield]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(super) struct ObjectAttribute2 {
|
pub(super) struct ObjectAttribute2 {
|
||||||
pub tile_index: B10,
|
pub tile_index: B10,
|
||||||
pub priority: Priority,
|
pub priority: Priority,
|
||||||
pub palete_bank: B4,
|
pub palete_bank: B4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn size_of_ObjectControllerReference(_: &mut crate::Gba) {
|
||||||
|
assert_eq!(size_of::<ObjectControllerReference>(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn object_usage(gba: &mut crate::Gba) {
|
||||||
|
const GRAPHICS: &Graphics = include_aseprite!(
|
||||||
|
"../examples/the-purple-night/gfx/objects.aseprite",
|
||||||
|
"../examples/the-purple-night/gfx/boss.aseprite"
|
||||||
|
);
|
||||||
|
|
||||||
|
const BOSS: &Tag = GRAPHICS.tags().get("Boss");
|
||||||
|
const EMU: &Tag = GRAPHICS.tags().get("emu - idle");
|
||||||
|
|
||||||
|
let object = gba.display.object.get();
|
||||||
|
|
||||||
|
let mut objects: Vec<_> = alloc::vec![
|
||||||
|
object.object(object.sprite(BOSS.sprite(0))),
|
||||||
|
object.object(object.sprite(EMU.sprite(0))),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(Some)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
object.commit();
|
||||||
|
|
||||||
|
let x = objects[0].as_mut().unwrap();
|
||||||
|
x.set_hflip(true);
|
||||||
|
x.set_vflip(true);
|
||||||
|
x.set_position((1, 1).into());
|
||||||
|
x.set_z(100);
|
||||||
|
x.set_sprite(object.sprite(BOSS.sprite(2)));
|
||||||
|
|
||||||
|
object.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,6 @@ fn main(mut gba: Gba) -> ! {
|
||||||
ball.set_x(ball_x as u16).set_y(ball_y as u16);
|
ball.set_x(ball_x as u16).set_y(ball_y as u16);
|
||||||
|
|
||||||
agb::display::busy_wait_for_vblank();
|
agb::display::busy_wait_for_vblank();
|
||||||
ball.commit();
|
object.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ const HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3");
|
||||||
type FixedNumberType = FixedNum<10>;
|
type FixedNumberType = FixedNum<10>;
|
||||||
|
|
||||||
pub struct Entity<'a> {
|
pub struct Entity<'a> {
|
||||||
sprite: Object<'a, 'a>,
|
sprite: Object<'a>,
|
||||||
position: Vector2D<FixedNumberType>,
|
position: Vector2D<FixedNumberType>,
|
||||||
velocity: Vector2D<FixedNumberType>,
|
velocity: Vector2D<FixedNumberType>,
|
||||||
collision_mask: Vector2D<u16>,
|
collision_mask: Vector2D<u16>,
|
||||||
|
@ -258,7 +258,6 @@ impl<'a> Entity<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.sprite.show();
|
self.sprite.show();
|
||||||
}
|
}
|
||||||
self.sprite.commit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,8 +355,7 @@ impl<'a> Player<'a> {
|
||||||
wizard.sprite.show();
|
wizard.sprite.show();
|
||||||
hat.sprite.show();
|
hat.sprite.show();
|
||||||
|
|
||||||
wizard.sprite.commit();
|
hat.sprite.set_z(-1);
|
||||||
hat.sprite.commit();
|
|
||||||
|
|
||||||
wizard.position = start_position;
|
wizard.position = start_position;
|
||||||
hat.position = start_position - (0, 10).into();
|
hat.position = start_position - (0, 10).into();
|
||||||
|
@ -498,22 +496,6 @@ impl<'a> Player<'a> {
|
||||||
_ => HAT_SPIN_3,
|
_ => HAT_SPIN_3,
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.facing {
|
|
||||||
agb::input::Tri::Negative => {
|
|
||||||
self.wizard.sprite.set_hflip(true);
|
|
||||||
self.hat
|
|
||||||
.sprite
|
|
||||||
.set_sprite(controller.sprite(hat_base_tile.sprite(5)));
|
|
||||||
}
|
|
||||||
agb::input::Tri::Positive => {
|
|
||||||
self.wizard.sprite.set_hflip(false);
|
|
||||||
self.hat
|
|
||||||
.sprite
|
|
||||||
.set_sprite(controller.sprite(hat_base_tile.sprite(0)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hat_resting_position = match self.wizard_frame {
|
let hat_resting_position = match self.wizard_frame {
|
||||||
1 | 2 => (0, 9).into(),
|
1 | 2 => (0, 9).into(),
|
||||||
5 => (0, 10).into(),
|
5 => (0, 10).into(),
|
||||||
|
@ -571,6 +553,21 @@ impl<'a> Player<'a> {
|
||||||
self.hat_slow_counter = 0;
|
self.hat_slow_counter = 0;
|
||||||
self.hat_left_range = false;
|
self.hat_left_range = false;
|
||||||
self.hat.position = self.wizard.position - hat_resting_position;
|
self.hat.position = self.wizard.position - hat_resting_position;
|
||||||
|
match self.facing {
|
||||||
|
agb::input::Tri::Negative => {
|
||||||
|
self.wizard.sprite.set_hflip(true);
|
||||||
|
self.hat
|
||||||
|
.sprite
|
||||||
|
.set_sprite(controller.sprite(hat_base_tile.sprite(5)));
|
||||||
|
}
|
||||||
|
agb::input::Tri::Positive => {
|
||||||
|
self.wizard.sprite.set_hflip(false);
|
||||||
|
self.hat
|
||||||
|
.sprite
|
||||||
|
.set_sprite(controller.sprite(hat_base_tile.sprite(0)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HatState::WizardTowards => {
|
HatState::WizardTowards => {
|
||||||
self.hat.sprite.set_sprite(
|
self.hat.sprite.set_sprite(
|
||||||
|
@ -907,6 +904,8 @@ fn main(mut agb: agb::Gba) -> ! {
|
||||||
mixer.after_vblank();
|
mixer.after_vblank();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object.commit();
|
||||||
|
|
||||||
level.show_backgrounds();
|
level.show_backgrounds();
|
||||||
|
|
||||||
world_display.hide();
|
world_display.hide();
|
||||||
|
@ -925,6 +924,7 @@ fn main(mut agb: agb::Gba) -> ! {
|
||||||
mixer.frame();
|
mixer.frame();
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
mixer.after_vblank();
|
mixer.after_vblank();
|
||||||
|
object.commit();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -938,6 +938,7 @@ fn main(mut agb: agb::Gba) -> ! {
|
||||||
mixer.frame();
|
mixer.frame();
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
mixer.after_vblank();
|
mixer.after_vblank();
|
||||||
|
object.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
level.hide_backgrounds();
|
level.hide_backgrounds();
|
||||||
|
|
|
@ -152,7 +152,7 @@ impl<'a> Level<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Entity<'a> {
|
struct Entity<'a> {
|
||||||
sprite: Object<'a, 'a>,
|
sprite: Object<'a>,
|
||||||
position: Vector2D<Number>,
|
position: Vector2D<Number>,
|
||||||
velocity: Vector2D<Number>,
|
velocity: Vector2D<Number>,
|
||||||
collision_mask: Rect<u16>,
|
collision_mask: Rect<u16>,
|
||||||
|
@ -285,7 +285,6 @@ impl<'a> Entity<'a> {
|
||||||
self.sprite.show();
|
self.sprite.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.sprite.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_with_size(&mut self, offset: Vector2D<Number>, size: Vector2D<i32>) {
|
fn commit_with_size(&mut self, offset: Vector2D<Number>, size: Vector2D<i32>) {
|
||||||
|
@ -304,7 +303,6 @@ impl<'a> Entity<'a> {
|
||||||
self.sprite.show();
|
self.sprite.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.sprite.commit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +538,6 @@ impl<'a> Player<'a> {
|
||||||
entity.sprite.set_sprite(s);
|
entity.sprite.set_sprite(s);
|
||||||
entity.sprite.show();
|
entity.sprite.show();
|
||||||
entity.position = (144, 0).into();
|
entity.position = (144, 0).into();
|
||||||
entity.sprite.commit();
|
|
||||||
|
|
||||||
Player {
|
Player {
|
||||||
entity,
|
entity,
|
||||||
|
@ -1405,8 +1402,6 @@ impl<'a> Enemy<'a> {
|
||||||
entity.sprite.set_sprite(sprite);
|
entity.sprite.set_sprite(sprite);
|
||||||
entity.sprite.show();
|
entity.sprite.show();
|
||||||
|
|
||||||
entity.sprite.commit();
|
|
||||||
|
|
||||||
Self { entity, enemy_data }
|
Self { entity, enemy_data }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2285,6 +2280,7 @@ fn game_with_level(gba: &mut agb::Gba) {
|
||||||
sfx.frame();
|
sfx.frame();
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
sfx.after_vblank();
|
sfx.after_vblank();
|
||||||
|
object.commit();
|
||||||
match game.advance_frame(&object, &mut vram, &mut sfx) {
|
match game.advance_frame(&object, &mut vram, &mut sfx) {
|
||||||
GameStatus::Continue => {}
|
GameStatus::Continue => {}
|
||||||
GameStatus::Lost => {
|
GameStatus::Lost => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue