Game Boy Advance homebrew development forum
Posts: 6

Recently, I've been reading the SFML Game Development book.
It covers a lot of topic about gamedev from scratch, including state stack based Scene class, GUI, command pattern, etc.

As a practice, I'm going to port Creepy Castle to the GBA, using Butano engine.

As it's an unauthorized port, I won't release it other than some video recordings...

Nothing much, only the GUI stuff for now;

The button implementation is a copycat of this menu implementation, but I'm not dynamically allocating it.

// buttons as member variables of the scene
ui::Container<4>           _uiContainer;
ui::SaveSlotButton         _btnSaveSlot[SAVE_SLOT_COUNT];
ui::IconTextButton<1, 4>   _btnBack;
ui::IconTextButton<2, 8>   _btnDeletion;

// initialize the buttons
_btnSaveSlot[0].setCallback([this]() {
    getContext().saveFile = &_fakeTestSave1;

// add the buttons to the container

I'll work on loading 16x16 meta-tile based tilemap on this week.

Posts: 4

Looks great!

Have you tried talking to the author of the original game about releasing a small demo?
It could help as advertising, I guess...

Posts: 6

GValiente wrote:

Have you tried talking to the author of the original game about releasing a small demo?
It could help as advertising, I guess...

I haven't yet, but if this goes far enough, I'll consider it.
For now, the plan is releasing a small fan-made boss fight video on April Fools' Day.

Posts: 18

This looks so good! One of my favorite things about GBA is the landscape orientation of the display area. It translates beautifully.


Posts: 6

So, a week passed.
I've been implementing 16x16 meta-tilemap loading this week. (and still WIP)

I have this map hand-crafted on Tiled map editor.
Tiled tilemap

Upon build, it's parsed with pytmx to generate hard-coded C++ constexpr object like this.

// Auto-generated by `tool/` on 2023-02-16 21:29:36
// DO NOT EDIT this file;  It will be overwritten on next build!
#include "mtile/MTilemap.h"
inline constexpr MTilemap<95, 95, 16, 18, 8, 11, 1, 7> mtilemap_sc1_room0 (
    { MobSpawnPoint{ 13, 7, entity::mob::MobKind::SQUEAKER }, ... },
    { ItemSpawnPoint{ 21, 7, entity::item::ItemKind::YELLOW_KEY }, ... },
    { DoorSpawnPoint{ 29, 6, entity::DoorKind::WHITE_KEY, core::HDirection::NONE }, ... },
    // 16x16 meta-tile id for `dynamic_regular_bg`.
    { ... , 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ... },

And I can read these SpawnPoints and Meta-tile IDs to generate the things in actual world.

void World::setMTilemap(const mtile::MTilemapBase& mTilemap) {
    // init `dynamic_regular_bg` tiles
    // init sprites
    span<const MobSpawnPoint> mobSpawnPoints = mTilemap.getMobSpawnPoints();
    BN_ASSERT(spawnPoints.size() <= entities.max_size(), "Not enough space for mobs (",
              spawnPoints.size(), " <= ", entities.max_size(), ")");
    for (const auto& spawn : mobSpawnPoints) {

And after dealing with meta-tile to gba-tile conversion, this pops out. yay

gba results gif

Scrolling is done based off the Butano's dynamic_regular_bg and camera.
It's done by checking the changed position of the camera each frame, and reload the meta-tiles around it if necessary.

scrolling BG

I've done this before, but it's much more clean this time, thanks to the Butano's update.

...and also thanks to the small camera window space.
I didn't have to load half-meta-tile like before, which complicates things a bit.

Posts: 4

Great update, I hope you can release a public demo or something.

Posts: 6

Video showcase

Wow, it's been 2 weeks from the last post? Time flies.
This time, I implemented EventQueue and the player movement.

See this as .mp4 video (audio included)

Nice, it's much better with the player animation and audio.

I'll explain how this works under the hood.


First of all, there's a central EventQueue which stores and propagates every event happened in the game.
And there's EventProducer & EventListener, who pushes/listens an event to/from EventQueue.

Say, if an object wants to send an event to the EventQueue,
its class should derive from EventProducer, and call sendEvent<EArgs>({...}, delayTicks).

Similarly, if an object wants to receive events from the EventQueue,
its class should derive from EventListener, and override handleEvent(const EventProducer& sender, const arg::EventArgs&).

Code (Usage)

Here's the example code that triggers player fall off a cliff.
WorldCollision derives both from EventListener & EventProducer .
when it receives HERO_MOVE_END event, and if this is a falling point, it sends COLL_HERO_FALL_REQ back.

void WorldCollision::handleEvent(const EventProducer& sender, const event::arg::EventArgs& args) { ...
    switch (args.eventType) { ...
        case event::EventType::HERO_MOVE_END: {
            const auto& hero = static_cast<const entity::hero::Hero&>(sender);
            const auto heroPos = hero.getMCellPos();

            for (const auto& fallPoint : _mTilemap->getFallPoints())
                if (heroPos == bn::point{ fallPoint.mCellX, fallPoint.mCellY })
        } ...

Code above uses static_cast, so I should be extremely careful about who the actual sender is, to avoid invalid cast.
Who's the actual sender? It is prefixed on EventType (e.g. EventType::HERO_*, EventType::COLL_*).
I know this is kinda dangerous, maybe I should move to bn::any & any_cast instead?

Then, our player class (it's Hero) will receive this COLL_HERO_FALL_REQ event, and do what they want.
I've also implemented event filtering, so only the Hero will receive COLL_HERO_FALL_REQ event.

You can also use custom event arguments which derives from event::arg::EventArgs
And you can delay the event for certain frames, thanks for EventQueue using priority_queue internally.

void Hero::handleHeroMoveReq(const event::arg::HeroMove& moveArgs) { ...
    // send `HERO_MOVE_BEGIN` event right now, with `arg::HeroMove` event arguments.
    sendEvent<event::arg::HeroMove>({event::EventType::HERO_MOVE_BEGIN, moveArgs.direction, moveArgs.isAtStairs});
    // send `HERO_MOVE_END` event after `constants::HERO_WALK_FRAMES` ticks.
    sendEvent<event::arg::HeroMove>({event::EventType::HERO_MOVE_END, ...}, constants::HERO_WALK_FRAMES);

Code (EventQueue)

Here's the gist of EventQueue, with some checks omitted.

class EventProducer { ...
    template <typename EArgs> requires arg::EArgsConcept<EArgs> // size & derive from `arg::EventArgs` check
    void sendEvent(const EArgs& args, int delayTicks = 0) {
        _eventQueue.pushEvent(*this, args, delayTicks);
    } ...
class EventQueue { ...
    template <typename EArgs> requires arg::EArgsConcept<EArgs> // size & derive from `arg::EventArgs` check
    void pushEvent(const EventProducer& sender, const EArgs& args, int delayTicks) {
        BN_ASSERT(_priorityQueue.size() < constants::MAX_EVENTS_IN_QUEUE);
        arg::EventArgs& newArgs = _pool.create<EArgs>(args);
        _priorityQueue.emplace(_sessionTime + core::PlayTime(delayTicks), _insertOrderCounter++, &sender, &newArgs);
    void EventQueue::propagateEvents() {
        // `EventQueueElem`s are sorted first with `time`, and then `insertOrder`.  So, I can do this.
        while (!_priorityQueue.empty() && _sessionTime >= {
            const auto& eventElem =;
            for (EventListener* listener : _listeners)
                if (listener->isSubscribing(eventElem.args->eventType))
                    listener->handleEvent(*eventElem.sender, *eventElem.args);
    } ...
    bn::generic_pool<constants::MAX_EVENT_ARG_SIZE, constants::MAX_EVENTS_IN_QUEUE> _pool;
    std::priority_queue<EventQueueElem, bn::vector<EventQueueElem, constants::MAX_EVENTS_IN_QUEUE>,
                        std::greater<EventQueueElem>> _priorityQueue;
class EventListener { ...
    virtual void handleEvent(const EventProducer& sender, const arg::EventArgs&) = 0;

    bool EventListener::isSubscribing(EventType type) const { ...
        return _subscribeFilter[(int)type];
    void EventListener::subscribe(EventType type) { ...
        _subscribeFilter[(int)type] = true;
    void EventListener::unsubscribe(EventType type) { ...
        _subscribeFilter[(int)type] = false;
    bn::bitset<EVENT_TYPE_BITSET_SIZE> _subscribeFilter;


Game Programming Patterns and Game Engine Architecture are really helpful to roll out this EventQueue, you should check them out too.

I wanted to write this post concise, but the snippet is a little too much, unfortunately.
Hope I can write it shorter next time.

Posts: 6

Gosh, 3 weeks already.
I'll just put an image and a video showcase this time.

I can now put these "hearts" on the map like below.
They will become chests with some random foods in it.
Item placement

Item usage
Download this as .mp4 video (audio included)

I can pick up some food, eat them when I'm low on HP.
Also, I can open the doors with the keys.

I also added the title screen music, although it's not finished yet.
(it's too long, and I'm bad at trascribing music)

That's all for now.