We Load an image from a file and draw it on-screen. We also briefly discuss platform-dependent code.

This is part of an ongoing series where we write a complete 2D game engine in C++ and SFML. A new tutorial is released every Monday. You can find the complete list of tutorials here and download the source code from the projects GitHub page.

In this tutorial we will load an image from a file and draw it on the screen. We will also briefly discuss platform-dependent code, as MacOS wants to do things a bit differently, and we want to ensure the experience is the same on every platform.

 

Working Directory

Before we can draw anything in our game we need to load the resource (in this case the image) from disk; and before we can load the resource we need to be able to locate it. Fortunately for us SFML automatically looks in the resource folder on Windows and Linux, and with a bit of extra code, on MacOS as well. SFML helps to minimise the amount of platform-specific code that we’ll have to write; however there are some occasions, such as this this one, when that will still prove necessary.

As a side-note, I have not been able to test a Linux build. I have been informed and believe the code will be able to located the image on Linux, but if you experience any problems, let me know in the comments and I’ll look into it. Eventually I’ll get round to testing it on Linux (my current work computer has Mac and Windows installed on a tiny 120GB SSD and unfortunately I just don’t have the space for another operating system!), and if any changes need to be made I’ll write a quick post on that and link to it here.

When we load a resource, we want to be able to just call a function that returns a string to what I refer to as the working directory. This is the directory that will contain our resources. We want to be able to locate our resources regardless of the OS the game is installed on. In our engine we want to be able to do something like this:

const std::string fileName = “viking.png";
const std::string filePath = workingDir.Get() + fileName;

Where workingDir.Get() will return a string to the games resource folder and “viking” is the name of our image. This abstracts away how we create the path to the file and if we need to make any changes to that process, we will only need to change the “WorkingDirectory” class (which we will be creating shortly).

To ensure that the code we write can find your resources, make sure you place any files in your IDEs ‘Resources’ folder. Screenshots of Xcode’s and Visual Studios resource folders are shown below.

Resources folder in Visual Studio.

Resources folder in XCode.

 

Implementation

Create a new class called WorkingDirectory.

WorkingDirectory.hpp
#ifndef RESOURCE_PATH_HPP
#define RESOURCE_PATH_HPP

#include <string>

#ifdef MACOS 
#include "CoreFoundation/CoreFoundation.h"
#endif

class WorkingDirectory
{
public:
    WorkingDirectory();
    
    inline const std::string& Get() // 1
    {
        return path;
    }    
private:
    std::string path;
};

#endif

WorkingDirectory.cpp
#include "WorkingDirectory.hpp"

WorkingDirectory::WorkingDirectory()
{
    path = “./";

#ifdef MACOS  // 2
    // Change the default working directory to that of 
	// the XCode resource path on MacOS.
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    CFURLRef resourcesURL =
		CFBundleCopyResourcesDirectoryURL(mainBundle);
    char path[PATH_MAX];
    if (CFURLGetFileSystemRepresentation(
		resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
    {
        CFRelease(resourcesURL);
        chdir(path);
    }
    
#endif
}

1. As you can see the Get method is straightforward, it simply returns a reference to the string “./” (initialised in the constructor), which points to the current directory. The string can also be empty i.e. “” and it will still work. So why do we need code to get the working directory if it just returns the equivalent of an empty string? Because as with many things we write at the moment, this lays the foundations for changes we will be making in future; for example: in future we may want the absolute paths to resources, and we can also create modifiers that return specific folders for different resources.

A quick note on the functions inline declaration, if you haven’t seen this keyword before: it simply means that whenever this function is called, replace the call with a copy of the functions body. This is performed at compile-time and is used to reduce the overhead of calling a method. It’s generally used for smaller, simple methods that are used frequently; such as our Get method. You can read more about inline methods here.

2. The code gets a bit more complicated when we look at the constructor of WorkingDirectory. Firstly it is surrounded by a preprocessor directive: “#ifdef MACOS”. This ensures it runs only when the compiler flag “MACOS” is used; in fact the code is removed from the compilation when the compiler flag is absent. We would only include this compiler flag if we’re building for MacOS.

The code within the preprocessor directive sets the working directory to that of the source folder (the same folder your source and resource files are located). This only needs to be performed once, so currently we need to ensure that WorkingDirectory is being instantiated at least once. Without these lines the MacOS build would not find any resources that you’ve placed in the resource folder.

If you are compiling your code within Xcode, you can add a compiler flag by following these steps:

  1. Click on the game engine project in the sidebar.
  2. Click on Build Settings on the top row.
  3. In the search bar type “custom compiler flag”.
  4. In “Other C Flags” enter “-D MACOS”. The Other C++ Flags field will be automatically populated.

You’ll end up with a screen that looks similar to the one below.

On MacOS you will also need to link to the CoreFoundation framework (last step I promise) for our custom code to work:

  1. You can do this on the General tab; you’ll find a section called ‘Linked Frameworks and Libraries’.
  2. Click on the plus sign.
  3. Locate CoreFroundation.framework.
  4. Click Add.

Now we have a method of accessing the resource directory on each platform, lets make use of it in our Game class.

Game.hpp
#include “WorkingDirectory.hpp"

Game.cpp
class Game
{    
…
private:
…
    WorkingDirectory workingDir;
};

I chose a viking sprite, available from here, to test our resource loading. Place the sprite within your projects resource folder.

Game.hpp
class Game
{
…
private:
…    
    sf::Texture vikingTexture;
    sf::Sprite vikingSprite;
};

For now if you are unsure about the difference between a sf::Texture and a sf::Sprite then don’t worry too much. They will be explained in more detail when we create our resource management system. For now, it is enough to know that a sf::Texture is a heavyweight object and we do not want to create them unnecessarily, and sf::Sprite stores a reference to a texture and allows us to draw all or a part of the texture.

With the references created we can load the image in the Game classes constructor:

Game.cpp
Game::Game() : window("that platform game")
{
	vikingTexture.loadFromFile(workingDir.Get() + “viking.png"); // 1   	
	vikingSprite.setTexture(vikingTexture);
}

1. This is where we use our newly created WorkingDirectory. Note that “viking.png” should be changed to match the name of your file.

If you run the project now you won’t see anything other than the same old white screen. This is because we are not yet drawing the sprite. We’ll do that in the Draw method (as you may have guessed).

Game.cpp
void Game::Draw()
{
    window.BeginDraw();
    
    window.Draw(vikingSprite); // Draw the sprite.
    
    window.EndDraw();
}

As previously mentioned all of our draw calls will be sandwiched between the begin and end draw methods.

With that done, when you run the game, you will now see the sprite drawn to the screen. While not a huge step (especially for anyone that has previously done any game development), it is definitely one step towards completing our new game engine.

In the next tutorial we will look at moving the sprite around the window independently of the frame rate.

As always, thank you for reading 🙂