Objects, again, again (#402)

* A redesign of objects giving the option of a more managed and an
unmanaged system.
* Managed system features
    * Z ordering (should be efficient, based on a doubly linked list).
* More than 128 objects if some are disabled (not currently efficient).
* Unmanaged
    * Same sprite system as before.
    * You control how objects get put in to OAM with the unmanaged OAM.
    
TODO before this is mergable:
* [x] Docs pass, aim for everything to have docs as before.
* [x] Tests, some of these needs some run time testing.
* [x] Affine matrices, I'm not leaving this to die this time.
* [x] Some API redo, so the names make sense / are similar to before.

Future work:
* The Sprite loader should keep most recently used sprites around and
upon allocation of a new sprite try unloading the least recently used
sprite. (Performance wise this would be a disaster as it would be doing
dealloc and alloc in vblank, so really we want to "learn" how much
buffer we should maintain and try to get that during GC).

- [x] Changelog updated
This commit is contained in:
Corwin 2023-04-25 21:14:49 +01:00 committed by GitHub
commit 68e981f3c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 3386 additions and 1631 deletions

View file

@ -39,6 +39,9 @@
{
"path": "../tools"
},
{
"path": "../examples/combo"
},
{
"path": "../agb-hashmap"
}

View file

@ -8,10 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- You can now import aseprite files directly (in addition to the already supported png and bmp files) when importing background tiles.
- New additional unmanaged object API for interacting with a more straightforward manner with the underlying hardware.
### Changed
- Importing background tiles has been improved. You no longer need to use `include_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage.
- The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code
- Moved the existing object API to be the OamManaged API. The old names persist with deprecated notices on them.
## [0.14.0] - 2023/04/11

20
agb-gbafix/Cargo.lock generated
View file

@ -88,18 +88,18 @@ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "clap"
version = "4.2.2"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.2.2"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
dependencies = [
"anstream",
"anstyle",
@ -187,21 +187,21 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
[[package]]
name = "rustix"
version = "0.37.11"
version = "0.37.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
dependencies = [
"bitflags",
"errno",

View file

@ -4,7 +4,7 @@
use agb::{
display::tiled::{TileFormat, TileSet, TileSetting, TiledMap},
display::{
object::{Object, ObjectController, Size, Sprite},
object::{OamManaged, Object, Size, Sprite},
palette16::Palette16,
tiled::RegularBackgroundSize,
HEIGHT, WIDTH,
@ -74,7 +74,7 @@ fn main(mut gba: agb::Gba) -> ! {
background.show();
background.commit(&mut vram);
let object = gba.display.object.get();
let object = gba.display.object.get_managed();
let sprite = object.sprite(&CHICKEN_SPRITES[0]);
let mut chicken = Character {
@ -143,9 +143,9 @@ fn main(mut gba: agb::Gba) -> ! {
}
}
fn update_chicken_object<'a>(
chicken: &'_ mut Character<'a>,
object: &'a ObjectController,
fn update_chicken_object(
chicken: &'_ mut Character<'_>,
gfx: &OamManaged,
state: State,
frame_count: u32,
) {
@ -157,20 +157,18 @@ fn update_chicken_object<'a>(
match state {
State::Ground => {
if chicken.velocity.x.abs() > 1 << 4 {
chicken.object.set_sprite(
object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]),
);
} else {
chicken
.object
.set_sprite(object.sprite(&CHICKEN_SPRITES[0]));
.set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]));
} else {
chicken.object.set_sprite(gfx.sprite(&CHICKEN_SPRITES[0]));
}
}
State::Upwards => {}
State::Flapping => {
chicken
.object
.set_sprite(object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]));
.set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]));
}
}

View file

@ -3,7 +3,12 @@
extern crate alloc;
use agb::display::object::{Graphics, ObjectController, Sprite, TagMap};
use agb::display::{
affine::AffineMatrix,
object::{self, Graphics, OamManaged, Sprite, TagMap},
};
use agb::fixnum::num;
use agb_fixnum::Num;
use alloc::vec::Vec;
const GRAPHICS: &Graphics = agb::include_aseprite!(
@ -15,14 +20,20 @@ const GRAPHICS: &Graphics = agb::include_aseprite!(
const SPRITES: &[Sprite] = GRAPHICS.sprites();
const TAG_MAP: &TagMap = GRAPHICS.tags();
fn all_sprites(gfx: &ObjectController) {
fn all_sprites(gfx: &OamManaged, rotation_speed: Num<i32, 16>) {
let mut input = agb::input::ButtonController::new();
let mut objs = Vec::new();
let mut rotation: Num<i32, 16> = num!(0.);
let rotation_matrix = AffineMatrix::from_rotation(rotation);
let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping());
for y in 0..9 {
for x in 0..14 {
let mut obj = gfx.object(gfx.sprite(&SPRITES[0]));
obj.show();
let mut obj = gfx.object_sprite(&SPRITES[0]);
obj.set_affine_matrix(matrix.clone());
obj.show_affine(object::AffineMode::Affine);
obj.set_position((x * 16 + 8, y * 16 + 8).into());
objs.push(obj);
}
@ -41,6 +52,15 @@ fn all_sprites(gfx: &ObjectController) {
break;
}
rotation += rotation_speed;
let rotation_matrix = AffineMatrix::from_rotation(rotation);
let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping());
for obj in objs.iter_mut() {
obj.set_affine_matrix(matrix.clone());
}
count += 1;
if count % 5 == 0 {
@ -50,12 +70,12 @@ fn all_sprites(gfx: &ObjectController) {
let this_image = (image + i) % SPRITES.len();
obj.set_sprite(gfx.sprite(&SPRITES[this_image]));
}
gfx.commit();
}
gfx.commit();
}
}
fn all_tags(gfx: &ObjectController) {
fn all_tags(gfx: &OamManaged) {
let mut input = agb::input::ButtonController::new();
let mut objs = Vec::new();
@ -65,7 +85,7 @@ fn all_tags(gfx: &ObjectController) {
let sprite = v.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.object(gfx.sprite(sprite));
let mut obj = gfx.object_sprite(sprite);
obj.show();
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into());
objs.push((obj, v));
@ -99,12 +119,11 @@ fn all_tags(gfx: &ObjectController) {
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let gfx = gba.display.object.get();
let gfx = gba.display.object.get_managed();
loop {
all_tags(&gfx);
gfx.commit();
all_sprites(&gfx);
gfx.commit();
all_sprites(&gfx, num!(0.));
all_sprites(&gfx, num!(0.01));
}
}

77
agb/src/arena.rs Normal file
View file

@ -0,0 +1,77 @@
use core::{alloc::Allocator, mem::ManuallyDrop};
use alloc::{alloc::Global, vec::Vec};
union ArenaItem<T> {
free: Option<ArenaKey>,
occupied: ManuallyDrop<T>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct ArenaKey(usize);
pub struct Arena<T, A: Allocator = Global> {
tip: Option<ArenaKey>,
data: Vec<ArenaItem<T>, A>,
inserted: usize,
}
impl<T> Arena<T, Global> {
pub const fn new() -> Self {
Self::new_in(Global)
}
}
impl<T, A: Allocator> Arena<T, A> {
pub const fn new_in(alloc: A) -> Self {
Self {
tip: None,
data: Vec::new_in(alloc),
inserted: 0,
}
}
pub unsafe fn insert(&mut self, value: T) -> ArenaKey {
self.inserted += 1;
match self.tip {
Some(tip) => {
self.tip = self.data[tip.0].free;
self.data[tip.0].occupied = ManuallyDrop::new(value);
tip
}
None => {
self.data.push(ArenaItem {
occupied: ManuallyDrop::new(value),
});
ArenaKey(self.data.len() - 1)
}
}
}
pub unsafe fn remove(&mut self, key: ArenaKey) {
self.inserted = self
.inserted
.checked_sub(1)
.expect("removed more items than exist in here!");
unsafe {
core::mem::ManuallyDrop::<T>::drop(&mut self.data[key.0].occupied);
}
self.data[key.0].free = self.tip;
self.tip = Some(key);
}
pub unsafe fn get(&self, key: ArenaKey) -> &T {
&self.data[key.0].occupied
}
}
impl<T, A: Allocator> Drop for Arena<T, A> {
fn drop(&mut self) {
assert_eq!(
self.inserted, 0,
"must remove all elements from arena before dropping it!"
);
}
}

View file

@ -356,6 +356,15 @@ impl AffineMatrixObject {
y: 0.into(),
}
}
pub(crate) fn components(self) -> [u16; 4] {
[
self.a.to_raw() as u16,
self.b.to_raw() as u16,
self.c.to_raw() as u16,
self.d.to_raw() as u16,
]
}
}
impl From<AffineMatrixObject> for AffineMatrix {

View file

@ -4,7 +4,11 @@ use bitflags::bitflags;
use modular_bitfield::BitfieldSpecifier;
use video::Video;
use self::{blend::Blend, object::ObjectController, window::Windows};
use self::{
blend::Blend,
object::{initilise_oam, OamManaged, OamUnmanaged, SpriteLoader},
window::Windows,
};
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
pub mod bitmap3;
@ -12,7 +16,6 @@ pub mod bitmap3;
pub mod bitmap4;
/// Test logo of agb.
pub mod example_logo;
/// Implements sprites.
pub mod object;
/// Palette type.
pub mod palette16;
@ -80,8 +83,21 @@ pub struct Display {
pub struct ObjectDistribution;
impl ObjectDistribution {
pub fn get(&mut self) -> ObjectController<'_> {
ObjectController::new()
pub fn get_unmanaged(&mut self) -> (OamUnmanaged<'_>, SpriteLoader) {
unsafe { initilise_oam() };
(OamUnmanaged::new(), SpriteLoader::new())
}
pub fn get_managed(&mut self) -> OamManaged<'_> {
unsafe { initilise_oam() };
OamManaged::new()
}
/// The old name for [`get_managed`][ObjectDistribution::get_managed] kept around for easier migration.
/// This will be removed in a future release.
#[deprecated = "use get_managed to get the managed oam instead"]
pub fn get(&mut self) -> OamManaged<'_> {
self.get_managed()
}
}
@ -143,7 +159,7 @@ pub fn busy_wait_for_vblank() {
while VCOUNT.get() < 160 {}
}
#[derive(BitfieldSpecifier, Clone, Copy)]
#[derive(BitfieldSpecifier, Clone, Copy, Debug)]
pub enum Priority {
P0 = 0,
P1 = 1,

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,87 @@
use core::cell::Cell;
use alloc::rc::Rc;
use crate::display::affine::AffineMatrixObject;
#[derive(Debug)]
struct AffineMatrixData {
frame_count: Cell<u32>,
location: Cell<u32>,
matrix: AffineMatrixObject,
}
#[derive(Debug, Clone)]
pub(crate) struct AffineMatrixVram(Rc<AffineMatrixData>);
/// An affine matrix that can be used on objects. It is just in time copied to
/// vram, so you can have as many as you like of these but you can only use up
/// to 16 in one frame. They are reference counted (Cloning is cheap) and
/// immutable, if you want to change a matrix you must make a new one and set it
/// on all your objects.
#[derive(Debug, Clone)]
pub struct AffineMatrixInstance {
location: AffineMatrixVram,
}
impl AffineMatrixInstance {
#[must_use]
/// Creates an instance of an affine matrix from its object form. Check out
/// the docs for [AffineMatrix][crate::display::affine::AffineMatrix] to see
/// how you can use them to create effects.
pub fn new(affine_matrix: AffineMatrixObject) -> AffineMatrixInstance {
AffineMatrixInstance {
location: AffineMatrixVram(Rc::new(AffineMatrixData {
frame_count: Cell::new(u32::MAX),
location: Cell::new(u32::MAX),
matrix: affine_matrix,
})),
}
}
pub(crate) fn vram(self) -> AffineMatrixVram {
self.location
}
}
impl AffineMatrixVram {
pub fn frame_count(&self) -> u32 {
self.0.frame_count.get()
}
pub fn set_frame_count(&self, frame: u32) {
self.0.frame_count.set(frame);
}
pub fn location(&self) -> u32 {
self.0.location.get()
}
pub fn set_location(&self, location: u32) {
self.0.location.set(location);
}
pub fn write_to_location(&self, oam: *mut u16) {
let components = self.0.matrix.components();
let location = self.0.location.get() as usize;
for (idx, component) in components.iter().enumerate() {
unsafe {
oam.add(location * 16 + idx * 4 + 3)
.write_volatile(*component);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn niche_optimisation(_gba: &mut crate::Gba) {
assert_eq!(
core::mem::size_of::<AffineMatrixInstance>(),
core::mem::size_of::<Option<AffineMatrixInstance>>()
);
}
}

View file

@ -0,0 +1,511 @@
use core::cell::{Cell, UnsafeCell};
use agb_fixnum::Vector2D;
use crate::{
arena::{Arena, ArenaKey},
display::Priority,
};
use super::{
AffineMatrixInstance, AffineMode, OamUnmanaged, ObjectUnmanaged, Sprite, SpriteLoader,
SpriteVram,
};
type ObjectKey = ArenaKey;
#[derive(Clone, Copy)]
struct Ordering {
next: Option<ObjectKey>,
previous: Option<ObjectKey>,
}
struct ObjectItem {
object: UnsafeCell<ObjectUnmanaged>,
z_order: Cell<Ordering>,
z_index: Cell<i32>,
}
struct Store {
store: UnsafeCell<Arena<ObjectItem>>,
first_z: Cell<Option<ObjectKey>>,
}
struct StoreIterator<'store> {
store: &'store Arena<ObjectItem>,
current: Option<ObjectKey>,
}
impl<'store> Iterator for StoreIterator<'store> {
type Item = &'store ObjectItem;
fn next(&mut self) -> Option<Self::Item> {
let to_output = unsafe { self.store.get(self.current?) };
self.current = to_output.z_order.get().next;
Some(to_output)
}
}
impl Store {
/// SAFETY: while this exists, no other store related operations should be
/// performed. Notably this means you shouldn't drop the ObjectItem as this
/// implementation will touch this.
unsafe fn iter(&self) -> StoreIterator {
StoreIterator {
store: unsafe { &*self.store.get() },
current: self.first_z.get(),
}
}
#[cfg(test)]
fn is_all_ordered_right(&self) -> bool {
let mut previous_z = i32::MIN;
let mut current_index = self.first_z.get();
while let Some(ci) = current_index {
let obj = self.get_object(ci);
let this_z = obj.z_index.get();
if this_z < previous_z {
return false;
}
previous_z = this_z;
current_index = obj.z_order.get().next;
}
true
}
fn insert_object(&self, object: ObjectUnmanaged) -> Object {
let object_item = ObjectItem {
object: UnsafeCell::new(object),
z_order: Cell::new(Ordering {
next: None,
previous: None,
}),
z_index: Cell::new(0),
};
let idx = {
let data = unsafe { &mut *self.store.get() };
unsafe { data.insert(object_item) }
};
if let Some(first) = self.first_z.get() {
let mut this_index = first;
while self.get_object(this_index).z_index.get() < 0 {
if let Some(idx) = self.get_object(this_index).z_order.get().next {
this_index = idx;
} else {
break;
}
}
if self.get_object(this_index).z_index.get() < 0 {
add_after_element(self, idx, this_index);
} else {
add_before_element(self, idx, this_index);
}
} else {
self.first_z.set(Some(idx));
}
Object {
me: idx,
store: self,
}
}
fn remove_object(&self, object: ObjectKey) {
remove_from_linked_list(self, object);
let data = unsafe { &mut *self.store.get() };
unsafe { data.remove(object) };
}
fn get_object(&self, key: ObjectKey) -> &ObjectItem {
unsafe { (*self.store.get()).get(key) }
}
}
/// OAM that manages z ordering and commit all visible objects in one call. This
/// is simpler to use than the [`OamUnmanaged`], but is less performant
/// depending on how objects are stored.
///
/// Use this if:
/// * You don't want to handle z ordering.
/// * You don't want to deal with the complexity of committing all objects during vblank.
///
/// Otherwise I'd recommend using [`OamUnmanaged`].
pub struct OamManaged<'gba> {
object_store: Store,
sprite_loader: UnsafeCell<SpriteLoader>,
unmanaged: UnsafeCell<OamUnmanaged<'gba>>,
}
impl OamManaged<'_> {
pub(crate) fn new() -> Self {
Self {
object_store: Store {
store: UnsafeCell::new(Arena::new()),
first_z: Cell::new(None),
},
sprite_loader: UnsafeCell::new(SpriteLoader::new()),
unmanaged: UnsafeCell::new(OamUnmanaged::new()),
}
}
/// SAFETY:
/// Do not reenter or recurse or otherwise use sprite loader cell during this.
unsafe fn do_work_with_sprite_loader<C, T>(&self, c: C) -> T
where
C: Fn(&mut SpriteLoader) -> T,
{
let sprite_loader = unsafe { &mut *self.sprite_loader.get() };
c(sprite_loader)
}
/// Commits all the visible objects. Call during vblank to make changes made
/// to objects visible.
pub fn commit(&self) {
// safety: commit is not reentrant
let unmanaged = unsafe { &mut *self.unmanaged.get() };
for (object, slot) in unsafe { self.object_store.iter() }
.map(|item| unsafe { &*item.object.get() })
.filter(|object| object.is_visible())
.zip(unmanaged.iter())
{
slot.set(object);
}
// safety: not reentrant
unsafe {
self.do_work_with_sprite_loader(SpriteLoader::garbage_collect);
}
}
/// Creates an object from the sprite in vram.
pub fn object(&self, sprite: SpriteVram) -> Object<'_> {
self.object_store
.insert_object(ObjectUnmanaged::new(sprite))
}
/// Creates a sprite in vram from a static sprite from [`include_aseprite`][crate::include_aseprite].
pub fn sprite(&self, sprite: &'static Sprite) -> SpriteVram {
// safety: not reentrant
unsafe {
self.do_work_with_sprite_loader(|sprite_loader| sprite_loader.get_vram_sprite(sprite))
}
}
/// Creates a sprite in vram and uses it to make an object from a static sprite from [`include_aseprite`][crate::include_aseprite].
pub fn object_sprite(&self, sprite: &'static Sprite) -> Object<'_> {
self.object(self.sprite(sprite))
}
}
/// A managed object used with the [`OamManaged`] interface.
pub struct Object<'controller> {
me: ObjectKey,
store: &'controller Store,
}
impl Drop for Object<'_> {
fn drop(&mut self) {
self.store.remove_object(self.me);
}
}
fn remove_from_linked_list(store: &Store, to_remove: ObjectKey) {
let my_current_neighbours = store.get_object(to_remove).z_order.get();
if let Some(previous) = my_current_neighbours.previous {
let stored_part = &store.get_object(previous).z_order;
let mut neighbour_left = stored_part.get();
neighbour_left.next = my_current_neighbours.next;
stored_part.set(neighbour_left);
} else {
store.first_z.set(my_current_neighbours.next);
}
if let Some(next) = my_current_neighbours.next {
let stored_part = &store.get_object(next).z_order;
let mut neighbour_right = stored_part.get();
neighbour_right.previous = my_current_neighbours.previous;
stored_part.set(neighbour_right);
}
store.get_object(to_remove).z_order.set(Ordering {
next: None,
previous: None,
});
}
fn add_before_element(store: &Store, elem: ObjectKey, before_this: ObjectKey) {
assert_ne!(elem, before_this);
let this_element_store = &store.get_object(elem).z_order;
let mut this_element = this_element_store.get();
let before_store = &store.get_object(before_this).z_order;
let mut before = before_store.get();
if let Some(previous) = before.previous {
let neighbour_left_store = &store.get_object(previous).z_order;
let mut neighbour_left = neighbour_left_store.get();
neighbour_left.next = Some(elem);
neighbour_left_store.set(neighbour_left);
} else {
store.first_z.set(Some(elem));
}
this_element.next = Some(before_this);
this_element.previous = before.previous;
before.previous = Some(elem);
this_element_store.set(this_element);
before_store.set(before);
}
fn add_after_element(store: &Store, elem: ObjectKey, after_this: ObjectKey) {
assert_ne!(elem, after_this);
let this_element_store = &store.get_object(elem).z_order;
let mut this_element = this_element_store.get();
let after_store = &store.get_object(after_this).z_order;
let mut after = after_store.get();
if let Some(next) = after.next {
let neighbour_left_store = &store.get_object(next).z_order;
let mut neighbour_right = neighbour_left_store.get();
neighbour_right.previous = Some(elem);
neighbour_left_store.set(neighbour_right);
}
this_element.previous = Some(after_this);
this_element.next = after.next;
after.next = Some(elem);
this_element_store.set(this_element);
after_store.set(after);
}
fn move_before(store: &Store, source: ObjectKey, before_this: ObjectKey) {
assert_ne!(source, before_this);
remove_from_linked_list(store, source);
add_before_element(store, source, before_this);
}
fn move_after(store: &Store, source: ObjectKey, after_this: ObjectKey) {
assert_ne!(source, after_this);
remove_from_linked_list(store, source);
add_after_element(store, source, after_this);
}
impl Object<'_> {
/// Sets the z position of an object. This is not a GBA concept. It causes
/// the order of rendering to be different, thus changing whether objects
/// are rendered above eachother.
///
/// Negative z is more towards the outside and positive z is further into
/// the screen => an object with a more *negative* z is drawn on top of an
/// object with a more *positive* z.
pub fn set_z(&mut self, z_index: i32) -> &mut Self {
let my_object = &self.store.get_object(self.me);
let order = z_index.cmp(&my_object.z_index.get());
match order {
core::cmp::Ordering::Equal => {}
core::cmp::Ordering::Less => {
let mut previous_index = self.me;
let mut current_index = self.me;
while self.store.get_object(current_index).z_index.get() > z_index {
previous_index = current_index;
let previous = self.store.get_object(current_index).z_order.get().previous;
if let Some(previous) = previous {
current_index = previous;
} else {
break;
}
}
if previous_index != self.me {
move_before(self.store, self.me, previous_index);
}
}
core::cmp::Ordering::Greater => {
let mut previous_index = self.me;
let mut current_index = self.me;
while self.store.get_object(current_index).z_index.get() < z_index {
previous_index = current_index;
let next = self.store.get_object(current_index).z_order.get().next;
if let Some(next) = next {
current_index = next;
} else {
break;
}
}
if previous_index != self.me {
move_after(self.store, self.me, previous_index);
}
}
}
my_object.z_index.set(z_index);
self
}
/// Safety:
/// Only have *ONE* of these at a time, do not call any functions that modify the slot map while having this.
unsafe fn object(&mut self) -> &mut ObjectUnmanaged {
unsafe { &mut *self.store.get_object(self.me).object.get() }
}
/// Safety:
/// Don't have a mutable one of these while having one of these, do not call any functions that modify the slot map while having this.
unsafe fn object_shared(&self) -> &ObjectUnmanaged {
unsafe { &*self.store.get_object(self.me).object.get() }
}
#[must_use]
/// Checks whether the object is not marked as hidden. Note that it could be
/// off screen or completely transparent and still claimed to be visible.
pub fn is_visible(&self) -> bool {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object_shared() }.is_visible()
}
/// Display the sprite in Normal mode.
pub fn show(&mut self) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().show() };
self
}
/// Display the sprite in Affine mode.
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().show_affine(affine_mode) };
self
}
/// Sets the horizontal flip, note that this only has a visible affect in Normal mode.
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_hflip(flip) };
self
}
/// Sets the vertical flip, note that this only has a visible affect in Normal mode.
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_vflip(flip) };
self
}
/// Sets the priority of the object relative to the backgrounds priority.
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_priority(priority) };
self
}
/// Changes the sprite mode to be hidden, can be changed to Normal or Affine
/// modes using [`show`][Object::show] and
/// [`show_affine`][Object::show_affine] respectively.
pub fn hide(&mut self) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().hide() };
self
}
/// Sets the x position of the object.
pub fn set_x(&mut self, x: u16) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_x(x) };
self
}
/// Sets the y position of the object.
pub fn set_y(&mut self, y: u16) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_y(y) };
self
}
/// Sets the position of the object.
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_position(position) };
self
}
/// Sets the affine matrix. This only has an affect in Affine mode.
pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_affine_matrix(affine_matrix) };
self
}
/// Sets the current sprite for the object.
pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self {
// safety: only have one of these, doesn't modify slotmap
unsafe { self.object().set_sprite(sprite) };
self
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use crate::{display::object::Graphics, include_aseprite};
use super::*;
const TEST_SPRITES: &Graphics = include_aseprite!("examples/gfx/tall.aseprite");
const TEST_SPRITE: &Sprite = &TEST_SPRITES.sprites()[0];
#[test_case]
fn test_always_ordered(gba: &mut crate::Gba) {
let managed = gba.display.object.get_managed();
let sprite = managed.sprite(TEST_SPRITE);
let mut objects = Vec::new();
for _ in 0..200 {
let obj = managed.object(sprite.clone());
objects.push(obj);
}
for modification_number in 0..10_000 {
let index_to_modify = (crate::rng::gen() as usize) % objects.len();
let modify_to = crate::rng::gen();
objects[index_to_modify].set_z(modify_to);
assert!(
managed.object_store.is_all_ordered_right(),
"objects are unordered after {} modifications. Modified {} to {}.",
modification_number + 1,
index_to_modify,
modify_to
);
}
}
}

View file

@ -0,0 +1,7 @@
mod sprite;
mod sprite_allocator;
const BYTES_PER_TILE_4BPP: usize = 32;
pub use sprite::{include_aseprite, Graphics, Size, Sprite, Tag, TagMap};
pub use sprite_allocator::{DynamicSprite, PaletteVram, SpriteLoader, SpriteVram};

View file

@ -0,0 +1,370 @@
use core::{alloc::Layout, slice};
use crate::display::palette16::Palette16;
use super::BYTES_PER_TILE_4BPP;
/// Sprite data. Refers to the palette, pixel data, and the size of the sprite.
pub struct Sprite {
pub(crate) palette: &'static Palette16,
pub(crate) data: &'static [u8],
pub(crate) size: Size,
}
impl Sprite {
#[doc(hidden)]
/// Creates a sprite from it's constituent data, used internally by
/// [include_aseprite] and should generally not be used outside it.
///
/// # Safety
/// The data should be aligned to a 2 byte boundary
#[must_use]
pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self {
Self {
palette,
data,
size,
}
}
#[must_use]
/// Gives the size of the sprite
pub fn size(&self) -> Size {
self.size
}
}
/// The sizes of sprite supported by the GBA.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[allow(missing_docs)]
pub enum Size {
// stored as attr0 attr1
S8x8 = 0b00_00,
S16x16 = 0b00_01,
S32x32 = 0b00_10,
S64x64 = 0b00_11,
S16x8 = 0b01_00,
S32x8 = 0b01_01,
S32x16 = 0b01_10,
S64x32 = 0b01_11,
S8x16 = 0b10_00,
S8x32 = 0b10_01,
S16x32 = 0b10_10,
S32x64 = 0b10_11,
}
#[doc(hidden)]
#[macro_export]
macro_rules! align_bytes {
($align_ty:ty, $data:literal) => {{
#[repr(C)] // guarantee 'bytes' comes after '_align'
struct AlignedAs<Align, Bytes: ?Sized> {
pub _align: [Align; 0],
pub bytes: Bytes,
}
const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs {
_align: [],
bytes: *$data,
};
&ALIGNED.bytes
}};
}
/// Includes sprites found in the referenced aseprite files. Can include
/// multiple at once and optimises palettes of all included in the single call
/// together. See [Size] for supported sizes. Returns a reference to [Graphics].
///
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// # use agb::{display::object::Graphics, include_aseprite};
/// const GRAPHICS: &Graphics = include_aseprite!(
/// "examples/gfx/boss.aseprite",
/// "examples/gfx/objects.aseprite"
/// );
/// ```
/// The tags from the aseprite file are included so you can refer to sprites by
/// name in code. You should ensure tags are unique as this is not enforced by
/// aseprite.
///
#[macro_export]
macro_rules! include_aseprite {
($($aseprite_path: expr),*) => {{
use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics};
use $crate::display::palette16::Palette16;
use $crate::align_bytes;
$crate::include_aseprite_inner!($($aseprite_path),*);
&Graphics::new(SPRITES, TAGS)
}};
}
pub use include_aseprite;
/// Stores sprite and tag data returned by [include_aseprite].
pub struct Graphics {
sprites: &'static [Sprite],
tag_map: &'static TagMap,
}
impl Graphics {
#[doc(hidden)]
/// Creates graphics data from sprite data and a tag_map. This is used
/// internally by [include_aseprite] and would be otherwise difficult to
/// use.
#[must_use]
pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self {
Self { sprites, tag_map }
}
#[must_use]
/// Gets the tag map from the aseprite files. This allows reference to
/// sprite sequences by name.
pub const fn tags(&self) -> &TagMap {
self.tag_map
}
/// Gets a big list of the sprites themselves. Using tags is often easier.
#[must_use]
pub const fn sprites(&self) -> &[Sprite] {
self.sprites
}
}
/// Stores aseprite tags. Can be used to refer to animation sequences by name.
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// # use agb::{display::object::{Graphics, Tag}, include_aseprite};
/// const GRAPHICS: &Graphics = include_aseprite!(
/// "examples/gfx/boss.aseprite",
/// "examples/gfx/objects.aseprite"
/// );
///
/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk");
/// ```
/// This being the whole animation associated with the walk sequence of the emu.
/// See [Tag] for details on how to use this.
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;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
impl TagMap {
#[doc(hidden)]
/// Creates a new tag map from (name, Tag) pairs. Used internally by
/// [include_aseprite] and should not really be used outside of it.
#[must_use]
pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap {
Self { tags }
}
#[doc(hidden)]
/// Attempts to get a tag. Generally should not be used.
#[must_use]
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);
}
i += 1;
}
None
}
/// Gets a tag associated with the name. A tag in aseprite refers to a
/// sequence of sprites with some metadata for how to animate it. You should
/// call this in a constant context so it is evaluated at compile time. It
/// is inefficient to call this elsewhere.
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// # use agb::{display::object::{Graphics, Tag}, include_aseprite};
/// const GRAPHICS: &Graphics = include_aseprite!(
/// "examples/gfx/boss.aseprite",
/// "examples/gfx/objects.aseprite"
/// );
///
/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk");
/// ```
///
/// See [Tag] for more details.
#[must_use]
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"),
}
}
/// Takes an iterator over all the tags in the map. Not generally useful.
pub fn values(&self) -> impl Iterator<Item = &'static Tag> {
self.tags.iter().map(|x| &x.1)
}
}
#[derive(Clone, Copy)]
enum Direction {
Forward,
Backward,
PingPong,
}
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"),
}
}
}
/// A sequence of sprites from aseprite.
pub struct Tag {
sprites: *const Sprite,
len: usize,
direction: Direction,
}
impl Tag {
/// The individual sprites that make up the animation themselves.
#[must_use]
pub fn sprites(&self) -> &'static [Sprite] {
unsafe { slice::from_raw_parts(self.sprites, self.len) }
}
/// A single sprite referred to by index in the animation sequence.
#[must_use]
pub const fn sprite(&self, idx: usize) -> &'static Sprite {
if idx >= self.len {
panic!("out of bounds access to sprite");
}
unsafe { &*self.sprites.add(idx) }
}
/// A sprite that follows the animation sequence. For instance, in aseprite
/// tags can be specified to animate:
/// * Forward
/// * Backward
/// * Ping pong
///
/// This takes the animation type in account and returns the correct sprite
/// following these requirements.
#[inline]
#[must_use]
pub fn animation_sprite(&self, idx: usize) -> &'static Sprite {
let len_sub_1 = self.len - 1;
match self.direction {
Direction::Forward => self.sprite(idx % self.len),
Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)),
Direction::PingPong => self.sprite(
(((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize)
.unsigned_abs(),
),
}
}
#[doc(hidden)]
/// Creates a new sprite from it's constituent parts. Used internally by
/// [include_aseprite] and should generally not be used elsewhere.
#[must_use]
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 Size {
pub(crate) 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,
}
}
pub(crate) const fn shape_size(self) -> (u16, u16) {
(self as u16 >> 2, self as u16 & 0b11)
}
pub(crate) fn layout(self) -> Layout {
Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap()
}
#[must_use]
/// Creates a size from width and height in pixels, panics if the width and
/// height is not representable by GBA sprites.
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!"),
}
}
#[must_use]
/// Returns the width and height of the size in pixels.
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),
}
}
}

View file

@ -0,0 +1,324 @@
use core::ptr::NonNull;
use alloc::rc::{Rc, Weak};
use crate::{
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
display::palette16::Palette16,
hash_map::HashMap,
};
use super::{
sprite::{Size, Sprite},
BYTES_PER_TILE_4BPP,
};
const PALETTE_SPRITE: usize = 0x0500_0200;
const TILE_SPRITE: usize = 0x06010000;
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || TILE_SPRITE,
end: || TILE_SPRITE + 1024 * 8 * 4,
})
};
static PALETTE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || PALETTE_SPRITE,
end: || PALETTE_SPRITE + 0x200,
})
};
/// 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 from_static_sprite(sprite: &'static Sprite) -> SpriteId {
SpriteId(sprite as *const _ as usize)
}
}
/// 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 from_static_palette(palette: &'static Palette16) -> PaletteId {
PaletteId(palette as *const _ as usize)
}
}
/// This holds loading of static sprites and palettes.
pub struct SpriteLoader {
static_palette_map: HashMap<PaletteId, Weak<PaletteVramData>>,
static_sprite_map: HashMap<SpriteId, Weak<SpriteVramData>>,
}
#[derive(Clone, Copy, Debug)]
struct Location(usize);
impl Location {
fn from_sprite_ptr(d: NonNull<u8>) -> Self {
Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP)
}
fn from_palette_ptr(d: NonNull<u8>) -> Self {
Self((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size())
}
fn as_palette_ptr(self) -> *mut u8 {
(self.0 * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8
}
fn as_sprite_ptr(self) -> *mut u8 {
(self.0 * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8
}
}
#[derive(Debug)]
struct PaletteVramData {
location: Location,
}
impl Drop for PaletteVramData {
fn drop(&mut self) {
unsafe { PALETTE_ALLOCATOR.dealloc(self.location.as_palette_ptr(), Palette16::layout()) }
}
}
/// A palette in vram, this is reference counted so it is cheap to Clone.
#[derive(Debug, Clone)]
pub struct PaletteVram {
data: Rc<PaletteVramData>,
}
impl PaletteVram {
/// Attempts to allocate a new palette in sprite vram
pub fn new(palette: &Palette16) -> Result<PaletteVram, LoaderError> {
let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) }
.ok_or(LoaderError::PaletteFull)?;
unsafe {
allocated
.as_ptr()
.cast::<u16>()
.copy_from_nonoverlapping(palette.colours.as_ptr(), palette.colours.len());
}
Ok(PaletteVram {
data: Rc::new(PaletteVramData {
location: Location::from_palette_ptr(allocated),
}),
})
}
}
#[derive(Debug)]
struct SpriteVramData {
location: Location,
size: Size,
palette: PaletteVram,
}
impl Drop for SpriteVramData {
fn drop(&mut self) {
unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) }
}
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LoaderError {
SpriteFull,
PaletteFull,
}
/// A sprite that is currently loaded into vram.
///
/// This is referenced counted such that clones of this are cheap and can be
/// reused between objects. When nothing references the sprite it gets
/// deallocated from vram.
///
/// You can create one of these either via the [DynamicSprite] interface, which
/// allows you to generate sprites at run time, or via a [SpriteLoader] (or
/// [OamManaged][super::super::OamManaged]).
#[derive(Clone, Debug)]
pub struct SpriteVram {
data: Rc<SpriteVramData>,
}
impl SpriteVram {
fn new(data: &[u8], size: Size, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
let allocated =
unsafe { SPRITE_ALLOCATOR.alloc(size.layout()) }.ok_or(LoaderError::SpriteFull)?;
unsafe {
allocated
.as_ptr()
.copy_from_nonoverlapping(data.as_ptr(), data.len());
}
Ok(SpriteVram {
data: Rc::new(SpriteVramData {
location: Location::from_sprite_ptr(allocated),
size,
palette,
}),
})
}
pub(crate) fn location(&self) -> u16 {
self.data.location.0 as u16
}
pub(crate) fn size(&self) -> Size {
self.data.size
}
pub(crate) fn palette_location(&self) -> u16 {
self.data.palette.data.location.0 as u16
}
}
impl SpriteLoader {
fn create_sprite_no_insert(
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
sprite: &'static Sprite,
) -> Result<(Weak<SpriteVramData>, SpriteVram), LoaderError> {
let palette = Self::try_get_vram_palette_asoc(palette_map, sprite.palette)?;
let sprite = SpriteVram::new(sprite.data, sprite.size, palette)?;
Ok((Rc::downgrade(&sprite.data), sprite))
}
fn try_get_vram_palette_asoc(
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
palette: &'static Palette16,
) -> Result<PaletteVram, LoaderError> {
let id = PaletteId::from_static_palette(palette);
Ok(match palette_map.entry(id) {
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
Some(data) => PaletteVram { data },
None => {
let pv = PaletteVram::new(palette)?;
entry.insert(Rc::downgrade(&pv.data));
pv
}
},
crate::hash_map::Entry::Vacant(entry) => {
let pv = PaletteVram::new(palette)?;
entry.insert(Rc::downgrade(&pv.data));
pv
}
})
}
/// Attempts to get a sprite
pub fn try_get_vram_sprite(
&mut self,
sprite: &'static Sprite,
) -> Result<SpriteVram, LoaderError> {
// check if we already have the sprite in vram
let id = SpriteId::from_static_sprite(sprite);
Ok(match self.static_sprite_map.entry(id) {
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
Some(data) => SpriteVram { data },
None => {
let (weak, vram) =
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
entry.insert(weak);
vram
}
},
crate::hash_map::Entry::Vacant(entry) => {
let (weak, vram) =
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
entry.insert(weak);
vram
}
})
}
/// Attempts to allocate a static palette
pub fn try_get_vram_palette(
&mut self,
palette: &'static Palette16,
) -> Result<PaletteVram, LoaderError> {
Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette)
}
/// Allocates a sprite to vram, panics if it cannot fit.
pub fn get_vram_sprite(&mut self, sprite: &'static Sprite) -> SpriteVram {
self.try_get_vram_sprite(sprite)
.expect("cannot create sprite")
}
/// Allocates a palette to vram, panics if it cannot fit.
pub fn get_vram_palette(&mut self, palette: &'static Palette16) -> PaletteVram {
self.try_get_vram_palette(palette)
.expect("cannot create sprite")
}
pub(crate) fn new() -> Self {
Self {
static_palette_map: HashMap::new(),
static_sprite_map: HashMap::new(),
}
}
/// Remove internal references to sprites that no longer exist in vram. If
/// you neglect calling this, memory will leak over time in relation to the
/// total number of different sprites used. It will not leak vram.
pub fn garbage_collect(&mut self) {
self.static_sprite_map
.retain(|_, v| Weak::strong_count(v) != 0);
self.static_palette_map
.retain(|_, v| Weak::strong_count(v) != 0);
}
}
impl Default for SpriteLoader {
fn default() -> Self {
Self::new()
}
}
/// Sprite data that can be used to create sprites in vram.
pub struct DynamicSprite<'a> {
data: &'a [u8],
size: Size,
}
impl DynamicSprite<'_> {
#[must_use]
/// Creates a new dynamic sprite from underlying bytes. Note that despite
/// being an array of u8, this must be aligned to at least a 2 byte
/// boundary.
pub fn new(data: &[u8], size: Size) -> DynamicSprite {
let ptr = &data[0] as *const _ as usize;
if ptr % 2 != 0 {
panic!("data is not aligned to a 2 byte boundary");
}
if data.len() != size.number_of_tiles() * BYTES_PER_TILE_4BPP {
panic!(
"data is not of expected length, got {} expected {}",
data.len(),
size.number_of_tiles() * BYTES_PER_TILE_4BPP
);
}
DynamicSprite { data, size }
}
/// Tries to copy the sprite to vram to be used to set object sprites.
pub fn try_vram(&self, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
SpriteVram::new(self.data, self.size, palette)
}
#[must_use]
/// Tries to copy the sprite to vram to be used to set object sprites.
/// Panics if it cannot be allocated.
pub fn to_vram(&self, palette: PaletteVram) -> SpriteVram {
self.try_vram(palette).expect("cannot create sprite")
}
}

View file

@ -0,0 +1,5 @@
mod attributes;
mod object;
pub use attributes::AffineMode;
pub use object::{OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged};

View file

@ -0,0 +1,210 @@
use modular_bitfield::BitfieldSpecifier;
use crate::display::Priority;
use self::attributes::{
ObjectAttribute0, ObjectAttribute1Affine, ObjectAttribute1Standard, ObjectAttribute2,
};
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct Attributes {
a0: ObjectAttribute0,
a1s: ObjectAttribute1Standard,
a1a: ObjectAttribute1Affine,
a2: ObjectAttribute2,
}
impl Default for Attributes {
fn default() -> Self {
Self {
a0: ObjectAttribute0::from_bytes([0, 0b10]),
a1s: Default::default(),
a1a: Default::default(),
a2: Default::default(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// The affine mode
pub enum AffineMode {
/// Normal affine, this is where the area of the affine is equal to the sprite size
Affine = 1,
/// Double affine, this is where the area of the affine is double that of the sprite
AffineDouble = 3,
}
impl Attributes {
pub fn write(self, ptr: *mut u16) {
let mode = self.a0.object_mode();
unsafe {
let attrs = core::mem::transmute::<_, [u16; 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(),
],
});
ptr.add(0).write_volatile(attrs[0]);
ptr.add(1).write_volatile(attrs[1]);
ptr.add(2).write_volatile(attrs[2]);
}
}
pub fn is_visible(self) -> bool {
self.a0.object_mode() != ObjectMode::Disabled
}
pub fn show(&mut self) -> &mut Self {
self.a0.set_object_mode(ObjectMode::Normal);
self
}
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
self.a0.set_object_mode(match affine_mode {
AffineMode::Affine => ObjectMode::Affine,
AffineMode::AffineDouble => ObjectMode::AffineDouble,
});
self
}
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
self.a1s.set_horizontal_flip(flip);
self
}
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
self.a1s.set_vertical_flip(flip);
self
}
pub fn set_x(&mut self, x: u16) -> &mut Self {
self.a1a.set_x(x.rem_euclid(1 << 9));
self.a1s.set_x(x.rem_euclid(1 << 9));
self
}
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
self.a2.set_priority(priority);
self
}
pub fn hide(&mut self) -> &mut Self {
self.a0.set_object_mode(ObjectMode::Disabled);
self
}
pub fn set_y(&mut self, y: u16) -> &mut Self {
self.a0.set_y(y as u8);
self
}
pub fn set_palette(&mut self, palette_id: u16) -> &mut Self {
self.a2.set_palette_bank(palette_id as u8);
self
}
pub fn set_affine_matrix(&mut self, affine_matrix_id: u16) -> &mut Self {
self.a1a.set_affine_index(affine_matrix_id as u8);
self
}
pub fn set_sprite(&mut self, sprite_id: u16, shape: u16, size: u16) -> &mut Self {
self.a2.set_tile_index(sprite_id);
self.a1a.set_size(size as u8);
self.a1s.set_size(size as u8);
self.a0.set_shape(shape as u8);
self
}
}
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
enum ObjectMode {
Normal,
Affine,
Disabled,
AffineDouble,
}
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
#[bits = 2]
enum GraphicsMode {
Normal,
AlphaBlending,
Window,
}
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
enum ColourMode {
Four,
Eight,
}
// this mod is not public, so the internal parts don't need documenting.
#[allow(dead_code)]
#[allow(clippy::all)]
#[allow(clippy::map_unwrap_or)]
mod attributes {
use modular_bitfield::{
bitfield,
specifiers::{B10, B2, B3, B4, B5, B8, B9},
};
use crate::display::Priority;
use super::*;
#[bitfield]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
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, PartialEq, Eq, Debug, Default)]
pub(super) struct ObjectAttribute1Standard {
pub x: B9,
#[skip]
__: B3,
pub horizontal_flip: bool,
pub vertical_flip: bool,
pub size: B2,
}
#[bitfield]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(super) struct ObjectAttribute1Affine {
pub x: B9,
pub affine_index: B5,
pub size: B2,
}
#[bitfield]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(super) struct ObjectAttribute2 {
pub tile_index: B10,
pub priority: Priority,
pub palette_bank: B4,
}
}

View file

@ -0,0 +1,375 @@
use core::{cell::UnsafeCell, marker::PhantomData};
use agb_fixnum::Vector2D;
use alloc::vec::Vec;
use crate::display::{
object::{
affine::AffineMatrixVram, sprites::SpriteVram, AffineMatrixInstance,
OBJECT_ATTRIBUTE_MEMORY,
},
Priority,
};
use super::attributes::{AffineMode, Attributes};
#[derive(Debug)]
struct OamFrameModifyables {
this_frame_sprites: Vec<SpriteVram>,
frame: u32,
affine_matrix_count: u32,
previous_index: usize,
}
/// This handles the unmanaged oam system which gives more control to the OAM slots.
/// This is utilised by calling the iter function and writing objects to those slots.
pub struct OamUnmanaged<'gba> {
phantom: PhantomData<&'gba ()>,
frame_data: UnsafeCell<OamFrameModifyables>,
previous_frame_sprites: Vec<SpriteVram>,
}
/// The iterator over the OAM slots. Dropping this will finalise the frame. To
/// use, iterate over and write to each slot.
///
/// For example, it could look like this:
///
/// ```no_run
/// # #![no_main]
/// # #![no_std]
/// use agb::display::object::{OamIterator, ObjectUnmanaged};
///
/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) {
/// for (object, slot) in objects.iter().zip(oam_iterator) {
/// slot.set(&object);
/// }
/// }
/// ```
///
/// # Pitfalls
/// You *must* use each OamSlot you obtain, this can be an issue if instead of
/// the above you write
///
/// ```no_run
/// # #![no_main]
/// # #![no_std]
/// use agb::display::object::{OamIterator, ObjectUnmanaged};
///
/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) {
/// for (slot, object) in oam_iterator.zip(objects.iter()) {
/// slot.set(&object);
/// }
/// }
/// ```
///
/// This will panic if called because when you run out of objects the zip will
/// have already grabbed the next OamSlot before realising there are no more
/// objects.
pub struct OamIterator<'oam> {
index: usize,
frame_data: &'oam UnsafeCell<OamFrameModifyables>,
}
/// A slot in Oam that you can write to. Note that you must call [OamSlot::set]
/// or else it is a bug and will panic when dropped.
///
/// See [`OamIterator`] for potential pitfalls.
pub struct OamSlot<'oam> {
slot: usize,
frame_data: &'oam UnsafeCell<OamFrameModifyables>,
}
impl Drop for OamSlot<'_> {
#[track_caller]
fn drop(&mut self) {
panic!("Dropping an OamSlot is a bug in your code. Use the slot by calling set (this consumes the slot) or don't obtain one. See documentation for notes on potential pitfalls.")
}
}
impl OamSlot<'_> {
/// Set the slot in OAM to contain the sprite given.
#[inline(always)]
pub fn set(self, object: &ObjectUnmanaged) {
self.set_inner(object);
// don't call the drop implementation.
// okay as none of the fields we have have drop implementations.
core::mem::forget(self);
}
/// By writing these as two separate functions, one inlined and one not, the
/// compiler doesn't have to copy around the slot structure while still
/// keeping move semantics. This is slightly faster in benchmarks.
#[inline(never)]
fn set_inner(&self, object: &ObjectUnmanaged) {
let mut attributes = object.attributes;
// SAFETY: This function is not reentrant and we currently hold a mutable borrow of the [UnmanagedOAM].
let frame_data = unsafe { &mut *self.frame_data.get() };
if let Some(affine_matrix) = &object.affine_matrix {
Self::handle_affine(&mut attributes, frame_data, affine_matrix);
}
attributes.write(unsafe { (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(self.slot * 4) });
frame_data.this_frame_sprites.push(object.sprite.clone());
}
fn handle_affine(
attributes: &mut Attributes,
frame_data: &mut OamFrameModifyables,
affine_matrix: &AffineMatrixVram,
) {
if affine_matrix.frame_count() != frame_data.frame {
affine_matrix.set_frame_count(frame_data.frame);
assert!(
frame_data.affine_matrix_count <= 32,
"too many affine matricies in one frame"
);
affine_matrix.set_location(frame_data.affine_matrix_count);
frame_data.affine_matrix_count += 1;
affine_matrix.write_to_location(OBJECT_ATTRIBUTE_MEMORY);
}
attributes.set_affine_matrix(affine_matrix.location() as u16);
}
}
impl<'oam> Iterator for OamIterator<'oam> {
type Item = OamSlot<'oam>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let idx = self.index;
if idx == 128 {
None
} else {
self.index += 1;
Some(OamSlot {
slot: idx,
frame_data: self.frame_data,
})
}
}
}
impl Drop for OamIterator<'_> {
fn drop(&mut self) {
let number_writen = self.index;
let last_frame_written = unsafe { &mut (*self.frame_data.get()).previous_index };
for idx in number_writen..*last_frame_written {
unsafe {
let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(idx * 4);
ptr.write_volatile(0b10 << 8);
}
}
*last_frame_written = number_writen;
}
}
impl OamUnmanaged<'_> {
/// Returns the OamSlot iterator for this frame.
pub fn iter(&mut self) -> OamIterator<'_> {
let frame_data = self.frame_data.get_mut();
frame_data.frame = frame_data.frame.wrapping_add(1);
frame_data.affine_matrix_count = 0;
// We drain the previous frame sprites here to reuse the Vecs allocation and remove the now unused sprites.
// Any sprites currently being shown will now be put in the new Vec.
self.previous_frame_sprites.clear();
core::mem::swap(
&mut frame_data.this_frame_sprites,
&mut self.previous_frame_sprites,
);
OamIterator {
index: 0,
frame_data: &self.frame_data,
}
}
pub(crate) fn new() -> Self {
Self {
frame_data: UnsafeCell::new(OamFrameModifyables {
this_frame_sprites: Vec::new(),
frame: 0,
affine_matrix_count: 0,
previous_index: 0,
}),
phantom: PhantomData,
previous_frame_sprites: Default::default(),
}
}
}
#[derive(Debug, Clone)]
/// An object to be used by the [`OamUnmanaged`] system. Changes made here are
/// reflected when set to an OamSlot using [`OamSlot::set`].
pub struct ObjectUnmanaged {
attributes: Attributes,
sprite: SpriteVram,
affine_matrix: Option<AffineMatrixVram>,
}
impl ObjectUnmanaged {
#[must_use]
/// Creates an unmanaged object from a sprite in vram.
pub fn new(sprite: SpriteVram) -> Self {
let sprite_location = sprite.location();
let palette_location = sprite.palette_location();
let (shape, size) = sprite.size().shape_size();
let mut sprite = Self {
attributes: Attributes::default(),
sprite,
affine_matrix: None,
};
sprite.attributes.set_sprite(sprite_location, shape, size);
sprite.attributes.set_palette(palette_location);
sprite
}
#[must_use]
/// Checks whether the object is not marked as hidden. Note that it could be
/// off screen or completely transparent and still claimed to be visible.
pub fn is_visible(&self) -> bool {
self.attributes.is_visible()
}
/// Display the sprite in Normal mode.
pub fn show(&mut self) -> &mut Self {
self.attributes.show();
self
}
/// Display the sprite in Affine mode.
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
assert!(
self.affine_matrix.is_some(),
"affine matrix must be set before enabling affine matrix!"
);
self.attributes.show_affine(affine_mode);
self
}
/// Sets the horizontal flip, note that this only has a visible affect in Normal mode.
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
self.attributes.set_hflip(flip);
self
}
/// Sets the vertical flip, note that this only has a visible affect in Normal mode.
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
self.attributes.set_vflip(flip);
self
}
/// Sets the priority of the object relative to the backgrounds priority.
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
self.attributes.set_priority(priority);
self
}
/// Changes the sprite mode to be hidden, can be changed to Normal or Affine
/// modes using [`show`][ObjectUnmanaged::show] and
/// [`show_affine`][ObjectUnmanaged::show_affine] respectively.
pub fn hide(&mut self) -> &mut Self {
self.attributes.hide();
self
}
/// Sets the x position of the object.
pub fn set_x(&mut self, x: u16) -> &mut Self {
self.attributes.set_x(x);
self
}
/// Sets the y position of the object.
pub fn set_y(&mut self, y: u16) -> &mut Self {
self.attributes.set_y(y);
self
}
/// Sets the position of the object.
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
self.set_y(position.y.rem_euclid(1 << 9) as u16);
self.set_x(position.x.rem_euclid(1 << 9) as u16);
self
}
/// Sets the affine matrix. This only has an affect in Affine mode.
pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self {
let vram = affine_matrix.vram();
self.affine_matrix = Some(vram);
self
}
fn set_sprite_attributes(&mut self, sprite: &SpriteVram) -> &mut Self {
let size = sprite.size();
let (shape, size) = size.shape_size();
self.attributes.set_sprite(sprite.location(), shape, size);
self.attributes.set_palette(sprite.palette_location());
self
}
/// Sets the current sprite for the object.
pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self {
self.set_sprite_attributes(&sprite);
self.sprite = sprite;
self
}
}
#[cfg(test)]
mod tests {
use crate::{
display::object::{Graphics, Tag},
include_aseprite,
};
use super::*;
#[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");
let (mut gfx, mut loader) = gba.display.object.get_unmanaged();
{
let mut slotter = gfx.iter();
let slot_a = slotter.next().unwrap();
let slot_b = slotter.next().unwrap();
let mut obj = ObjectUnmanaged::new(loader.get_vram_sprite(BOSS.sprite(2)));
obj.show();
slot_b.set(&obj);
slot_a.set(&obj);
}
}
}

View file

@ -1,3 +1,5 @@
use core::alloc::Layout;
#[repr(C)]
#[derive(Clone)]
pub struct Palette16 {
@ -12,7 +14,6 @@ impl Palette16 {
// Clippy bug: claims that index is only used in recursion. I can't reproduce in
// other examples, even just copy pasting this struct and impl into a blank project :/
#[allow(clippy::only_used_in_recursion)]
pub fn update_colour(&mut self, index: usize, colour: u16) {
self.colours[index] = colour;
}
@ -21,4 +22,8 @@ impl Palette16 {
pub fn colour(&self, index: usize) -> u16 {
self.colours[index]
}
pub(crate) const fn layout() -> Layout {
Layout::new::<Self>()
}
}

View file

@ -165,6 +165,8 @@ pub mod syscall;
/// Interactions with the internal timers
pub mod timer;
pub(crate) mod arena;
pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator};
#[cfg(not(any(test, feature = "testing")))]

View file

@ -76,11 +76,11 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if",
"once_cell",
"version_check",
]
@ -178,30 +178,19 @@ dependencies = [
[[package]]
name = "fontdue"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
@ -227,12 +216,6 @@ dependencies = [
"png",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "log"
version = "0.4.17"
@ -422,9 +405,3 @@ name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View file

@ -32,7 +32,7 @@ const BALL: &Tag = GRAPHICS.tags().get("Ball");
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
// Get the OAM manager
let object = gba.display.object.get();
let object = gba.display.object.get_managed();
// Create an object with the ball sprite
let mut ball = object.object_sprite(BALL.sprite(0));

View file

@ -0,0 +1,14 @@
[unstable]
build-std = ["core", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[build]
target = "thumbv4t-none-eabi"
[target.thumbv4t-none-eabi]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-qt"
[target.armv4t-none-eabi]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-qt"

407
examples/amplitude/Cargo.lock generated Normal file
View file

@ -0,0 +1,407 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "agb"
version = "0.14.0"
dependencies = [
"agb_fixnum",
"agb_hashmap",
"agb_image_converter",
"agb_macros",
"agb_sound_converter",
"bare-metal",
"bitflags 2.1.0",
"modular-bitfield",
"rustc-hash",
]
[[package]]
name = "agb_fixnum"
version = "0.14.0"
dependencies = [
"agb_macros",
]
[[package]]
name = "agb_hashmap"
version = "0.14.0"
dependencies = [
"rustc-hash",
]
[[package]]
name = "agb_image_converter"
version = "0.14.0"
dependencies = [
"asefile",
"fontdue",
"image",
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "agb_macros"
version = "0.14.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "agb_sound_converter"
version = "0.14.0"
dependencies = [
"hound",
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "amplitude"
version = "0.1.0"
dependencies = [
"agb",
]
[[package]]
name = "asefile"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a71de7aecd2d0a76ec90fde2c443d12667c737d92de76bd187f101eca37891"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"flate2",
"image",
"log",
"nohash",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bare-metal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide 0.6.2",
]
[[package]]
name = "fontdue"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-iter",
"num-rational",
"num-traits",
"png",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
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.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[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 1.0.109",
]
[[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.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"deflate",
"miniz_oxide 0.3.7",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[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.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

View file

@ -0,0 +1,20 @@
[package]
name = "amplitude"
version = "0.1.0"
authors = [""]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
agb = { version = "0.14.0", path = "../../agb" }
[profile.dev]
opt-level = 2
debug = true
[profile.release]
panic = "abort"
lto = true
debug = true
codegen-units = 1

View file

@ -0,0 +1,72 @@
# AGBRS template
## A basic template example for agb projects
This makes getting started with a new project for the Game Boy Advance in rust really simple, by providing
all the boiler plate files for you.
## Building
### Prerequisites
You will need the following installed in order to build and run this project:
* A recent version of `rustup`. See the [rust website](https://www.rust-lang.org/tools/install) for instructions for your operating system
* `arm-none-eabi-binutils` for assembling and linking
* Windows: [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads).
Make sure you select "Add path to environment variable" during the install
* Debian and derivatives (e.g. Ubuntu, raspberry pi OS, linux mint): `sudo apt install binutils-arm-none-eabi`
* Arch linux and derivatives: `sudo pacman -S arm-none-eabi-binutils`
You will also want to install an emulator. The best support in agb is with [mgba](https://mgba.io), with
`println!` support via `agb::println!` but any emulator should work. You'll get the best experience if
`mgba-qt` is in your `PATH`.
If you want to run your game on real hardware, you will also need to install `agb-gbafix` which you can do after installing
rust with the following: `cargo install agb-gbafix`. This is not required if you are only running your game in an emulator.
### Running in an emulator
Once you have the prerequisites installed, you should be able to build using
```sh
cargo build
```
or in release mode (recommended for the final version to ship to players)
```sh
cargo build --release
```
The resulting file will be in `target/thumbv4t-none-eabi/debug/<your game>` or `target/thumbv4t-none-eabi/release/<your game>` depending on
whether you did a release or debug build.
If you have `mgba-qt` in your path, you will be able to run your game with
```sh
cargo run
```
or in release mode
```sh
cargo run --release
```
## Starting development
You can find the documentation for agb [here](https://docs.rs/agb/latest/agb/).
You may also want to change the package name and version in `Cargo.toml` before you start.
## Shipping a .gba file for real hardware
To make a game run on real hardware, you will need to convert the built file into a file suitable for
running on the real thing.
First build the binary in release mode using the instructions above, then do the following:
```sh
agb-gbafix target/thumbv4t-none-eabi/release/<your game> -o <your game>.gba
```

117
examples/amplitude/gba.ld Normal file
View file

@ -0,0 +1,117 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__start)
EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M
}
__text_start = ORIGIN(rom);
INPUT (agb.a)
SECTIONS {
. = __text_start;
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > rom
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > rom
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>rom
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>rom
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
__iwram_end = ABSOLUTE(.);
} > iwram
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)
}
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.debug_ranges 0 : { *(.debug_ranges) }
/* discard anything not already mentioned */
/DISCARD/ : { *(*) }
}

View file

@ -0,0 +1,115 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__start)
EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
}
__text_start = ORIGIN(ewram);
INPUT (agb.a)
SECTIONS {
. = __text_start;
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > rom
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > ewram
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>ewram
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>ewram
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
__iwram_end = ABSOLUTE(.);
} > iwram
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)
}
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.debug_ranges 0 : { *(.debug_ranges) }
/* discard anything not already mentioned */
/DISCARD/ : { *(*) }
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
components = ["rust-src", "clippy", "rustfmt"]

View file

@ -0,0 +1,370 @@
#![no_std]
#![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
#![deny(clippy::all)]
extern crate alloc;
use agb::{
display::{
self,
affine::AffineMatrix,
object::{
AffineMatrixInstance, AffineMode, Graphics, OamIterator, ObjectUnmanaged, Sprite,
SpriteLoader, SpriteVram, Tag,
},
palette16::Palette16,
},
fixnum::{num, Num, Vector2D},
include_aseprite,
input::{Button, ButtonController},
rng,
};
use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
type Number = Num<i32, 8>;
struct Saw {
object: ObjectUnmanaged,
position: Vector2D<Number>,
angle: Number,
rotation_speed: Number,
}
enum Colour {
Red,
Blue,
}
struct Circle {
colour: Colour,
position: Vector2D<Number>,
}
#[derive(Clone)]
struct SpriteCache {
saw: SpriteVram,
blue: SpriteVram,
red: SpriteVram,
numbers: Box<[SpriteVram]>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DrawDirection {
Left,
Right,
}
fn draw_number(
mut number: u32,
position: Vector2D<i32>,
oam: &mut OamIterator,
direction: DrawDirection,
sprite_cache: &SpriteCache,
) -> Option<()> {
let mut digits = Vec::new();
if number == 0 {
digits.push(0);
}
while number != 0 {
digits.push(number % 10);
number /= 10;
}
let mut current_position = if direction == DrawDirection::Right {
position + (4 * (digits.len() - 1) as i32, 0).into()
} else {
position
};
for digit in digits {
let mut obj = ObjectUnmanaged::new(sprite_cache.numbers[digit as usize].clone());
obj.show().set_position(current_position);
oam.next()?.set(&obj);
current_position -= (4, 0).into();
}
Some(())
}
impl SpriteCache {
fn new(loader: &mut SpriteLoader) -> Self {
const SPRITES: &Graphics = include_aseprite!(
"gfx/circles.aseprite",
"gfx/saw.aseprite",
"gfx/numbers.aseprite"
);
const NUMBERS: &Tag = SPRITES.tags().get("numbers");
const BLUE_CIRCLE: &Sprite = SPRITES.tags().get("Blue").sprite(0);
const RED_CIRCLE: &Sprite = SPRITES.tags().get("Red").sprite(0);
const SAW: &Sprite = SPRITES.tags().get("Saw").sprite(0);
Self {
saw: loader.get_vram_sprite(SAW),
blue: loader.get_vram_sprite(BLUE_CIRCLE),
red: loader.get_vram_sprite(RED_CIRCLE),
numbers: (0..10)
.map(|x| NUMBERS.sprite(x))
.map(|x| loader.get_vram_sprite(x))
.collect::<Vec<_>>()
.into_boxed_slice(),
}
}
}
struct Game {
settings: FinalisedSettings,
circles: VecDeque<Circle>,
saws: VecDeque<Saw>,
head_position: Vector2D<Number>,
phase_time: Number,
input: ButtonController,
frame_since_last_saw: i32,
alive_frames: u32,
}
enum GameState {
Continue,
Loss(u32),
}
impl Game {
fn from_settings(settings: Settings) -> Self {
let finalised = settings.to_finalised_settings();
let mut circles = VecDeque::with_capacity(finalised.number_of_circles);
for idx in 0..finalised.number_of_circles {
circles.push_back(Circle {
colour: Colour::Red,
position: Vector2D::new(
finalised.speed * idx as i32 - 4,
settings.head_start_position.y,
),
})
}
Game {
input: agb::input::ButtonController::new(),
settings: finalised,
circles,
saws: VecDeque::new(),
head_position: settings.head_start_position,
phase_time: 0.into(),
frame_since_last_saw: 0,
alive_frames: 0,
}
}
fn frame(&mut self, sprite_cache: &SpriteCache) -> GameState {
self.input.update();
let (height, colour) = if self.input.is_pressed(Button::A) {
(self.settings.wave_height_ability, Colour::Blue)
} else {
(self.settings.wave_height_normal, Colour::Red)
};
let next_phase_time = self.phase_time + self.settings.phase_speed;
let this_frame_y_delta = next_phase_time.cos() - self.phase_time.cos();
self.phase_time = next_phase_time % num!(1.);
let this_frame_y_delta = this_frame_y_delta * height;
self.head_position.y += this_frame_y_delta;
// update circles
for circle in self.circles.iter_mut() {
circle.position.x -= self.settings.speed;
}
self.circles.pop_front();
// generate circle
let circle = Circle {
colour,
position: self.head_position,
};
self.circles.push_back(circle);
// update saws + check for death
let mut saw_has_hit_head = false;
let mut number_of_saws_to_pop = 0;
for (idx, saw) in self.saws.iter_mut().enumerate() {
saw.position.x -= self.settings.speed;
if saw.position.x < (-32).into() {
number_of_saws_to_pop = idx + 1;
}
saw.angle += saw.rotation_speed;
let angle_affine_matrix = AffineMatrix::from_rotation(saw.angle);
saw.object.set_affine_matrix(AffineMatrixInstance::new(
angle_affine_matrix.to_object_wrapping(),
));
saw.object.show_affine(AffineMode::Affine);
saw.object
.set_position(saw.position.floor() - (16, 16).into());
if (saw.position - self.head_position).magnitude_squared()
< ((16 + 4) * (16 + 4)).into()
{
saw_has_hit_head = true;
}
}
// destroy saws
for _ in 0..number_of_saws_to_pop {
self.saws.pop_front();
}
// create saw
self.frame_since_last_saw -= 1;
if self.frame_since_last_saw <= 0 {
self.frame_since_last_saw = self.settings.frames_between_saws;
let mut rotation_direction = rng::gen().signum();
if rotation_direction == 0 {
rotation_direction = 1;
}
let rotation_magnitude =
Number::from_raw(rng::gen().abs() % (1 << 8)) % num!(0.02) + num!(0.005);
let rotation_speed = rotation_magnitude * rotation_direction;
let saw = Saw {
object: ObjectUnmanaged::new(sprite_cache.saw.clone()),
position: (300, rng::gen().rem_euclid(display::HEIGHT)).into(),
angle: 0.into(),
rotation_speed,
};
self.saws.push_back(saw);
}
self.alive_frames += 1;
let out_of_bounds_death = self.head_position.y.floor() < -4
|| (self.head_position.y + 1).floor() > display::HEIGHT + 4;
if saw_has_hit_head || out_of_bounds_death {
GameState::Loss(self.alive_frames)
} else {
GameState::Continue
}
}
fn render(&self, oam: &mut OamIterator, sprite_cache: &SpriteCache) -> Option<()> {
for saw in self.saws.iter() {
oam.next()?.set(&saw.object);
}
for circle in self.circles.iter() {
let mut object = ObjectUnmanaged::new(match circle.colour {
Colour::Red => sprite_cache.red.clone(),
Colour::Blue => sprite_cache.blue.clone(),
});
object
.show()
.set_position(circle.position.floor() - (4, 4).into());
oam.next()?.set(&object);
}
Some(())
}
}
struct Settings {
phase_speed: Number,
frames_between_saws: i32,
speed: Number,
head_start_position: Vector2D<Number>,
wave_height_normal: Number,
wave_height_ability: Number,
}
impl Settings {
fn to_finalised_settings(&self) -> FinalisedSettings {
FinalisedSettings {
number_of_circles: ((self.head_start_position.x + 4) / self.speed + 1)
.floor()
.try_into()
.expect("number should be positive"),
speed: self.speed,
phase_speed: self.phase_speed,
frames_between_saws: self.frames_between_saws,
wave_height_ability: self.wave_height_ability,
wave_height_normal: self.wave_height_normal,
}
}
}
struct FinalisedSettings {
wave_height_normal: Number,
wave_height_ability: Number,
phase_speed: Number,
frames_between_saws: i32,
speed: Number,
number_of_circles: usize,
}
pub fn main(mut gba: agb::Gba) -> ! {
let (mut unmanaged, mut sprites) = gba.display.object.get_unmanaged();
let sprite_cache = SpriteCache::new(&mut sprites);
let (_background, mut vram) = gba.display.video.tiled0();
vram.set_background_palettes(&[Palette16::new([u16::MAX; 16])]);
let vblank = agb::interrupt::VBlank::get();
let mut max_score = 0;
loop {
let mut game = Game::from_settings(Settings {
phase_speed: num!(0.02),
frames_between_saws: 60,
speed: num!(1.),
head_start_position: (40, 100).into(),
wave_height_normal: 20.into(),
wave_height_ability: 5.into(),
});
loop {
let state = game.frame(&sprite_cache);
if game.alive_frames > max_score {
max_score = game.alive_frames;
}
vblank.wait_for_vblank();
let oam_frame = &mut unmanaged.iter();
draw_number(
max_score,
(display::WIDTH - 4, 1).into(),
oam_frame,
DrawDirection::Left,
&sprite_cache,
);
draw_number(
game.alive_frames,
(1, 1).into(),
oam_frame,
DrawDirection::Right,
&sprite_cache,
);
game.render(oam_frame, &sprite_cache);
if let GameState::Loss(_) = state {
for _ in 0..30 {
vblank.wait_for_vblank();
}
break;
}
}
}
}

View file

@ -0,0 +1,10 @@
#![no_std]
#![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
amplitude::main(gba)
}

View file

@ -76,15 +76,22 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if 1.0.0",
"once_cell",
"version_check",
]
[[package]]
name = "amplitude"
version = "0.1.0"
dependencies = [
"agb",
]
[[package]]
name = "asefile"
version = "0.3.5"
@ -167,6 +174,7 @@ name = "combo"
version = "0.1.0"
dependencies = [
"agb",
"amplitude",
"hyperspace-roll",
"the-hat-chooses-the-wizard",
"the-purple-night",
@ -203,9 +211,9 @@ dependencies = [
[[package]]
name = "fontdue"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
@ -220,22 +228,11 @@ dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
@ -274,12 +271,6 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "libflate"
version = "0.1.27"
@ -554,12 +545,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "xml-rs"
version = "0.8.4"

View file

@ -10,6 +10,7 @@ agb = { version = "0.14.0", path = "../../agb" }
the-purple-night = { path = "../the-purple-night" }
the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" }
hyperspace-roll = { path = "../hyperspace-roll" }
amplitude = { path = "../amplitude" }
[profile.dev]
opt-level = 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -21,6 +21,7 @@ pub enum Game {
TheHatChoosesTheWizard,
ThePurpleNight,
HyperspaceRoll,
Amplitude,
}
impl Game {
@ -29,6 +30,7 @@ impl Game {
Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba),
Game::ThePurpleNight => the_purple_night::main(gba),
Game::HyperspaceRoll => hyperspace_roll::main(gba),
Game::Amplitude => amplitude::main(gba),
}
}
@ -37,6 +39,7 @@ impl Game {
0 => Game::TheHatChoosesTheWizard,
1 => Game::ThePurpleNight,
2 => Game::HyperspaceRoll,
3 => Game::Amplitude,
_ => unreachable!("game out of index in an unreachable manner"),
}
}
@ -46,7 +49,8 @@ include_background_gfx!(
games, "121105",
hat => "gfx/hat.png",
purple => "gfx/purple.png",
hyperspace => "gfx/hyperspace.png"
hyperspace => "gfx/hyperspace.png",
amplitude => "gfx/amplitude.png"
);
fn get_game(gba: &mut agb::Gba) -> Game {
@ -58,13 +62,15 @@ fn get_game(gba: &mut agb::Gba) -> Game {
let hat = TileSet::new(games::hat.tiles, TileFormat::FourBpp);
let purple = TileSet::new(games::purple.tiles, TileFormat::FourBpp);
let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::FourBpp);
let amplitude = TileSet::new(games::amplitude.tiles, TileFormat::FourBpp);
let tiles = [hat, purple, hyperspace];
let tiles = [hat, purple, hyperspace, amplitude];
let palette_assignments = &[
games::hat.palette_assignments,
games::purple.palette_assignments,
games::hyperspace.palette_assignments,
games::amplitude.palette_assignments,
];
vram.set_background_palettes(games::PALETTES);
@ -79,7 +85,7 @@ fn get_game(gba: &mut agb::Gba) -> Game {
let y = pos.y.rem_euclid(20);
let x = pos.x.rem_euclid(30);
let game = (pos.x).rem_euclid(90) as usize / 30;
let game = (pos.x).rem_euclid(tiles.len() as i32 * 30) as usize / 30;
let tile_id = (y * 30 + x) as usize;
(
&tiles[game],

View file

@ -76,11 +76,11 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if",
"once_cell",
"version_check",
]
@ -178,30 +178,19 @@ dependencies = [
[[package]]
name = "fontdue"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
@ -234,12 +223,6 @@ dependencies = [
"png",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "log"
version = "0.4.17"
@ -422,9 +405,3 @@ name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View file

@ -494,7 +494,7 @@ pub(crate) fn battle_screen(
let obj = &agb.obj;
let mut select_box_obj = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
let mut select_box_obj = agb.obj.object_sprite(SELECT_BOX.sprite(0));
select_box_obj.show();
let num_dice = player_dice.dice.len();

View file

@ -1,4 +1,4 @@
use agb::display::object::{Object, ObjectController};
use agb::display::object::{OamManaged, Object};
use agb::rng;
use alloc::vec;
use alloc::vec::Vec;
@ -39,7 +39,7 @@ pub struct BattleScreenDisplay<'a> {
const HEALTH_BAR_WIDTH: usize = 48;
impl<'a> BattleScreenDisplay<'a> {
pub fn new(obj: &'a ObjectController, current_battle_state: &CurrentBattleState) -> Self {
pub fn new(obj: &'a OamManaged, current_battle_state: &CurrentBattleState) -> Self {
let mut misc_sprites = vec![];
let player_x = 12;
let player_y = 8;
@ -52,8 +52,8 @@ impl<'a> BattleScreenDisplay<'a> {
Ship::PilotedShip
});
let mut player_obj = obj.object(obj.sprite(player_sprite));
let mut enemy_obj = obj.object(obj.sprite(enemy_sprite));
let mut player_obj = obj.object_sprite(player_sprite);
let mut enemy_obj = obj.object_sprite(enemy_sprite);
player_obj.set_x(player_x).set_y(player_y).set_z(1).show();
enemy_obj.set_x(enemy_x).set_y(player_y).set_z(1).show();
@ -66,7 +66,7 @@ impl<'a> BattleScreenDisplay<'a> {
.faces_to_render()
.enumerate()
.map(|(i, (face, _))| {
let mut die_obj = obj.object(obj.sprite(FACE_SPRITES.sprite_for_face(face)));
let mut die_obj = obj.object_sprite(FACE_SPRITES.sprite_for_face(face));
die_obj.set_y(120).set_x(i as u16 * 40 + 28).show();
@ -89,7 +89,7 @@ impl<'a> BattleScreenDisplay<'a> {
let player_shield: Vec<_> = (0..5)
.map(|i| {
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
let mut shield_obj = obj.object_sprite(shield_sprite);
shield_obj
.set_x(player_x + 18 + 11 * i)
.set_y(player_y)
@ -101,7 +101,7 @@ impl<'a> BattleScreenDisplay<'a> {
let enemy_shield: Vec<_> = (0..5)
.map(|i| {
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
let mut shield_obj = obj.object_sprite(shield_sprite);
shield_obj
.set_x(enemy_x - 16 - 11 * i)
.set_y(player_y)
@ -146,9 +146,8 @@ impl<'a> BattleScreenDisplay<'a> {
let enemy_attack_display = (0..2)
.map(|i| {
let mut attack_obj = obj.object(
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)),
);
let mut attack_obj = obj
.object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack));
let attack_obj_position = (120, 56 + 32 * i).into();
attack_obj.set_position(attack_obj_position).hide();
@ -189,7 +188,7 @@ impl<'a> BattleScreenDisplay<'a> {
pub fn update(
&mut self,
obj: &'a ObjectController,
obj: &'a OamManaged,
current_battle_state: &CurrentBattleState,
) -> Vec<Action> {
for (i, player_shield) in self.objs.player_shield.iter_mut().enumerate() {
@ -279,7 +278,7 @@ impl<'a> BattleScreenDisplay<'a> {
actions_to_apply
}
pub fn add_action(&mut self, action: Action, obj: &'a ObjectController, sfx: &mut Sfx) {
pub fn add_action(&mut self, action: Action, obj: &'a OamManaged, sfx: &mut Sfx) {
play_sound_for_action_start(&action, sfx);
self.animations
@ -309,7 +308,7 @@ impl<'a> EnemyAttackDisplay<'a> {
}
}
pub fn update(&mut self, attack: &Option<EnemyAttackState>, obj: &'a ObjectController) {
pub fn update(&mut self, attack: &Option<EnemyAttackState>, obj: &'a OamManaged) {
if let Some(attack) = attack {
self.face.show().set_sprite(
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(attack.attack_type())),
@ -350,27 +349,27 @@ enum AnimationUpdateState {
}
impl<'a> AnimationStateHolder<'a> {
fn for_action(a: Action, obj: &'a ObjectController) -> Self {
fn for_action(a: Action, obj: &'a OamManaged) -> Self {
let state = match a {
Action::PlayerActivateShield { amount, .. } => {
AnimationState::PlayerActivateShield { amount, frame: 0 }
}
Action::PlayerShoot { .. } => AnimationState::PlayerShoot {
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
bullet: obj.object_sprite(BULLET_SPRITE),
x: 64,
},
Action::PlayerDisrupt { .. } => AnimationState::PlayerDisrupt {
bullet: obj.object(obj.sprite(DISRUPT_BULLET)),
bullet: obj.object_sprite(DISRUPT_BULLET),
x: 64,
},
Action::PlayerHeal { .. } => AnimationState::PlayerHeal {},
Action::PlayerBurstShield { .. } => AnimationState::PlayerBurstShield { frame: 0 },
Action::PlayerSendBurstShield { .. } => AnimationState::PlayerSendBurstShield {
bullet: obj.object(obj.sprite(BURST_BULLET)),
bullet: obj.object_sprite(BURST_BULLET),
x: 64,
},
Action::EnemyShoot { .. } => AnimationState::EnemyShoot {
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
bullet: obj.object_sprite(BULLET_SPRITE),
x: 175,
},
Action::EnemyShield { amount, .. } => AnimationState::EnemyShield { amount, frame: 0 },
@ -383,7 +382,7 @@ impl<'a> AnimationStateHolder<'a> {
fn update(
&mut self,
objs: &mut BattleScreenDisplayObjects<'a>,
obj: &'a ObjectController,
obj: &'a OamManaged,
current_battle_state: &CurrentBattleState,
) -> AnimationUpdateState {
match &mut self.state {

View file

@ -1,6 +1,6 @@
use agb::{
display::{
object::{Object, ObjectController},
object::{OamManaged, Object},
tiled::{RegularMap, TiledMap},
HEIGHT, WIDTH,
},
@ -91,10 +91,10 @@ fn move_net_position_ud(idx: usize, direction: Tri) -> usize {
}
}
fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> Vec<Object<'a>> {
fn create_dice_display<'a>(gfx: &'a OamManaged, dice: &'_ PlayerDice) -> Vec<Object<'a>> {
let mut objects = Vec::new();
for (idx, dice) in dice.dice.iter().enumerate() {
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(dice.faces[1])));
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(dice.faces[1]));
obj.set_x((idx as i32 * 32 - 24 / 2 + 20) as u16);
obj.set_y(16 - 24 / 2);
@ -105,10 +105,10 @@ fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> V
objects
}
fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -> Vec<Object<'a>> {
fn create_net<'a>(gfx: &'a OamManaged, die: &'_ Die, modified: &[usize]) -> Vec<Object<'a>> {
let mut objects = Vec::new();
for (idx, &face) in die.faces.iter().enumerate() {
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(face)));
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(face));
let (x, y) = screen_position_for_index(idx);
obj.set_x((x - 24 / 2) as u16);
obj.set_y((y - 24 / 2) as u16);
@ -119,7 +119,7 @@ fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -
}
for &m in modified.iter().chain(core::iter::once(&3)) {
let mut obj = gfx.object(gfx.sprite(MODIFIED_BOX));
let mut obj = gfx.object_sprite(MODIFIED_BOX);
let (x, y) = screen_position_for_index(m);
obj.set_x((x - 32 / 2) as u16);
obj.set_y((y - 32 / 2) as u16);
@ -139,10 +139,10 @@ fn upgrade_position(idx: usize) -> (u32, u32) {
)
}
fn create_upgrade_objects<'a>(gfx: &'a ObjectController, upgrades: &[Face]) -> Vec<Object<'a>> {
fn create_upgrade_objects<'a>(gfx: &'a OamManaged, upgrades: &[Face]) -> Vec<Object<'a>> {
let mut objects = Vec::new();
for (idx, &upgrade) in upgrades.iter().enumerate() {
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(upgrade)));
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(upgrade));
let (x, y) = upgrade_position(idx);
obj.set_x((x - 24 / 2) as u16);
obj.set_y((y - 24 / 2) as u16);
@ -180,13 +180,13 @@ pub(crate) fn customise_screen(
let mut input = agb::input::ButtonController::new();
let mut select_box = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
let mut select_box = agb.obj.object_sprite(SELECT_BOX.sprite(0));
select_box.show();
let mut selected_dice = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
let mut selected_dice = agb.obj.object_sprite(SELECTED_BOX);
selected_dice.hide();
let mut selected_face = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
let mut selected_face = agb.obj.object_sprite(SELECTED_BOX);
selected_face.hide();
agb.sfx.frame();

View file

@ -1,5 +1,5 @@
use agb::{
display::object::{Object, ObjectController, Sprite, Tag},
display::object::{OamManaged, Object, Sprite, Tag},
fixnum::Vector2D,
};
use alloc::vec::Vec;
@ -141,14 +141,12 @@ pub struct HealthBar<'a> {
}
impl<'a> HealthBar<'a> {
pub fn new(pos: Vector2D<i32>, max: usize, obj: &'a ObjectController) -> Self {
pub fn new(pos: Vector2D<i32>, max: usize, obj: &'a OamManaged) -> Self {
assert_eq!(max % 8, 0);
let sprites = (0..(max / 8))
.map(|i| {
let health_sprite = obj.sprite(SMALL_SPRITES.red_bar(0));
let mut health_object = obj.object(health_sprite);
let mut health_object = obj.object_sprite(SMALL_SPRITES.red_bar(0));
health_object
.set_position(pos + (i as i32 * 8, 0).into())
.show();
@ -159,7 +157,7 @@ impl<'a> HealthBar<'a> {
Self { max, sprites }
}
pub fn set_value(&mut self, new_value: usize, obj: &'a ObjectController) {
pub fn set_value(&mut self, new_value: usize, obj: &'a OamManaged) {
assert!(new_value <= self.max);
for (i, sprite) in self.sprites.iter_mut().enumerate() {
@ -195,22 +193,22 @@ pub struct FractionDisplay<'a> {
}
impl<'a> FractionDisplay<'a> {
pub fn new(pos: Vector2D<i32>, digits: usize, obj: &'a ObjectController) -> Self {
pub fn new(pos: Vector2D<i32>, digits: usize, obj: &'a OamManaged) -> Self {
let mut sprites = Vec::with_capacity(digits * 2 + 1);
for i in 0..digits {
let mut left_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
let mut left_digit = obj.object_sprite(SMALL_SPRITES.number(0));
left_digit.set_position(pos + (i as i32 * 4, 0).into());
sprites.push(left_digit);
let mut right_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
let mut right_digit = obj.object_sprite(SMALL_SPRITES.number(0));
right_digit.set_position(pos + (i as i32 * 4 + digits as i32 * 4 + 7, 0).into());
sprites.push(right_digit);
}
let mut slash = obj.object(obj.sprite(SMALL_SPRITES.slash()));
let mut slash = obj.object_sprite(SMALL_SPRITES.slash());
slash.set_position(pos + (digits as i32 * 4 + 1, 0).into());
sprites.push(slash);
@ -222,7 +220,7 @@ impl<'a> FractionDisplay<'a> {
}
}
pub fn set_value(&mut self, current: usize, max: usize, obj: &'a ObjectController) {
pub fn set_value(&mut self, current: usize, max: usize, obj: &'a OamManaged) {
if self.current_current == current && self.current_max == max {
return;
}
@ -260,7 +258,7 @@ impl<'a> NumberDisplay<'a> {
}
}
pub fn set_value(&mut self, new_value: Option<u32>, obj: &'a ObjectController) {
pub fn set_value(&mut self, new_value: Option<u32>, obj: &'a OamManaged) {
if self.value == new_value {
return;
}
@ -271,7 +269,7 @@ impl<'a> NumberDisplay<'a> {
if let Some(mut new_value) = new_value {
if new_value == 0 {
let mut zero_object = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
let mut zero_object = obj.object_sprite(SMALL_SPRITES.number(0));
zero_object.show().set_position(self.position);
self.objects.push(zero_object);
@ -284,7 +282,7 @@ impl<'a> NumberDisplay<'a> {
new_value /= 10;
let mut current_value_obj =
obj.object(obj.sprite(SMALL_SPRITES.number(current_value_digit)));
obj.object_sprite(SMALL_SPRITES.number(current_value_digit));
current_value_obj
.show()

View file

@ -12,7 +12,7 @@
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
use agb::display::object::ObjectController;
use agb::display::object::OamManaged;
use agb::display::tiled::{TileFormat, TiledMap, VRamManager};
use agb::display::Priority;
use agb::interrupt::VBlank;
@ -90,7 +90,7 @@ pub struct PlayerDice {
}
struct Agb<'a> {
obj: ObjectController<'a>,
obj: OamManaged<'a>,
vblank: VBlank,
star_background: StarBackground<'a>,
vram: VRamManager,
@ -104,7 +104,7 @@ pub fn main(mut gba: agb::Gba) -> ! {
save::save_high_score(&mut gba.save, 0).expect("Could not reset high score");
}
let gfx = gba.display.object.get();
let gfx = gba.display.object.get_managed();
let vblank = agb::interrupt::VBlank::get();
let (tiled, mut vram) = gba.display.video.tiled0();

View file

@ -76,11 +76,11 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if",
"once_cell",
"version_check",
]
@ -178,30 +178,19 @@ dependencies = [
[[package]]
name = "fontdue"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
@ -233,12 +222,6 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "log"
version = "0.4.17"
@ -467,9 +450,3 @@ name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View file

@ -2,7 +2,7 @@ use crate::TAG_MAP;
use super::{sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level};
use agb::{
display::object::{ObjectController, Tag},
display::object::{OamManaged, Tag},
fixnum::Vector2D,
};
@ -35,11 +35,11 @@ pub enum EnemyUpdateState {
}
impl<'a> Enemy<'a> {
pub fn new_slime(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
pub fn new_slime(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
Enemy::Slime(Slime::new(object, start_pos + (0, 1).into()))
}
pub fn new_snail(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
pub fn new_snail(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
Enemy::Snail(Snail::new(object, start_pos))
}
@ -52,7 +52,7 @@ impl<'a> Enemy<'a> {
pub fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
level: &Level,
player_pos: Vector2D<FixedNumberType>,
hat_state: HatState,
@ -94,7 +94,7 @@ struct EnemyInfo<'a> {
impl<'a> EnemyInfo<'a> {
fn new(
object: &'a ObjectController,
object: &'a OamManaged,
start_pos: Vector2D<FixedNumberType>,
collision: Vector2D<u16>,
) -> Self {
@ -135,7 +135,7 @@ pub struct Slime<'a> {
}
impl<'a> Slime<'a> {
fn new(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
fn new(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
let slime = Slime {
enemy_info: EnemyInfo::new(object, start_pos, (14u16, 14u16).into()),
state: SlimeState::Idle,
@ -146,7 +146,7 @@ impl<'a> Slime<'a> {
fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
level: &Level,
player_pos: Vector2D<FixedNumberType>,
hat_state: HatState,
@ -257,7 +257,7 @@ pub struct Snail<'a> {
}
impl<'a> Snail<'a> {
fn new(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
fn new(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
let snail = Snail {
enemy_info: EnemyInfo::new(object, start_pos, (16u16, 16u16).into()),
state: SnailState::Idle(0),
@ -272,7 +272,7 @@ impl<'a> Snail<'a> {
fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
level: &Level,
player_pos: Vector2D<FixedNumberType>,
hat_state: HatState,

View file

@ -8,7 +8,7 @@ extern crate alloc;
use agb::{
display::{
object::{Graphics, Object, ObjectController, Tag, TagMap},
object::{Graphics, OamManaged, Object, Tag, TagMap},
tiled::{
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
TileSetting, TiledMap, VRamManager,
@ -124,12 +124,11 @@ pub struct Entity<'a> {
}
impl<'a> Entity<'a> {
pub fn new(object: &'a ObjectController, collision_mask: Vector2D<u16>) -> Self {
let dummy_sprite = object.sprite(WALKING.sprite(0));
let mut sprite = object.object(dummy_sprite);
sprite.set_priority(Priority::P1);
pub fn new(object: &'a OamManaged, collision_mask: Vector2D<u16>) -> Self {
let mut dummy_object = object.object_sprite(WALKING.sprite(0));
dummy_object.set_priority(Priority::P1);
Entity {
sprite,
sprite: dummy_object,
collision_mask,
position: (0, 0).into(),
velocity: (0, 0).into(),
@ -348,7 +347,7 @@ fn ping_pong(i: i32, n: i32) -> i32 {
}
impl<'a> Player<'a> {
fn new(controller: &'a ObjectController, start_position: Vector2D<FixedNumberType>) -> Self {
fn new(controller: &'a OamManaged, start_position: Vector2D<FixedNumberType>) -> Self {
let mut wizard = Entity::new(controller, (6_u16, 14_u16).into());
let mut hat = Entity::new(controller, (6_u16, 6_u16).into());
@ -382,7 +381,7 @@ impl<'a> Player<'a> {
fn update_frame(
&mut self,
input: &ButtonController,
controller: &'a ObjectController,
controller: &'a OamManaged,
timer: i32,
level: &Level,
enemies: &[enemies::Enemy],
@ -616,7 +615,7 @@ enum UpdateState {
impl<'a, 'b> PlayingLevel<'a, 'b> {
fn open_level(
level: &'a Level,
object_control: &'a ObjectController,
object_control: &'a OamManaged,
background: &'a mut InfiniteScrolledMap<'b>,
foreground: &'a mut InfiniteScrolledMap<'b>,
input: ButtonController,
@ -677,7 +676,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> {
self.player.wizard.sprite.set_priority(Priority::P0);
}
fn dead_update(&mut self, controller: &'a ObjectController) -> bool {
fn dead_update(&mut self, controller: &'a OamManaged) -> bool {
self.timer += 1;
let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8);
@ -696,7 +695,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> {
&mut self,
sfx_player: &mut SfxPlayer,
vram: &mut VRamManager,
controller: &'a ObjectController,
controller: &'a OamManaged,
) -> UpdateState {
self.timer += 1;
self.input.update();
@ -828,7 +827,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
vram.set_background_palettes(tile_sheet::PALETTES);
let object = agb.display.object.get();
let object = agb.display.object.get_managed();
let vblank = agb::interrupt::VBlank::get();
let mut current_level = 0;

View file

@ -76,11 +76,11 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if 1.0.0",
"once_cell",
"version_check",
]
@ -193,9 +193,9 @@ dependencies = [
[[package]]
name = "fontdue"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
@ -210,22 +210,11 @@ dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
@ -251,12 +240,6 @@ dependencies = [
"png",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "libflate"
version = "0.1.27"
@ -485,12 +468,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "xml-rs"
version = "0.8.4"

View file

@ -14,7 +14,7 @@ use alloc::{boxed::Box, vec::Vec};
use agb::{
display::{
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
object::{Graphics, OamManaged, Object, Sprite, Tag, TagMap},
tiled::{
InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting,
VRamManager,
@ -164,9 +164,8 @@ struct Entity<'a> {
}
impl<'a> Entity<'a> {
fn new(object_controller: &'a ObjectController, collision_mask: Rect<Number>) -> Self {
let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0));
let mut sprite = object_controller.object(s);
fn new(object_controller: &'a OamManaged, collision_mask: Rect<Number>) -> Self {
let mut sprite = object_controller.object_sprite(LONG_SWORD_IDLE.sprite(0));
sprite.set_priority(Priority::P1);
Entity {
sprite,
@ -523,7 +522,7 @@ struct Player<'a> {
}
impl<'a> Player<'a> {
fn new(object_controller: &'a ObjectController<'a>) -> Player {
fn new(object_controller: &'a OamManaged<'_>) -> Player<'a> {
let mut entity = Entity::new(object_controller, Rect::new((0, 1).into(), (5, 10).into()));
let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0));
entity.sprite.set_sprite(s);
@ -546,7 +545,7 @@ impl<'a> Player<'a> {
fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
buttons: &ButtonController,
level: &Level,
sfx: &mut sfx::Sfx,
@ -800,7 +799,7 @@ impl BatData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
level: &Level,
@ -933,7 +932,7 @@ impl SlimeData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
level: &Level,
@ -1062,7 +1061,7 @@ impl MiniFlameData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
_level: &Level,
@ -1191,7 +1190,7 @@ impl EmuData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
level: &Level,
@ -1364,7 +1363,7 @@ impl EnemyData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
level: &Level,
@ -1385,7 +1384,7 @@ struct Enemy<'a> {
}
impl<'a> Enemy<'a> {
fn new(object_controller: &'a ObjectController, enemy_data: EnemyData) -> Self {
fn new(object_controller: &'a OamManaged, enemy_data: EnemyData) -> Self {
let mut entity = Entity::new(object_controller, enemy_data.collision_mask());
let sprite = enemy_data.sprite();
@ -1399,7 +1398,7 @@ impl<'a> Enemy<'a> {
fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
player: &Player,
level: &Level,
sfx: &mut sfx::Sfx,
@ -1430,7 +1429,7 @@ impl ParticleData {
fn update<'a>(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
entity: &mut Entity<'a>,
player: &Player,
_level: &Level,
@ -1518,7 +1517,7 @@ struct Particle<'a> {
impl<'a> Particle<'a> {
fn new(
object_controller: &'a ObjectController,
object_controller: &'a OamManaged,
particle_data: ParticleData,
position: Vector2D<Number>,
) -> Self {
@ -1534,7 +1533,7 @@ impl<'a> Particle<'a> {
fn update(
&mut self,
controller: &'a ObjectController,
controller: &'a OamManaged,
player: &Player,
level: &Level,
) -> UpdateInstruction {
@ -1561,7 +1560,7 @@ impl<'a> BossState<'a> {
fn update(
&mut self,
enemies: &mut Arena<Enemy<'a>>,
object_controller: &'a ObjectController,
object_controller: &'a OamManaged,
player: &Player,
sfx: &mut sfx::Sfx,
) -> BossInstruction {
@ -1596,7 +1595,7 @@ struct FollowingBoss<'a> {
}
impl<'a> FollowingBoss<'a> {
fn new(object_controller: &'a ObjectController, position: Vector2D<Number>) -> Self {
fn new(object_controller: &'a OamManaged, position: Vector2D<Number>) -> Self {
let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (0, 0).into()));
entity.position = position;
@ -1608,7 +1607,7 @@ impl<'a> FollowingBoss<'a> {
gone: false,
}
}
fn update(&mut self, controller: &'a ObjectController, player: &Player) {
fn update(&mut self, controller: &'a OamManaged, player: &Player) {
let difference = player.entity.position - self.entity.position;
self.timer += 1;
@ -1677,7 +1676,7 @@ enum BossInstruction {
}
impl<'a> Boss<'a> {
fn new(object_controller: &'a ObjectController, screen_coords: Vector2D<Number>) -> Self {
fn new(object_controller: &'a OamManaged, screen_coords: Vector2D<Number>) -> Self {
let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (28, 28).into()));
entity.position = screen_coords + (144, 136).into();
Self {
@ -1693,7 +1692,7 @@ impl<'a> Boss<'a> {
fn update(
&mut self,
enemies: &mut Arena<Enemy<'a>>,
object_controller: &'a ObjectController,
object_controller: &'a OamManaged,
player: &Player,
sfx: &mut sfx::Sfx,
) -> BossInstruction {
@ -1797,7 +1796,7 @@ impl<'a> Boss<'a> {
self.entity
.commit_with_size(offset + shake, (32, 32).into());
}
fn explode(&self, enemies: &mut Arena<Enemy<'a>>, object_controller: &'a ObjectController) {
fn explode(&self, enemies: &mut Arena<Enemy<'a>>, object_controller: &'a OamManaged) {
for _ in 0..(6 - self.health) {
let x_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1;
let y_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1;
@ -1871,7 +1870,7 @@ impl<'a> Game<'a> {
fn advance_frame(
&mut self,
object_controller: &'a ObjectController,
object_controller: &'a OamManaged,
vram: &mut VRamManager,
sfx: &mut sfx::Sfx,
) -> GameStatus {
@ -2083,7 +2082,7 @@ impl<'a> Game<'a> {
}
}
fn load_enemies(&mut self, object_controller: &'a ObjectController) {
fn load_enemies(&mut self, object_controller: &'a OamManaged) {
if self.slime_load < self.level.slime_spawns.len() {
for (idx, slime_spawn) in self
.level
@ -2153,7 +2152,7 @@ impl<'a> Game<'a> {
vram.set_background_palettes(&modified_palettes);
}
fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self {
fn new(object: &'a OamManaged<'a>, level: Level<'a>, start_at_boss: bool) -> Self {
let mut player = Player::new(object);
let mut offset = (8, 8).into();
if start_at_boss {
@ -2196,7 +2195,7 @@ fn game_with_level(gba: &mut agb::Gba) {
let (background, mut vram) = gba.display.video.tiled0();
vram.set_background_palettes(background::PALETTES);
let tileset = TileSet::new(background::background.tiles, TileFormat::FourBpp);
let object = gba.display.object.get();
let object = gba.display.object.get_managed();
loop {
let backdrop = InfiniteScrolledMap::new(

View file

@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
@ -121,9 +121,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "memchr"
@ -205,9 +205,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.3"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
@ -216,9 +216,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.29"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "simd-adler32"

24
tools/Cargo.lock generated
View file

@ -74,9 +74,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.12.0"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "cc"
@ -107,18 +107,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.2.2"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.2.2"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
dependencies = [
"anstream",
"anstyle",
@ -321,9 +321,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "link-cplusplus"
@ -336,9 +336,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
[[package]]
name = "log"
@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.11"
version = "0.37.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
dependencies = [
"bitflags",
"errno",