Interrupts (2) and event handling

Nothing very visual this time around, but first some good advance on the architecture. As I said last time, I started to reorganize the code. And well, just by doing that, it seems very much more like a game is being made. Progress!

Interrupts, again

Previously, I was discovering the power of interrupts to disable the visibility of sprites past a certain line, for better visual effect (or rather, to not have them on top of the HUD…). But there were different behavior on different emulators. After a discussion on the GBDK Discord with bbbbbr, one of the project’s maintainer, I changed the approach. The probable reason of the difference was the time the interrupt was taking. There was a one line difference, and with future CPU use increase, it could cause issues.

Now, there are two interrupts implemented: VBL and LCD. VBL is the vertical blanking (vertical refresh of a line); LCD is called in different circumstances, which are detailed here. I set up the LCD one to be called when the line 135 is going to be rendered. The Game Boy is 144 lines in height, and my HUD measures 8 lines. Here, we set it up at line 135.

In the LCD interrupt, we wait until there’s a horizontal blank: it will then be line 136. That’s the line where our HUD start, so we disable sprites. For all other situations, we enable sprites during the vertical blank. In the end, this enables sprites for line 0-135 and disables them for lines 136-144.

#define STATF_MODES_MASK (STATF_HBL | STATF_VBL | STATF_OAM)

// vertical blank interrupt
void VBL_isr()
{
    SHOW_SPRITES;
}

void LCD_isr()
{
    // wait until horizontal blank
    while ((STAT_REG & STATF_MODES_MASK) != STATF_HBL);

    HIDE_SPRITES;
}

int main()
{
    CRITICAL
    {
        add_VBL(VBL_isr);
        add_LCD(LCD_isr);
        set_interrupts(VBL_IFLAG | LCD_IFLAG);
        STAT_REG |= STATF_LYC; // enable LY == LYC mode
        LYC_REG = 135u; // line I want the interrupt to start minus one
    }
}

Event system, level handling

I designed a system so that I can easily implement levels in the following manner: an array of events without a fixed size, separate for each level, to easily iterate on game design.

#include "level3.h"

// note: this struct is defined elsewhere, it's there for clarity on the blog :)
struct event
{
    enum eventTypes event;

    uint8_t posX;
    uint8_t posY;

    enum parameter param;
    uint16_t paramValue;
};


struct event LEVEL3_EVENTS[] =
{
    { scrollStop, 0, 25u, p_none, 0 },
    { scrollStart, 0, 25u, p_timer, 5 },
};

uint8_t get_level3_array_size()
{
    return sizeof(LEVEL3_EVENTS) / sizeof(LEVEL3_EVENTS[0]);
}

Both enums are forever easily expandable. The paramValue associated with the parameter enum can be anything, depending on the situation.

In the example, two events are registered:

  • at posY 25, stop the scrolling;
  • at posY 25, start a timer of 5 seconds, then when completed, start the scrolling again.

Here is what it looks like:

0:00
/0:18

The most interesting part is the implementation I made in order to have different arrays without a set size, which are used for the events.

// event.h
extern struct event* events;
extern uint8_t eventsLength;

// game.c
void init_level(int8_t level)
{
    if (level == 1)
    {
        eventsLength = get_level1_array_size();
        events = LEVEL1_EVENTS;
    }
}

// event.c
void check_events(uint8_t posX, uint8_t posY)
{
    for (uint8_t i = currentEventPosition; i != eventsLength ; i++)
    {
        if (events[i].posX == posX && events[i].posY == posY)
        {
            handle_event(i);
        }
    }
}

When initializing a new level, the array is set to be used in the events functions, with a pointer and a variable holding its size. That way, I can loop without trouble.

As for the way I handle effectively the events, it’s a function pointer. It handles things differently depending on the parameters, if there’s a timer and such, but it’s just basically calling the corresponding function registered in the function pointer, depending on the event.


The next topic will be implementing my pipeline for assets management. I need to integrate a few assets to work on the next set of features, so I might as well define my workflow right now. It’ll also be the occasion to make my sprites be 8×16 rather than the current 8×8. I’m making basic implementations that will need more work, but it’s shaping up!