Postscript 0: Properly Finding Resource Paths

In this short postscript we’ll learn how to make use of SDL_GetBasePath to properly resolve the path to our resource directory where we’ll be storing all the assets needed for each lesson. This approach lets us avoid issues with relative paths since it doesn’t depend on where the program working directory is set when it’s run. This functionality was introduced in SDL 2.0.1 so if you haven’t updated to the latest SDL be sure to grab that before getting started.

Lesson Directory Structure

SDL_GetBasePath will return the path to the directory where the application executable is, so to properly find the resource directory we’ll have to know how to change this path to get the resource directory path. For the lessons I’ll be using the directory structure below, if you’re using a different directory structure you’ll likely need to make some modifications to how the resource path is set.

Lessons/
    bin/
        executables in here
    res/
        Lesson1/
            Lesson 1's assets are stored here
    Lesson1/
        src/
            Lesson 1's source code
    include/
        Headers shared between lessons

Getting the Resource Path

We’ll now write a convenient utility header containing the getResourcePath function which will be used to resolve the resource path based on the folder structure above. The function will also take a subdirectory name to be appended to the path so we can then get the Lesson1 resource directory with getResourcePath("Lesson1"). The code below is placed into the shared include directory and named res_path.h in my code. The comments throughout the code provide more detail on what’s going on so be sure to read through them.

#ifndef RES_PATH_H
#define RES_PATH_H

#include <iostream>
#include <string>
#include <SDL.h>

/*
 * Get the resource path for resources located in res/subDir
 * It's assumed the project directory is structured like:
 * bin/
 *  the executable
 * res/
 *  Lesson1/
 *  Lesson2/
 *
 * Paths returned will be Lessons/res/subDir
 */
std::string getResourcePath(const std::string &subDir = ""){
	//We need to choose the path separator properly based on which
	//platform we're running on, since Windows uses a different
	//separator than most systems
#ifdef _WIN32
	const char PATH_SEP = '\\';
#else
	const char PATH_SEP = '/';
#endif
	//This will hold the base resource path: Lessons/res/
	//We give it static lifetime so that we'll only need to call
	//SDL_GetBasePath once to get the executable path
	static std::string baseRes;
	if (baseRes.empty()){
		//SDL_GetBasePath will return NULL if something went wrong in retrieving the path
		char *basePath = SDL_GetBasePath();
		if (basePath){
			baseRes = basePath;
			SDL_free(basePath);
		}
		else {
			std::cerr << "Error getting resource path: " << SDL_GetError() << std::endl;
			return "";
		}
		//We replace the last bin/ with res/ to get the the resource path
		size_t pos = baseRes.rfind("bin");
		baseRes = baseRes.substr(0, pos) + "res" + PATH_SEP;
	}
	//If we want a specific subdirectory path in the resource directory
	//append it to the base path. This would be something like Lessons/res/Lesson0
	return subDir.empty() ? baseRes : baseRes + subDir + PATH_SEP;
}

#endif


Using the Resource Path Lookup

With our new utility function we can easily construct the resource path for our programs and no longer need to rely on relative paths and the various hassles that come with them. To get access to this new header in our shared include directory we’ll need to add the directory include to our include path.

  • CMake: In your top level CMakeLists file add include_directories(include).
  • Visual Studio: Add the include directory through your project preferences, similar to how you set the SDL2 include directories.
  • GCC and Clang: Use the -I flag to add the directory to your include path, eg. -Iinclude.

We’ll now write a simple program that will print out the resource directory path to make sure everything is working correctly.

#include <iostream>
#include <string>
#include <SDL.h>
#include "res_path.h"

int main(int argc, char **argv){
	if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
		std::cerr << "SDL_Init error: " << SDL_GetError() << std::endl;
		return 1;
	}
	std::cout << "Resource path is: " << getResourcePath() << std::endl;

	SDL_Quit();
	return 0;
}

End of Postscript

You’ll want to double check that the path output by the test program is correct as we’ll be using getResourcePath extensively throughout the lessons to reliably find the various images and other assets we need for our programs. A related function provided by SDL is SDL_GetPrefPath which returns the path where your application can write personal files (save games, etc.).

I’ll see you again soon in Lesson 1: Hello World!