Resurrect Pogo-a-Gogo: Part 1 - Migrate to SDL2

In 2015, I was in my first semester at university, studying Computer Engineering. As we got closer to the final exams, we had to pick a project for our Introduction to Programming course. My Teammate suggested something fun – recreating a small game from Crash Bash.

We thought it was a great idea and chose the "Pogo-a-Gogo" mini-game from Crash Bash as our project.

Crash Bash - Pogo-a-Gogo

We used SDL 1.2 and the C++ language to make it happen. We put in a lot of effort, writing lots of code and fixing many problems along the way. When it was time for our project to be graded, we were thrilled to receive an amazing 120 out of 100 marks. It was a big win for us, and it showed how much we enjoyed bringing back a classic game from our childhood.

Legacy Code Needs Maintenance

SDL1.2 became outdated back in 2012. I faced challenges trying to use the sdl12-compat library to make my old code work on my M1 MacBook. Homebrew was helpful, as it offered precompiled libraries. However, I encountered a persistent issue with linking SDLmain to my program.

This led me to ponder a significant decision: How difficult would it be to transition my code to SDL2? This move would enable it to run on newer systems like Darwin ARM64 and possibly even in web browsers through WebAssembly (WASM).

There is an Official Migration Guide, which helps a lot in this process. After a quick read through I found four major changes that needed to be addressed:

  • In SDL2, we've transitioned from using an SDL_Surface for the main screen to employ an SDL_Renderer object.

  • Instead of utilizing SDL_Flip() to display the main Surface on the screen, we now use SDL_RenderPresent() to showcase the content of a Renderer within the associated window.

  • Functions from the SDL_gfx library now require an SDL_Renderer* as their destination.

  • SDL_GetKeyState() has undergone a name change to SDL_GetKeyboardState(). Furthermore, you access the returned array using SDL_SCANCODE_* instead of SDLK_*.

My old code for creating Windows and the main Surface looked like this:

SDL_Screen *screen;

// inside Game::init() function
screen=SDL_SetVideoMode(Block_Size*Block_NO,Block_Size*Block_NO+ScoreBoard_Size,32,0);

In SDL2, it now appears like this:

SDL_Window *sdlWindow;
SDL_Renderer *sdlRenderer;

// inside Game::init() function
SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, &sdlWindow, &sdlRenderer);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");  // make the scaled rendering look smoother.
SDL_RenderSetLogicalSize(sdlRenderer, Block_Size*Block_NO, Block_Size*Block_NO+ScoreBoard_Size);

I found the concept of having distinct resolutions for the Window and Renderer quite appealing because it simplifies the process of scaling.

The next part, which is the screen refresh in the game loop, is more straightforward:

// inside Game::loop
SDL_Flip(screen);

Becomes:

// inside Game::loop
SDL_RenderPresent(sdlRenderer);

After that, we have the SDL_gfx functions, and I've incorporated several of them into my code. These include roundedBoxRGBA(), filledCircleRGBA(), and aacircleRGBA(). To fix compile issues, I've replaced every occurrence of the screen with sdlRenderer in these functions.

I removed the declaration of SDL_Screen *screen and then conducted a thorough search in my code to identify any instances where the screen variable was being used. Surprisingly, I found that only one function relied on it.

void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL )
{
    //Holds offsets
    SDL_Rect offset;

    //Get offsets
    offset.x = x;
    offset.y = y;

    //Blit
    SDL_BlitSurface( source, clip, destination, &offset );
}

Upon reviewing the function calls, it became evident that in all cases, the screen served as the value for the destination input variable.

Following the refactoring process, my updated apply_surface function now appears as follows:

void apply_surface( int x, int y, SDL_Surface* source, SDL_Renderer* destination, SDL_Rect* clip = NULL )
{
    if (source == nullptr)
        return;
    //Holds offsets
    SDL_Rect offset;

    //Get offsets
    offset.x = x;
    offset.y = y;

    offset.h = source->h;
    offset.w = source->w;

    //Blit
    SDL_Texture *sdlTexture = SDL_CreateTextureFromSurface(destination, source);
    SDL_RenderCopy(destination, sdlTexture, NULL, &offset);
}

There you have it! My game now compiles successfully. It's time to take it for a test drive and see how it Looks.

Weird Artifact Issue

My game appears just as I remembered it, but there's an issue – some strange artifacts have emerged on both sides of the game.

I performed a quick search and learned that to prevent artifacts, I needed to clear the Renderer at the beginning of each frame. Consequently, I incorporated the following line at the start of the game loop:

// start of Game::loop
SDL_RenderClear(sdlRenderer);

That's it! My game is up and running again, and I'm quite pleased with the outcome.