With a rough version of the game’s core mechanics in hand, I arrived at the first major hurdle of my development process - integration of a graphics API. It wouldn’t have made sense to invest any more time in iterating through mechanical variations on the main loop without a fully testable prototype; debugging with the command line can only take you so far. After some deliberation, I settled on the latest version of Simple DirectMedia Layer development library (SDL 2.0 | https://www.libsdl.org/). From the site:
“[SDL] is a cross-platform development library designed to provide low level access to audio, mouse, joystick, and graphics hardware via OpenGL and Direct3D… SDL officially supports Windows, Mac OS X, Linux, iOS, and Android… SDL is written in C, [and] works natively with C++.”
Cross-platform compatibility is a reality of the game development industry, so I wanted a first-hand experience tuning my game for a diverse array of devices. I also want all my games to be available for friends to enjoy, so cross-platform has always been a goal.
SDL’s hardware-based rendering also saves big on processor resources. In the end, time complexity was not a major constraint on the Standoff project. Still, developing for the lowest common denominator was a worthwhile exercise - in the future, I may end up expanding on the Standoff resource management module for use in projects with a greater need for efficiency.
I turned to LazyFoo’s SDL tutorial series (http://lazyfoo.net/tutorials/SDL/) to bring me up to speed on the basics of using SDL. I chose to encapsulate the application’s graphical functionality within a ‘ResourceManager’ class:
class ResourceManager_c
{
public:
ResourceManager_c();
~ResourceManager_c();
bool init();
bool loadMedia();
void close();
SDL_Renderer* getRenderer();
void renderSpriteAt(Sprite_e sprite_id,
const std::pair<int, int>& screen_tile_coord, double degrees = 0);
protected:
void createSpriteMap();
// member variables
};
My first priority was to delineate the window’s lifetime with initialization and garbage collection routines. These processes are represented by the ResourceManager_c::init() and close() methods, respectively. The former contains the start calls and validations required by the SDL library, while the latter frees memory allocated to the necessary raw pointers. This formulation already highlights one of the challenges in using SDL - namely, the need for raw pointers. Once again, my naive attempts to store a vector of pointers were met with frustration, but this time I was able to receive assistance from LazyFoo’s tutorials.
class LTexture
{
public:
LTexture();
~LTexture();
bool loadFromFile(SDL_Renderer* renderer, std::string img_path);
void free();
void render(SDL_Renderer* renderer, SDL_Rect* dest_rect,
SDL_Rect* clip = NULL, double degrees = 0);
private:
SDL_Texture* mTexture;
};
It was in this way that I arrived at most elegant approach of my experiments with memory management - the LTexture class - packing the flexibility of a raw pointer alongside a suite of all relevant functions. This class acts as a wrapper for a raw SDL_Texture pointer, and allows the generalized ResourceManager to interface with each texture in turn.
LTexture::LTexture()
{
mTexture = NULL;
}
LTexture::~LTexture()
{
free();
}
bool LTexture::loadFromFile(SDL_Renderer* renderer, std::string img_path)
{
// get rid of any pre-existing texture
free();
bool success = true;
SDL_Texture* loaded_texture = NULL;
loaded_texture = IMG_LoadTexture(renderer, img_path.c_str());
if (loaded_texture == NULL) {
printf(
"Unable to load texture %s! SDL_image Error: %s\n",
img_path.c_str(), IMG_GetError());
success = false;
}
else {
mTexture = loaded_texture;
}
return success;
}
void LTexture::free()
{
if (mTexture != NULL) {
SDL_DestroyTexture(mTexture);
mTexture = NULL;
}
}
The loadFromFile() and free() methods serve to control the life cycle of LTexture’s owned SDL_Texture object. The former is invoked by the ResourceManager in order to initialize the desired textures that are to be stored as member variables. The latter is called in the destructor, ensuring that the object is destroyed upon going out of scope. These routines leverage SDL_image (SDL_image 2.0 | https://www.libsdl.org/projects/SDL_image/), an image file loading library built as an extension to the base SDL library. IMG_LoadTexture() in particular is taken from the SDL_image library; the method is dynamically loaded at runtime based on an associated .dll file, and can be configured to handle files of many of the most common image file types. The flexibility provided by this library was one of the many reasons why I chose to use SDL in the first place.

struct Sprite_s
{
Sprite_s(Sprite_e sprite_id, SDL_Rect clip) :
mSpriteId(sprite_id),
mClip(clip)
{ }
Sprite_e mSpriteId;
SDL_Rect mClip;
};
In the interest of minimizing the number of required textures, I chose to format my assets as a sprite sheet. This means that the ResourceManager only needs to load two textures at initialization - the board and the spritesheet. After loading the textures into memory, I call the ResourceManager_c::createSpriteMap() method to map individual sprites to a unique ID.
void ResourceManager_c::createSpriteMap()
{
SDL_Rect clip = { 0, 0, TILE_WIDTH, TILE_WIDTH };
for (int sprite_int = P2_PAWN; sprite_int != P2_MANHOLE_TILE; ++sprite_int)
{
mSprites.push_back(Sprite_s(static_cast<Sprite_e>(sprite_int), clip));
if (clip.x < TILE_WIDTH * NUM_SPRITESHEET_COL) {
clip.x = clip.x + TILE_WIDTH;
}
else {
clip.x = 0;
clip.y = clip.y + TILE_WIDTH;
}
}
}
My decision to use a vector of structures is derived from a desire to implement a more efficient container than STL’s std::map, at least for my purposes. This solution is workable without being overly complex, and provides a foundation for future explorations of the concept. On a different project with more constraints, a static cast might incur more overhead than would be acceptable. In this case, O(n) for the number of sprites is a small price to pay during the initialization phase.
void ResourceManager_c::renderSpriteAt(
Sprite_e sprite_id, const std::pair<int, int>& screen_tile_coord, double degrees)
{
SDL_Rect dest_rect;
if (sprite_id != BOARD) {
dest_rect = {
screen_tile_coord.first * TILE_WIDTH,
screen_tile_coord.second * TILE_WIDTH,
TILE_WIDTH,
TILE_WIDTH
};
std::vector<Sprite_s>::iterator it;
for (it = mSprites.begin(); it != mSprites.end(); ++it)
{
if (it->mSpriteId == sprite_id) {
gSpritesheetTexture.render(
gRenderer, &dest_rect, &it->mClip, degrees);
}
}
}
else { // special case for drawing the board
dest_rect = {
(screen_tile_coord.first * TILE_WIDTH) - BORDER_WIDTH,
(screen_tile_coord.second * TILE_WIDTH) - BORDER_WIDTH,
(BORDER_WIDTH * 2) + (TILE_WIDTH * 9),
(BORDER_WIDTH * 2) + (TILE_WIDTH * 9)
};
gBoardTexture.render(gRenderer, &dest_rect, NULL, degrees);
}
}
void LTexture::render(SDL_Renderer* renderer, SDL_Rect* dest_rect,
SDL_Rect* clip, double degrees)
{
if (mTexture == NULL) {
printf("Missing Texture Error!");
}
else {
if (degrees != 0) {
SDL_RenderCopyEx(
renderer, mTexture, clip, dest_rect, degrees, NULL, SDL_FLIP_NONE);
}
else {
SDL_RenderCopy(renderer, mTexture, clip, dest_rect);
}
}
}
With my assets properly loaded and structured, I still needed a way for the main game loop to interface with the ResourceManager class. To this end, I finally began to write a rendering algorithm. SDL’s render function must be passed the raw SDL_Texture pointer on invocation, so calling it from within LTexture is required. However, since the main game loop can’t directly access the LTexture class, another function is needed to mediate between the two. The ResourceManager_c’s renderSpriteAt() method serves this purpose, determining what to pass before LTexture’s own render() takes over. The ‘degrees’ parameter defaults to zero for both routines, making for a much more flexible function call overall.
That about wraps up my discussion of the graphics module built for my Standoff application. Overall, this “unit” served as the foundation for my future forays into windowing and graphics management, with SDL2 as my current framework of choice when working with OpenGL, as opposed to GLFW or GLUT. I have worked with GLUT in the past and felt that it obscured too much of the core loop functionality for use in anything other than pure graphical simulations.