Tag Archives: SDL2

SDL2 Drag and Drop

Hi everyone! It’s been a while since my last SDL2 article (see all SDL2 articles). Today, I’m going to show how you can use the mouse to drag an object across an SDL2 window.

Drag and drop is nowadays a standard feature of any Multiple Document Interface (MDI). Although MDI as a user interface layout has fallen out of fashion in mainstream applications (even GNOME seems to think we don’t need it), it has enabled so many things ranging from windows in operating system GUIs to inventory containers in games.

The source code for this article is available in the SDL2DragDrop folder at the Gigi Labs BitBucket repository.

Note: any & in the code should be just an &. Sorry about that. I’m still waiting for this to be fixed.

Displaying an Empty Window

Let’s start out by displaying an empty window. We can use the code below for this:

#include <SDL2/SDL.h>
 
int main(int argc, char ** argv)
{
    // variables
    
    bool quit = false;
    SDL_Event event;
    
    // init SDL
    
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window * window = SDL_CreateWindow("SDL2 Drag and Drop",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
    
    // handle events
    
    while (!quit)
    {
        SDL_Delay(10);
        SDL_PollEvent(&amp;event);
    
        switch (event.type)
        {
            case SDL_QUIT:
                quit = true;
                break;
        }
    
        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);
        
        SDL_RenderPresent(renderer);
    }
    
    // cleanup SDL
    
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    
    return 0;
}

Most of this should be familiar from “Showing an Empty Window in SDL2“, but there are a few differences:

  • The #include on the first line is different from that of many of my previous SDL2 articles. That’s because I’m now using SDL2 on Linux (see “How to Set Up SDL2 on Linux“).
  • We’re using SDL_RenderClear() to give the window a background colour (as we’ve done in a few earlier articles).
  • We’re using SDL_PollEvent() to check for events all the time (combined with SDL_Delay() to occasionally give the CPU a break). This is also something we’ve done a few times before.

On Linux, assuming my file is called main.cpp, I can compile this from the command-line as follows:

g++ main.cpp -lSDL2 -lSDL2main -o sdl2exe

Just to be super clear (as this has been a point of confusion in earlier articles), I’m using the C++ compiler. We’ll be using a couple of C++ features, so please don’t try to compile this article’s code as-is with a C compiler.

Once this compiles successfully, I can run the executable that was produced as a result:

./sdl2exe

…and I get an empty window:

Adding Rectangles

We’re going to need something we can drag around, so let’s add a couple of rectangles. Again, we’ve done this before (see “SDL2 Bounding Box Collision Detection“), but we’ll improve a little by storing our rectangles in a C++ STL list.

First, add the necessary #include at the top of the file:

#include <list>

With this in place, we can now create a couple of SDL_Rects and put them in a list. The following code goes before the event loop.

    SDL_Rect rect1 = { 288, 208, 100, 100 };
    SDL_Rect rect2 = { 50, 50, 100, 80 };
    
    std::list<SDL_Rect *> rectangles;
    rectangles.push_back(&amp;rect1);
    rectangles.push_back(&amp;rect2);

Towards the end of the event loop, we can add code to draw those rectangles. Since we’re using a list, we can add more rectangles if we want, and this part of the code isn’t going to change. Note that I’m also using a little C++11 shortcut to iterate through the list.

        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);
        
        for (auto const&amp; rect : rectangles)
        {
            SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
            SDL_RenderFillRect(renderer, rect);
        }
        
        SDL_RenderPresent(renderer);

If we compile and run this now, we get a couple of green rectangles:

Changing Colour on Click

Before we get to actually dragging those rectangles, we need a way to select one when it gets clicked, both in code and visually. For this, we’ll add a selectedRect variable somewhere before the event loop:

SDL_Rect * selectedRect = NULL;

By default, no rectangle is selected, which is why this is set to NULL.

We also need a couple more variables somewhere at the beginning of the program to help us keep track of mouse events:

    bool leftMouseButtonDown = false;
    SDL_Point mousePos;

In the switch statement within the event loop, we can now start adding mouse event handlers. We’ll start with one for SDL_MOUSEMOTION, which keeps track of the mouse coordinates in the mousePos variable we just declared:

            case SDL_MOUSEMOTION:
                mousePos = { event.motion.x, event.motion.y };
                break;

Adding another event handler for SDL_MOUSEBUTTONUP, we clear the leftMouseButtonDown flag and the selected rectangle when the left mouse button is released:

            case SDL_MOUSEBUTTONUP:
                if (leftMouseButtonDown &amp;&amp; event.button.button == SDL_BUTTON_LEFT)
                {
                    leftMouseButtonDown = false;
                    selectedRect = NULL;
                }
                break;

Finally, we add another event handler for SDL_MOUSEBUTTONDOWN, which uses SDL_PointInRect() to identify the rectangle being clicked (if any), and sets it as the selected rectangle:

            case SDL_MOUSEBUTTONDOWN:
                if (!leftMouseButtonDown &amp;&amp; event.button.button == SDL_BUTTON_LEFT)
                {
                    leftMouseButtonDown = true;
                    
                    for (auto rect : rectangles)
                    {
                        if (SDL_PointInRect(&amp;mousePos, rect))
                        {
                            selectedRect = rect;
                            break;
                        }
                    }
                }
                break;

At this point, we can add some conditional logic in the rendering code to draw the selected rectangle (i.e. the one being clicked) in blue instead of green:

        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);
        
        for (auto const&amp; rect : rectangles)
        {
            if (rect == selectedRect)
                SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
            else
                SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
            
            SDL_RenderFillRect(renderer, rect);
        }
        
        SDL_RenderPresent(renderer);

If we compile and run this now, we find that rectangles become blue when clicked:

Drag and Drop

That might have seemed like a lot of work just to highlight a rectangle when clicked, but in fact we have already implemented much of what we need for drag and drop. To finish the job, we’ll start by adding a new variable near the beginning of the program:

SDL_Point clickOffset;

This clickOffset variable will store the point within the rectangle (relative to the rectangle’s boundary) where you clicked, so that as we move the rectangle by dragging, we can keep that same spot under the mouse pointer.

In fact, when the left mouse button is pressed, we will now store this location so that we can use it later in the code:

            case SDL_MOUSEBUTTONDOWN:
                if (!leftMouseButtonDown &amp;&amp; event.button.button == SDL_BUTTON_LEFT)
                {
                    leftMouseButtonDown = true;
                    
                    for (auto rect : rectangles)
                    {
                        if (SDL_PointInRect(&amp;mousePos, rect))
                        {
                            selectedRect = rect;
                            clickOffset.x = mousePos.x - rect->x;
                            clickOffset.y = mousePos.y - rect->y;
                            
                            break;
                        }
                    }
                }
                break;

Then, while the mouse is moving, we update the selected rectangle’s position accordingly:

            case SDL_MOUSEMOTION:
                {
                    mousePos = { event.motion.x, event.motion.y };
                    
                    if (leftMouseButtonDown &amp;&amp; selectedRect != NULL)
                    {
                        selectedRect->x = mousePos.x - clickOffset.x;
                        selectedRect->y = mousePos.y - clickOffset.y;
                    }
                }
                break;

…and that is all that is needed to allow the user to click and drag those rectangles:

Wrapping Up

In this article, we’ve mostly built on concepts covered in earlier articles. By manipulating mouse events, we were able to add interactivity to rectangles. They change colour when clicked, and can be dragged around using the mouse.

There are a few things you’ll notice if you’re diligent enough:

  • If you drag the rectangles around really fast, they lag a little behind the mouse pointer. I’m not really sure how to fix that.
  • There isn’t really a proper z-index implementation, so it looks bizarre when you can drag a rectangle underneath another. This can probably be fixed by changing the order in which the rectangles are rendered, so that the selected one always appears on top of the rest.
  • Using a list is okay for a few items, but if you have a lot of objects in your window, you might want to use a more efficient data structure such as a quadtree.

Saving Screenshots in SDL2

Saving screenshots is a simple and common feature in many games. It allows us to capture the image of the game being rendered on the screen at any given time.

While this might sound easy, let us remember that the image rendered to the screen might have a lot of different overlaid surfaces, meaning that it would be a pain to recompose that image on the side of the CPU in the same way that we’re composing the image to the texture that eventually ends up in video memory.

Fortunately, this Stack Overflow answer provides a simple solution. SDL2 provides the SDL_RenderReadPixels() function, which can be used to read pixel data back from video memory. This is generally discouraged, because it is very costly to do such a thing, but taking screenshots is a one-off operation where it makes perfect sense.

The following code shows how screenshot capture was implemented in Ultima 1 Revenge. It is only slightly different from the code in the aforementioned Stack Overflow answer:

void SosariaInputController::SaveScreenshot()
{
	const Uint32 format = SDL_PIXELFORMAT_ARGB8888;
	const int width = 640;
	const int height = 400;
	auto renderer = sdl2Core->GetRenderer();

	SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, format);
	SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
	SDL_SaveBMP(surface, "screenshot.bmp");
	SDL_FreeSurface(surface);
}

While I am not showing where the sdl2Core object is coming from, just assume that it stores an instance of SDL_Renderer. With that available, all that is needed is to create a surface using one of the functions that SDL2 provides, call SDL_RenderReadPixels() to transfer the pixels from video memory to the surface, and then actually do something with the surface (in this case, we are saving it to a bitmap file). If you wanted to read back only a portion of the screen, you would pass in an SDL_Rect instead of NULL as the second parameter to SDL_RenderReadPixels().

This simple code is used to save screenshots in Ultima 1 Revenge, such as the one shown earlier.

Playing a WAV File Using SDL2

Sound effects and music are fundamental in giving life to a game. In this article, we’re going to see how we can play a simple WAV file using just the native SDL2 Audio APIs. Unfortunately these APIs are very tricky to use, and the documentation is littered with incoherent examples and legacy function calls. For this reason, most people prefer to use the SDL_mixer extension library to handle sound and music.

The source code for this article is available at the Gigi Labs BitBucket repository. It includes a sample WAV file generated with Bfxr. You will need to copy the WAV file into the output directly (along with SDL2.dll) before running the program.

While error handling has been omitted in this article for conciseness, checking the output of each SDL2 function call and showing something in case of error (e.g. using SDL_ShowSimpleMessageBox()) will save you a lot of hair-ripping experiences.

In order to use audio in SDL2, the first thing we need to do is initialise the audio subsystem when we initialise SDL2 itself:

SDL_Init(SDL_INIT_AUDIO);

We can play simple sound effects in SDL2 by loading and playing a WAV file. We can load a WAV file by calling SDL_LoadWAV(), passing in arguments which it will populate with data read from the WAV file:

	// load WAV file

	SDL_AudioSpec wavSpec;
	Uint32 wavLength;
	Uint8 *wavBuffer;

	SDL_LoadWAV("Powerup5.wav", &wavSpec, &wavBuffer, &wavLength);

The next thing we need to do is get a handle on our audio device, which is a fancy way of saying speakers (or headphones, or whatever).

	// open audio device

	SDL_AudioDeviceID deviceId = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, 0);

You’ll notice we have a bunch of arguments, and for most of them we aren’t really caring about the details and passing in NULL or 0 to get reasonable defaults. Below is a summary of what each argument does; feel free to skip it if you just want to get something up and running quickly.

  • The first argument is the name of the audio device you want to open. While you might have several, passing in NULL will give you a reasonable default audio device. You can, however, use this to explicitly name a device you want to use – see SDL_OpenAudioDevice() documentation for more detail.
  • The second argument is relevant to recording devices, and we don’t care about it for playback.
  • The third argument represents the desired audio output format. We already got this information when we read the WAV file.
  • If provided, the fourth argument will be populated with the actual output format of the audio device. In our case, we don’t care, and can pass NULL.
  • The fifth argument is for advanced scenarios and we don’t need it either.

Now that we have a handle on the audio device, we can actually play something:

	// play audio

	int success = SDL_QueueAudio(deviceId, wavBuffer, wavLength);
	SDL_PauseAudioDevice(deviceId, 0);

SDL_QueueAudio is a handy function available since SDL 2.0.4 (at the time of writing this article, the current stable version is 2.0.5) that lets you send WAV (audio) data to the audio device without having to register callback functions (which is what you’d otherwise have to do).

SDL_PauseAudioDevice() is used to pause/unpause audio playback on the audio device (depending on the value of the second parameter). By passing 0 as the second parameter, we are enabling playback (i.e. unpausing the audio device), and this allows the sound to be played.

Let’s add a small delay so that we can hear the sound before the application exits:

	// keep application running long enough to hear the sound

	SDL_Delay(3000);

Finally, let’s remember to clean up after ourselves before exiting the application:

	// clean up

	SDL_CloseAudioDevice(deviceId);
	SDL_FreeWAV(wavBuffer);
	SDL_Quit();

	return 0;

We can now run the application, and should hear the sound play if everything is set up correctly. If not, then:

  • Remember to have both SDL2.dll (if running under Windows) and the WAV file in the output directory.
  • Make sure the path to the WAV file is set correctly. If running under Visual Studio, you may also need to change the Working Directory project setting. See the last part of “Setting up SDL2 with Visual Studio 2015” for instructions on how to do this.
  • Add error handling logic, as suggested at the beginning of this article.

Drawing Lines with SDL2

We’ve seen before how we can draw pixels using SDL2, similar to a basic Paint application. The next step is to draw lines. To do this, we can reuse our pixel drawing code with something like Bresenham’s line algorithm, or we can use SDL_RenderDrawLine(), which SDL2 gives us out of the box.

The source code for this article is available at the Gigi Labs BitBucket repository.

Let’s start with the following code, which gives us a basic empty window:

#include <SDL.h>

int main(int argc, char ** argv)
{
    // variables

    bool quit = false;
    SDL_Event event;

    // init SDL

    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window * window = SDL_CreateWindow("SDL2 line drawing",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);

    // handle events

    while (!quit)
    {
        SDL_Delay(10);
        SDL_PollEvent(&event);

        switch (event.type)
        {
            case SDL_QUIT:
                quit = true;
                break;
            // TODO input handling code goes here
        }

        // clear window

        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);

        // TODO rendering code goes here

        // render window

        SDL_RenderPresent(renderer);
    }

    // cleanup SDL

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

A line is usually drawn between two endpoints: (x1, y1) and (x2, y2). When the user presses the left mouse button, we’ll store the place he clicked as (x1, y1), and then draw a line to the current cursor position (x2, y2). When he lets go of the left mouse button, we’ll store that line.

Thus, we need an integer variable for each coordinate, and a boolean that indicates whether a line is being drawn (i.e. whether the left mouse button is pressed):

    int x1 = 0;
    int y1 = 0;
    int x2 = 0;
    int y2 = 0;
    bool drawing = false;

We can now add handlers for when the left mouse button is pressed and released. At a basic level, this involves toggling the drawing flag. However, when the left mouse button is pressed (i.e. when a new line is started), we also need to record the current cursor position as (x1, y1), and reset the value of (x2, y2).

            case SDL_MOUSEBUTTONDOWN:
                switch (event.button.button)
                {
                    case SDL_BUTTON_LEFT:
                        x1 = event.motion.x;
                        y1 = event.motion.y;
                        x2 = event.motion.x;
                        y2 = event.motion.y;
                        drawing = true;
                        break;
                }
                break;
            case SDL_MOUSEBUTTONUP:
                switch (event.button.button)
                {
                    case SDL_BUTTON_LEFT:
                        drawing = false;
                        break;
                }
                break;

An additional event handler, this time for mouse movement, updates the value of (x2, y2) while the left mouse button is pressed, giving a feel that the line is being dragged:

            case SDL_MOUSEMOTION:
                if (drawing)
                {
                    x2 = event.motion.x;
                    y2 = event.motion.y;
                }
                break;

We can now add code to actually draw the line, before the call to SDL_RenderPresent():

        // draw current line

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        if (drawing)
            SDL_RenderDrawLine(renderer, x1, y1, x2, y2);

Great, we now can draw lines by left-clicking and dragging the mouse:

…but the lines disappear whenever the left mouse button is released!

In order to keep the drawn lines in the window, we’ll need to store them, and draw them on each iteration of the game loop. We first need to declare a struct that represents each line we draw:

struct Line
{
    int x1;
    int y1;
    int x2;
    int y2;
};

We then need a simple data structure to store the lines, such as a list from the C++ standard library:

#include <list>

Let’s declare a variable for the list that will hold our lines:

std::list<Line> lines;

Then, we’ll add some code so that when the left mouse button is released, the new line is added to the list:

            case SDL_MOUSEBUTTONUP:
                switch (event.button.button)
                {
                    case SDL_BUTTON_LEFT:
                        drawing = false;
                        Line line = { x1, y1, x2, y2 };
                        lines.push_back(line);
                        break;
                }
                break;

Finally, before the code that draws the new line, let’s add some code that draws all the old ones:

        // draw stored lines

        SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);

        for (std::list<Line>::const_iterator i = lines.begin(); i != lines.end(); ++i)
        {
            Line line = *i;
            SDL_RenderDrawLine(renderer, line.x1, line.y1, line.x2, line.y2);
        }

Now, all the lines you draw will remain in the window:

SDL2 Bounding Box Collision Detection

One of the most basic and fundamental pieces of logic in a game is to check whether an object hit another object. This is known as collision detection. You can use it to check whether a bullet hit the player or an enemy, or to see if the player bumped into an impassable barrier, etc.

The source code for this article is available at the Gigi Labs BitBucket repository.

Although objects in a 2D game may have various shapes, it’s usually easiest to check for collisions by assuming a rectangular boundary (i.e. a bounding box) around each object, and testing those rectangles for intersections. This is both easy to write and efficient to compute, and although it may not be 100% faithful to the shape of the object, in most cases it’s good enough.

To see how this works, we’ll draw two rectangles, and test whether they intersect. We’ll start off with the following code, which is quite similar to what we had in earlier SDL2 articles here:

    // variables

    bool quit = false;
    SDL_Event event;
    int x = 288;
    int y = 208;

    // init SDL

    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window * window = SDL_CreateWindow("SDL2 Bounding Box Collision Detection",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);

    SDL_Rect rect1 = { x, y, 100, 100 };
    SDL_Rect rect2 = { 50, 50, 100, 80 };

    // handle events

    while (!quit)
    {
        SDL_Delay(10);
        SDL_PollEvent(&event);

        switch (event.type)
        {
            case SDL_QUIT:
                quit = true;
                break;
            // TODO keyboard input handling goes here
        }

        // TODO rendering & collision detection goes here
    }

    // cleanup SDL

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;

This doesn’t do anything other than render an empty window. We can draw a couple of rectangles by using SDL_RenderFillRect(). Add the following code in the place of the rendering and collision detection comment:

        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);

        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
        SDL_RenderFillRect(renderer, &rect1);

        SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
        SDL_RenderFillRect(renderer, &rect2);

        SDL_RenderPresent(renderer);

First we’re clearing the window to a light grayish colour with SDL_RenderClear(), and then we’re drawing two rectangles (one blue, one green) using SDL_RenderFillRect(). Before each of these calls, we must set the drawing colour using SDL_SetRenderDrawColor(). Finally, SDL_RenderPresent() will render everything to the window.

Now we have two rectangles:

Adding some simple keyboard handling code (as per “Handling Keyboard and Mouse Events in SDL2“) will allow us to move the green rectangle with the arrow keys on the keyboard:

        switch (event.type)
        {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_LEFT:  rect1.x--; break;
                    case SDLK_RIGHT: rect1.x++; break;
                    case SDLK_UP:    rect1.y--; break;
                    case SDLK_DOWN:  rect1.y++; break;
                }
                break;
        }

Now we can work on the actual collision detection: if you move the green rectangle such that it hits the blue one, we’ll change the colour of both to red. We could write the code to check whether the rectangles overlap (e.g. as in Lazy Foo’s collision detection tutorial), but why bother? SDL2 gives us a handy SDL_HasIntersection() function that we can use out of the box. Our rendering and collision detection code changes thus:

        SDL_bool collision = SDL_HasIntersection(&rect1, &rect2);

        SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
        SDL_RenderClear(renderer);

        if (collision)
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        else
            SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
        SDL_RenderFillRect(renderer, &rect1);

        if (collision)
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        else
            SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
        SDL_RenderFillRect(renderer, &rect2);

        SDL_RenderPresent(renderer);

This code simply decides what colour to draw for each rectangle based on whether a collision occurred. Sure enough, when you drive the green rectangle into the blue one, both change to red:

Sweet! In this article, we learned how to draw rectangles and check whether they intersect using functions that SDL2 provides. Rectangle intersection is a simple way of checking for collision detection between the bounding boxes of objects in a 2D game. This is a simple and fast approach that works well enough for most situations. Sometimes though, you may need to use more appropriate geometry (e.g. a bounding circle works best for a ball).