All graphics (sprites & backgrounds) for the Butano Game engine must be bitmap images. These images must be placed in the “graphics” folder of your project. No changes need to be added to the makefile. However, like mentioned in my previous tutorial on Project Structure in Butano, a matching .json file will be needed.
The Butano Game Engine has three shapes for sprites, and four sizes for each shape. Here’s a table of all the possibilities for Shape & Size. Your image must be one of the following sizes:
Shape / Size | Small | Normal | Big | Huge |
Square | 8×8 | 16×16 | 32×32 | 64×64 |
Wide | 16×8 | 32×8 | 32×16 | 64×32 |
Tall | 8×16 | 8×32 | 16×32 | 32×64 |
For each bitmap asset for sprites, Butano will create a constant object for that asset under the “bn::sprite_items” namespace.
I’m using a graphic from the Butano Mosaic example:
I’ve renamed it to “testplayer.bmp“, and created a matching “testplayer.json” file:
Important Note: All graphics (sprites and backgrounds) must have all lowercase names. Also if you see an error, like “testplayer.bmp error: Invalid bits per pixel: 24” make sure you are exporting with an indexed palette.
{
"type": "sprite"
}
Creating the sprite
To get your sprite on screen you need to include two things:
- The sprite pointer class
- The generated sprite items class
Since my bitmap was named “testplayer.bmp”, after re-running the “make” command, butano has generated a matching sprite items header file “bn_sprite_items_testplayer.h”.
#include <bn_sprite_ptr.h>
#include "bn_sprite_items_testplayer.h"
With those included, you need to now create the sprite item. This must be done AFTER the call to “bn::core::init()“. The following, creates a sprite at x=0, y=0;
// This must be done AFTER the call to "bn::core::init()"
bn::sprite_ptr sprite = bn::sprite_items::testplayer.create_sprite(0, 0);
After that, your sprite will be visible. Without any other adjustments, (0,0) will place your sprite in the middle of the screen.
The Game Boy Advance supports up to 128 different sprites. You can even adjust their render order by using their “bn::sprite_ptr::set_z_order(int)” or “bn::sprite_ptr::set_bg_priority(int)” functions.
Moving, Scaling, and Rotating Sprites
Unlike with the Game Boy, with the Game Boy Advance & Butano Game Engine we can also rotate, shear, and scale our sprites.
Moving sprites
We can get our sprites position using the bn::sprite_ptr::x() and bn::sprite_ptr::y() functions. Additionally, we can change those values using the matching bn::sprite_ptr::set_x(fixed x) and bn::sprite_ptr::set_y(fixed y) functions.
Note: All four of these functions use Butano’s bn::fixed type. The GBA doesn’t have dedicated hardware for floats/doubles. While they can be emulated in software, that is very slow and not recommended. More Info.
Here’s an example how to make our sprite move horizontally
bn::fixed spriteX = sprite.x();
spriteX+=2;
sprite.set_x(spriteX);
Scaling Sprites
You can pass bn::fixed values to the bn::sprite_ptr::set_horizontal_scale and bn::sprite_ptr::set_vertical_scale functions to change the size of your sprite.
bn::fixed horizontalScale = sprite.horizontal_scale();
bn::fixed verticalScale= sprite.vertical_scale();
horizontalScale += bn::fixed(0.01);
verticalScale+= bn::fixed(0.03);
sprite.set_horizontal_scale(horizontalScale);
sprite.set_vertical_scale(verticalScale);
Which will look like this:
There Are limits though. If you go equal to, or below zero, you’ll see a error in your emulator:
If you go too big, your sprite will be limited. The maximum sprite canvas size is double the original size of the sprite. for example: If your sprite is 64×64, the max canvas size is 128×128. In the below screenshot, my 64×64 sprite has gone beyond that limit, and becomes clipped.
Rotating Sprites
You can get & set a sprites rotation using the “bn::sprite_ptr::rotation_angle()” and “bn::sprite_ptr::set_rotation_angle_safe(bn::fixed)” functions respectively. The value must be a bn::fixed lke the previous functions, and in degrees (not radians)
bn::fixed rotation = sprite.rotation_angle();
rotation+=bn::fixed(0.5);
sprite.set_rotation_angle_safe(rotation);
When you compile and run, you’ll se your sprite happily rotating.
Note: there is a “bn::sprite_ptr::set_rotation_angle(bn::fixed)” function, but you will it explicitly expects a value between 0 and 360. If you provide a value outside of that range, you’ll see an error like this:
You can also flip sprites horizontally and vertically using the “bn::sprite_ptr::set_horizontal_flip(bool)” and “bn::sprite_ptr::set_vertical_flip(bool)” functions.
Shearing Sprites (Also called “Skewing”)
In addition to rotation, translation, and scaling; you can also “shear” sprites. This is where you slant the sprite along it’s horizontal or vertical axis. This is called “Skewing” in some other programs. This is done via the following functions:
- bn::sprite_ptr::horizontal_shear()
- bn::sprite_ptr::vertical_shear()
- bn::sprite_ptr::set_horizontal_shear(bn::fixed)
- bn::sprite_ptr::set_vertical_shear(bn::fixed)
If you apply a constant horizontal skew:
bn::fixed shear = sprite.horizontal_shear();
shear += bn::fixed(.5);
sprite.set_horizontal_shear(shear);
Which looks like this:
Removing/Hiding sprites
Sprites will automatically be removed, when their pointer goes out of context. If your sprite remains on screen, then SOMEWHERE exists a pointer to it still. You can temporarily hide a sprite, and still keep it alive. use the “bn::sprite_ptr::set_visible(bool)” function. Here’s an example of a sprite being removed, when it goes out of context.
#include <bn_core.h>
#include <bn_keypad.h>
#include <bn_sprite_ptr.h>
#include "bn_sprite_items_testplayer.h"
int main()
{
bn::core::init();
{
bn::sprite_ptr sprite = bn::sprite_items::testplayer.create_sprite(0, 0);
// Wait for a to be presseed
while(!bn::keypad::a_pressed()){bn::core::update();}
}
// The "sprite" is out of context, and will automatically be removed
}
Important Reminder: Do not try to create sprite pointers before the call to “bn::core::init()”
Alternatively, you can use “bn::optional” class or contain the sprite pointers in a “bn::vector“.
Using Optionals for sprite management
The “bn::optional” class is another way to get rid of sprites. You can give it a value using the “=” operator. You can remove it’s value using it’s reset function. Use it’s “has_value” function to determine if it has an actual value or not.
To directly access it’s target object , use the arrow operator “->”. To access the optional itself, use the dot operator “.”. Here’s an example below. When A is pressed, the optional is given a value. When B is pressed, the optional’ s value is removed (thereby removing the sprite). If the optional has a value, the sprite will be visible and moving across the screen
#include <bn_core.h>
#include <bn_keypad.h>
#include <bn_sprite_ptr.h>
#include "bn_sprite_items_testplayer.h"
int main()
{
bn::core::init();
// This is an optional sprite_ptr
// It can OPTIONALLY have a sprite_ptr value
// use the '.' operator to access data/functions on the optional
// Use the '->' operator to access data/functions on the sprite pointer itself
bn::optional<bn::sprite_ptr> optionalSprite;
// Wait for a to be presseed
while(true){
bn::core::update();
// if we pressed a, and our optional does not have an actual value
// use the '.' operator to access data/functions on the optional, and not the sprite pointer itself
if(bn::keypad::a_pressed() && !optionalSprite.has_value()){
// Give our optional a value
// use the '.' operator to access data/functions on the optional, and not the sprite pointer itself
optionalSprite = bn::sprite_items::testplayer.create_sprite( 0, 0);
}
// if we pressed b, and our optional has an actual value
// use the '.' operator to access data/functions on the optional, and not the sprite pointer itself
if(bn::keypad::b_pressed() && optionalSprite.has_value()){
// Clear our optional sprite, leaving it with no value
// use the '.' operator to access data/functions on the optional, and not the sprite pointer itself
// The sprite will automatically be removed from the screen
optionalSprite.reset();
}
// Do we have an value in our optional?
// use the '.' operator to access data/functions on the optional, and not the sprite pointer itself
if(optionalSprite.has_value()){
// Get the x position of the sprite
// Use -> to directly access the sprite data/functions, and not the optional data/functions
bn::fixed x = optionalSprite->x();
x+=bn::fixed(2);
// Update the position of the sprite
// Use -> to directly access the sprite data/functions, and not the optional data/functions
optionalSprite->set_x(x);
}
}
}
The result. Each time i press b the sprite disappears. When i press a, it re-appears at the start and moves rightward.
Using Vectors for sprite management
A bn::vector, is similar to your traditional c++ vector class. If you push your sprite pointer directly into it. When you remove that sprite pointer from the vector, the sprite will automatically be removed from the screen.
#include <bn_core.h>
#include <bn_random.h>
#include <bn_keypad.h>
#include <bn_vector.h>
#include <bn_sprite_ptr.h>
#include "bn_sprite_items_testplayer.h"
int main()
{
bn::core::init();
// From: #include <bn_random.h>
bn::random r;
// A vector of sprite pointers, maximum size: 1
// From: #include "bn_vector.h"
bn::vector<bn::sprite_ptr,10> sprites;
// Wait for a to be presseed
while(true){
bn::core::update();
// if we dont have 10 sprites and a is pressed
if(bn::keypad::a_pressed() && sprites.size()<10){
// Figure out a random position
int randomX = -120+r.get()%240;
int randomY = -80+r.get()%160;
// Add to an vector of sprite points
sprites.push_back(bn::sprite_items::testplayer.create_sprite( randomX, randomY));
}
// When b is presssed, and if we have any sprites
if(bn::keypad::b_pressed() && sprites.size()>0){
// Get a random index to erase
int eraseIndex = r.get() % sprites.size();
// Get the iterator for the matching item
bn::vector<bn::sprite_ptr,1>::iterator it = sprites.begin() + eraseIndex;
// Remove from the array
// Since no other references exist, the sprite will be removed here
sprites.erase(it);
}
bn::vector<bn::sprite_ptr,10>::iterator it = sprites.begin();
while(it<sprites.end())
{
// Get the x position of the sprite
// Use -> to directly access the sprite data/functions, and not the optional data/functions
bn::fixed x = it->x();
x+=bn::fixed(2);
if(x>160)sprites.erase(it);
else{
// Update the position of the sprite
// Use -> to directly access the sprite data/functions, and not the optional data/functions
it->set_x(x);
it++;
}
}
}
}
The result:
Conclusion
That’s the basics of sprites. In a later tutorial we’ll go over sprite animations, and maybe some other features. Be sure to check out the Butano sprites example to see everything the engine offers.