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.
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 anSDL_Renderer
object.Instead of utilizing
SDL_Flip()
to display the main Surface on the screen, we now useSDL_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 toSDL_GetKeyboardState()
. Furthermore, you access the returned array usingSDL_SCANCODE_*
instead ofSDLK_*
.
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.
Subscribe to my newsletter
Read articles from Ehsan Aliakbar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by