Objects deep dive
An object is a sprite drawn to an arbitrary part of the screen. They are typically used for anything that moves such as characters and NPCs. All objects can be flipped and affine objects can have an affine transformation applied to them that can rotate and scale them.
Importing sprites
Sprites are imported from aseprite files. Aseprite is an excellent pixel sprite editor that can be acquired for around $20 or compiled yourself for free. It also provides features around adding tags for grouping sprites to form animations and dictating how an animation is performed. The aseprite documentation contains detail on tags. This makes it very useful for creating art for use by agb.
You can import 15 colour sprites using the include_aseprite macro.
In a single invocation of include_aseprite the palettes are all optimised together.
For example, you might use the following to import some sprites.
agb::include_aseprite!(mod sprites, "sprites.aseprite", "other_sprites.aseprite");
This will create a module called sprites that contains statics corresponding to every tag in the provided aseprite files as an agb Tag.
You can also import 255 colour sprites using the include_aseprite_256 macro, which has the same syntax as include_aseprite!().
You have 15 colour and 255 colours because the 0th index of the palette is always fully transparent.
Similar to backgrounds, 255 colour sprites take twice the amount of video RAM and cartridge ROM space, so prefer using 15 colour sprites as they are faster to copy and you will be able to have more of them on screen at once.
Splitting large frames into smaller sprites
The GBA only supports specific sprite sizes (up to 64x64). If your aseprite file has frames larger than you’d like for individual OAM objects, you can specify a target sprite size before the file path to automatically split each frame into multiple smaller sprites.
The size is written as WIDTHxHEIGHT and must be a valid GBA sprite size.
The frame dimensions must be evenly divisible by the target size.
Sub-frames are generated in row-major order (left-to-right, top-to-bottom), and tags are automatically adjusted to cover all sub-frames.
agb::include_aseprite!(
mod sprites,
32x16 "big_character.aseprite",
"small_item.aseprite"
);
In this example, each frame of big_character.aseprite is split into 32x16 sub-sprites, while small_item.aseprite is imported at its native size.
Note that the animation helpers on Tag (such as .animation_sprite() and .animation_frame()) will iterate over individual sub-frames, not complete original frames.
You will likely want to use .sprite() directly to display all the sub-frames that make up a single original frame yourself.
ROM and VRAM
Sprites must be in VRAM to be displayed on screen.
The SpriteVram type represents a sprite in VRAM.
This implements the From trait from &'static Sprite.
The From implementation will deduplicate the sprites in VRAM, this means that you can repeatedly use the same sprite and it’ll only use the space in VRAM once.
This deduplication does have the performance implication of requiring a HashMap lookup, although for many games this will a rather small penalty.
By storing and reusing the SpriteVram you can avoid this lookup.
Furthermore, SpriteVram is reference counted, so Cloneing it is cheap and doesn’t allocate more VRAM.
use agb::display::object::SpriteVram;
agb::include_aseprite!(mod sprites, "examples/chicken.aseprite");
let sprite = SpriteVram::from(sprites::IDLE.sprite(0));
let clone = sprite.clone();
Regular objects
When you have a sprite, you will want to display it to the screen.
This is an Object.
Like many things in agb, you can display to the screen using the show method on Object on the frame.
use agb::display::{GraphicsFrame, object::Object};
agb::include_aseprite!(mod sprites, "examples/chicken.aseprite");
fn chicken(frame: &mut GraphicsFrame) {
// Object::new takes anything that implements Into<SpriteVram>, so we can pass in a static sprite.
Object::new(sprites::IDLE.sprite(0))
.set_pos((32, 32))
.set_hflip(true)
.show(frame);
}
Animations

With your sprites organised in tags, you can use the .animation_sprite() method to get the specific frame for the animation.
This method takes into account the ‘animation direction’ and correctly picks the frame you would want to show.

Often you’ll want to divide the current frame by something to show the animation at a speed that is less than 60 frames per second.
For example, if you wanted to display the ‘Walking’ animation from above, you would use something like this:
use agb::display::{GraphicsFrame, object::Object};
agb::include_aseprite!(mod sprites, "gfx/sprites.aseprite");
fn walk(frame: &mut GraphicsFrame, frame_count: usize) {
// We divide the frame count by 4 here so that we only update once
// every 4 frames rather than every frame.
Object::new(sprites::WALKING.animation_sprite(frame_count / 4))
.set_pos((32, 32))
.show(frame);
}
Object ordering
When rendering multiple objects in the same location, one will show above another.
The one shown on top is the one object that has had the .show() method called first.
From the example, the number 2 is rendered first, followed by 3, 4 and then 1. The number 2 is rendered on top, and if the 1 and the 3 were to overlap then the 3 would be shown above the 1 in addition to the 4 it is visibly covering.
If your game requires many objects to have a correct covering order, you should ensure you call the .show() method in the correct order.
Normally this will be calling .show() on objects with the greatest y value first.
You may want to call .sort_by_key() with the y value being the key before showing the objects.
It is important that you do not use an unstable sort here, since that could cause z-fighting as objects with the same y value could get sorted to a different location each frame, causing them to change render order each frame.
Affine objects
Affine objects can be rotated and scaled by an affine transformation.
These objects are created using the ObjectAffine type.
This, like an Object, requires a sprite but also requires an AffineMatrixObject and an AffineMode.
The affine article goes over some detail in how to create affine matrices.
With a given affine matrix, you can use AffineMatrixObject::new or the From impl to create an AffineMatrixObject.
When using the same affine matrix for multiple sprites, it is important to reuse the AffineMatrixObject as otherwise you may run out of affine matrices.
You can use up to 32 affine matrices at once.
AffineMatrixObject implements Clone, and cloning is very cheap as it just increases a reference count.
An AffineMatrix also stores a translation component.
However, creating the AffineMatrixObject will lose this translation component, so you’ll also need to set it as the position as follows:
let affine_matrix = calculate_affine_matrix();
let affine_matrix_instance = AffineMatrixObject::new(affine_matrix);
ObjectAffine::new(sprite, affine_matrix_instance, AffineMode::Affine)
.set_pos(affine_matrix.position().round())
.show(frame);
Be aware that the position of an affine object is the centre of the sprite, and not the top left corner like it is for regular sprites.
Affine objects have two display modes, regular and double mode. In regular mode, the objects pixels will never exceed the original bounding box (which you can see in the image above). Double mode allows for the sprite to be scaled to twice the size of the original sprite.
You can see the behaviour of affine modes more interactively in the affine objects example.
Affine objects can be animated in the same way as regular objects, by passing a different sprite to the new function.
Dynamic sprites
A dynamic sprite is a sprite whose data is defined during runtime rather than at compile time.
agb has two kinds of dynamic sprites: DynamicSprite16 and DynamicSprite256.
These are naturally for sprites that use a single palette and those that use multiple.
The easiest way to create a dynamic sprite is through the relevant type, here is an example of creating a DynamicSprite16 and setting a couple of pixels.
use agb::display::{
Palette16, Rgb15,
object::{DynamicSprite16, Size},
};
let mut sprite = DynamicSprite16::new(Size::S8x8);
static PALETTE: Palette16 = const {
let mut palette = [Rgb15::BLACK; 16];
palette[1] = Rgb15::WHITE;
Palette16::new(palette)
};
sprite.set_pixel(4, 4, 1);
sprite.set_pixel(5, 5, 1);
let in_vram = sprite.to_vram(&PALETTE);
And you could then go on to use the sprite however you like with Object as normal.
For example
Object::new(in_vram).set_pos((10, 10)).show(&mut frame);
How to handle the camera position?
In many games, you will have objects both in screen space and in world space. You will find that to correctly draw objects to the screen you will need to convert world space coordinates to screen spaces coordinates before showing it. The position of your “camera” needs to be propagated to where the object is shown. There are many ways of achieving this, the simplest being wherever you create your object you can pass through a camera position to correct the position
use agb::{
fixnum::{Vector2D, Num},
display::{
GraphicsFrame,
object::Object,
},
};
struct MyObject {
position: Vector2D<Num<i32, 8>>
}
impl MyObject {
fn show(&self, camera: Vector2D<Num<i32, 8>>, frame: &mut GraphicsFrame) {
Object::new(SOME_SPRITE).set_pos((self.position - camera).round()).show(frame);
}
}
While you can get the position of an Object, do not try using this to correct for the camera position as it will not work.
The precision that positions are stored in the Object are enough to be displayed to the screen and not much more.
Trying to use this for world coordinates will fail.
See also
- The pong tutorial goes over the basics of sprites.