SDL 2.0 Lesson Cycle: Lesson 5 - Slicing a Sprite Sheet

image

From a translator:

This is a continuation of a series of translations of Twinklebear tutorials, available in the original here . The translation is partly free and may contain minor amendments or additions from the translator. Translation of the first two lessons - authorshipInvalidPointerand the third and fourth for k1-801.


List of lessons:



Sprite sheet slicing


Often in 2D games, they use one large image to store several smaller images, for example, tiles in tilesets instead of many small pictures for each tile. Such an image is called a sprite sheet and it is very convenient for work, since we do not need to change the texture that we want to draw, but only indicate which part of the texture to use.

In this tutorial, we will see how to select parts of a texture using SDL_RenderCopy , as well as a little about how to detect specific keystroke events that we will use to select which part of the texture to draw. There will be four colorful circles on the sprite sheet:

image


In this tutorial, a sprite sheet consists of many sprites of the same size, in which case slicing is not difficult. Otherwise, for sprites of different sizes, we would need a metadata file with information about the location of the parts. For this tutorial we will use 4 sprites of size 100x100. The code for this lesson is based on lesson 4, if you donโ€™t already have a code to write from, you can get it from Github .

Part selection


Using the SDL, itโ€™s very easy to select the part of the texture you want to draw. In lesson 4, the remaining SDL_RenderCopy parameters with a NULL value indicate the coordinates of the rectangle that determines which part of the texture we want to draw. When passing the NULL value, we indicate that we need the entire texture, but we can easily add the parameters of the rectangle and draw only part of the texture. To do this, make changes to the renderTexture function so that it can take a rectangular area, but still keep a short version of the syntax from the old version for convenience.

Change renderTexture


In order not to bind more and more parameters to our renderTexture function and at the same time maintain the convenience of the default values, we will divide it into two functions. The first is almost identical to calling SDL_RenderCopy, but it provides a clipping region parameter with a nullptr value. This version of renderTexture will receive the location in the form of a rectangular area, which we can configure ourselves or using one of our other specialized renderTexture functions. The new basic render function is becoming very simple.

/**
*  SDL_Texture  SDL_Renderer   .
*     
* @param tex  ,   
* @param ren ,    
* @param dst     
* @param clip     ( )
*                 nullptr   
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, SDL_Rect dst,
        SDL_Rect *clip = nullptr)
{
        SDL_RenderCopy(ren, tex, clip, &dst);
}

For convenience, we will write another function where we would not need to create SDL_Rect for the location, but only provide x and y and let our display function fill the width and height of the texture. We will create an overloaded version of renderTexture that will do this with some settings to handle clipping. Add the cut rectangle as the parameter with the default value nullptr and in case the cut was passed, we will use the width and height of the cut instead of the width and height of the texture. Thus, we will not stretch the small sprite to the size of its potentially very large sprite sheet when it is drawn. This function is a modification of the original renderTexture function and looks very similar.

/**
*  SDL_Texture  SDL_Renderer   x, y, 
*          
*    ,       
*          
* @param tex  ,   
* @param ren ,    
* @param x  x,    
* @param y  y,    
* @param clip     ( )
*                 nullptr   
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y,
        SDL_Rect *clip = nullptr)
{
        SDL_Rect dst;
        dst.x = x;
        dst.y = y;
        if (clip != nullptr){
                dst.w = clip->w;
                dst.h = clip->h;
        }
        else {
                SDL_QueryTexture(tex, NULL, NULL, &dst.w, &dst.h);
        }
        renderTexture(tex, ren, dst, clip);
}

Define clipping rectangles


In our case, itโ€™s very easy to calculate clipping rectangles using the method much similar to the tiling method from lesson 3 , however, instead of going line by line, we will go column by column. Thus, the first piece will be green, the second will be red, the third will be blue and the fourth will be yellow. The idea of โ€‹โ€‹computing is the same as in lesson 3, but we run through the columns instead of rows. So our y coordinates are calculated by obtaining the remainder when dividing the tile index by the number of tiles (2), and the x coordinate by dividing the index by the number of tiles. These x and y coordinates are x and y indices, so we translate them into real pixel coordinates by multiplying by the width and height of the cutout, which is the same for all tiles (100x100). Finally, select the piece to draw, in this case the first.

We would also like to draw our pieces in the center of the screen, so we compute these x and y coordinates using the width and height of the tile instead of the width and height of the texture.

//iW  iH    
//   ,     ,     
int iW = 100, iH = 100;
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;

//    
SDL_Rect clips[4];
for (int i = 0; i < 4; ++i){
        clips[i].x = i / 2 * iW;
        clips[i].y = i % 2 * iH;
        clips[i].w = iW;
        clips[i].h = iH;
}
//     
int useClip = 0;

If instead we would use a more complex sprite sheet with rotated sprites of different sizes packed together, we would need to store information about their location and rotation in some metadata file so that we can easily find parts.

Change images based on input


To check all the parts of the image that we created, we add the keyboard input processing to the event processing loop and select the displayed part using the keys 1-4. To determine whether a key has been pressed, you can check if the event is of type SDL_KEYDOWN and if so, we can find out which key was pressed by checking the key code inside the event using e.key.keysym.sym. A complete list of event types , key codes, and other information on SDL_Event is available on the wiki.

When the key is pressed, we need to change the value of useClip to the number of the part of the image that we want to draw. With these changes, the event loop looks like this:

while (SDL_PollEvent(&e)){
        if (e.type == SDL_QUIT)
                quit = true;
        //       
        if (e.type == SDL_KEYDOWN){
                switch (e.key.keysym.sym){
                        case SDLK_1:
                                useClip = 0;
                                *break;
                        case SDLK_2:
                                useClip = 1;
                                *break;
                        case SDLK_3:
                                useClip = 2;
                                *break;
                        case SDLK_4:
                                useClip = 3;
                                *break;
                        case SDLK_ESCAPE:
                                quit = true;
                                *break;
                        default:
                                *break;
                }
        }
}

Draw a cropped image


The last thing to do is get the right part of the image on the screen! We will do this by calling our more convenient version of renderTexture to draw part of the image without additional scaling and transferring the part we want to use (the one used in useClip).

SDL_RenderClear(renderer);
renderTexture(image, renderer, x, y, &clips[useClip]);
SDL_RenderPresent(renderer);

End of lesson 5


When you start the program, you should see part 0 (green circle) in the center of the screen and be able to select different parts of the image using the number keys. If you encounter any problems, double-check your code and / or send an email or tweet to the original author.

All Articles