The Game Developer's Guidepost

The Game Developer's Guidepost

Your guide to learning game development

Programming Simple 2D Games in D

Chapter 1: Initializing SDL2

Welcome to the first tutorial in this series! We’ll discuss how to initialize the SDL2 library, open a new window, and clear the window to a light gray background color. Let's go ahead and get started.

To begin with you'll probably want to create a new folder for this project. If you're using an IDE it will probably handle project management for you, though it's quite simple to do this yourself. Normally developers create a folder named after the project and add a sub-folder called "src" or "source" to store the source code files for the project all in one place. Organizing the files for a project in a sensible manner makes it easier both to build the project and locate specific files during development. I would suggest getting used to using this convention if you're not already as it's a good way to keep your work organized.

You'll need to to download the D bindings for the SDL2 libraries from here and add it to your project. Since this project is so simple we'll write all the source code for the game to a single file. Create a new file for this purpose called “treasure_grabber.d” (or any name you prefer) and add it to your project. It's best if you use the .d file extension to make it clear the file contains D source code. To do this on Windows you'll first need to configure File Explorer to show file extensions at the end of file names as they're typically hidden by default. Open this new file in the IDE/code editor of your choice and add the following code:

import core.stdc.stdio : fprintf, stderr;
import sdl2;

enum windowWidth  = 1024;
enum windowHeight = 576;

int main()
{
    return 0;
}

We begin by importing fprintf and stderr from the C-standard library (core.stdc.stdio) and the bindings for the SDL2 libraries (sdl2). We'll be using fprintf instead of the output functions provided by the D standard library because it makes it easier to print C-style strings (character arrays terminated by a null character) to the console. Next we define two compile-time constants for our window dimensions which will give our game a nice 16:9 aspect ratio. Next we add the entry point of every D program: the main function. It doesn’t do very much right now, but we’ll fix that! Add the following to main before the return statement:

    if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError());
        return 1;
    }
    scope(exit) SDL_Quit();

SDL2 provides a lot of convenient features, not all of which you’ll need for a given application or game. These library features are divided into individual subsystems which we can choose to load into memory if we need them. To keep things simple (after all, this is our first project) we'll just ask SDL2 to initialize all of its subsystems in one go. To do this we can call the aptly named SDL_Init function and pass it the SDL_INIT_EVERYTHING flag to initialize all of the available subsystems.

Unfortunately there’s no guarantee the call to SDL_Init will succeed. In case of failure, SDL_Init will return a negative integer. We have no way of recovering from this so we’ll wrap the call to SDL_Init inside an if statement and simply abort the application on failure. Just before we abort, though, we’ll use SDL_GetError to retrieve a description of the last error SDL2 encountered and print it to the console. This will make it easier to determine what caused SDL_Init to fail in the first place.

The call to SDL_Init allocates all sorts of resources for internal use behind the scenes. We want to make sure SDL2 will release those resources before our program exits. We do this by calling SDL_Quit. But main already has one early return statement, does this mean we'll have to remember to call SDL_Quit before every subsequent exit point? Not if you use the scope(exit) statement. This is a very useful feature of D that allows you to run a block of code at each subsequent points of exit from a given scope. This is a lot like the defer statement in Go if you're familiar with that language. Therefore, by calling SDL_Quit in a scope(exit) statement, we're pretty much guaranteed the function will be called no matter when we return from main.

Now that we have initialized all of its subsystems, let’s ask SDL2 to create a window for us:

    SDL_Window* window = SDL_CreateWindow("Treasure Grabber",
                                SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                windowWidth, windowHeight, 0);
    if (!window)
    {
        fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError());
        return 1;
    }
    scope(exit) SDL_DestroyWindow(window);

We’re calling the SDL_CreateWindow function which returns an opaque pointer to an SDL_Window allocated by the library. The arguments we pass to this function configures certain aspects of the window. The first argument sets the title of the window, in this case “Treasure Grabber.” The next two arguments decide the starting position of the window on the computer monitor. This can be specific x and y coordinates, but we’ll use the SDL_WINDOWPOS_CENTERED flag to center the window on the screen. The next two arguments determine the width and height of the window which we’ll set to the dimensions we defined earlier. The last argument takes flags that set the behavior of our window. We won’t need any specific window behavior just now so we can simply pass in zero.

There’s a chance that SDL_CreateWindow will fail to return a valid window, instead returning a null pointer. This is another failure case we can’t recover from. Just as we did with our call to SDL_Init, in the case of failure we print a description of what caused the error and then abort. Should it succeed however, we still need to free the resources SDL_CreateWindow allocated by calling SDL_DestroyWindow. Just like before, we wrap this call in a scope(exit) statement to ensure this function gets called before we exit from main.

This would be a great time to begin rendering things now that we have a valid window (if all went well). We can start simple by filling the window with a solid background color. Luckily for us SDL2 provides some very basic rendering functionality just for this sort of task. Before we can start rendering into our window, however, we need to ask the SDL2 library for a rendering context:

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer)
    {
        fprintf(stderr, "SDL_CreateRenderer Error: %s\n", SDL_GetError());
        return 1;
    }
    scope(exit) SDL_DestroyRenderer(renderer);

SDL_CreateRenderer returns a pointer to a render context allocated by SDL2. The first argument tells the library the window in which we want our game rendered. The next argument specifies the index of the video driver we want to use. Fortunately we can pass -1 to let the library choose an appropriate driver for us automatically. The last argument is a flag that determines the behavior of our renderer. Passing SDL_RENDERER_ACCELERATED lets us take advantage of hardware acceleration to boost the performance of our rendering calls.

At this point you may notice the code above is somewhat painfully similar to the last two code snippets. Not to worry though; things will get much more interesting after we're done with all this boiler-plate. Should SDL_CreateRenderer fail to generate a valid render context it will return a null pointer, a case we will handle just as we did if SDL_CreateWindow failed to give us a valid window: print the reason it failed and abort. On success we will destroy the render context before returning from main.

And now we’re finally ready to fill the screen with a solid color:

    SDL_SetRenderDrawColor(renderer, 168, 168, 178, 255);
    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);

These three lines are all we need to draw a nice light gray background color. The first function sets the color our renderer will use when drawing to the screen. We pass it our rendering context followed by the desired color’s red, green, blue, and alpha values (in that order). Each color value ranges from 0 to 255. The next function tells the renderer that we want to clear the window to the color we just set. The final function call applies all the rendering tasks we requested and displays the result inside our game window.

If you compile and run the game now you’ll only get a glimpse of the window before it quickly disappears. This is because our game will reach the end of the main function nearly instantly, promptly closing our game and the window along with it. This isn’t a very satisfying conclusion for all our hard work, so let’s add a line of code that will prevent the game from closing so quickly:

    SDL_Delay(2000);

The SDL_Delay function pauses the application for (at least) a given number of milliseconds. Here we’re telling it to wait for two seconds before our game closes. This way you’ll actually be able to take a good look at the fruits of your labor.

And that’s everything for this chapter! Try compiling and running your project. You should see a window filled with a light gray color. If not, try the following:

Chapter Resources