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 / SizeSmall Normal BigHuge
Square8×816×1632×3264×64
Wide 16×832×832×1664×32
Tall8×168×3216×3232×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:

  1. The sprite pointer class
  2. 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:

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.

Leave a Reply

Your email address will not be published. Required fields are marked *