📜 ⬆️ ⬇️

Translation SDL Game Framework Series. Part 2 - SDL Coordinates and Bliting

Taking the first lesson as a basis, we will delve into the world of SDL surfaces. As I said, SDL surfaces are mostly images stored in memory. Imagine that we have an empty window of 320x240 pixels. In the SDL coordinate system, the window is represented as follows:



This coordinate system is different from the one you are used to (I'm talking about Cartesian). But the main difference between these systems is that the Y coordinate “grows” down. Understanding the SDL coordinate system is important in order to properly draw the images on the screen, so take a good look.

Since we already have a prepared and customized surface ( Surf_Display ), we just need to find a way to draw images. This method is called blitching (from the English. Blitting - moving a group of bits from one place to another, in our case means transferring the image (or part of it) over the other), i.e. a kind of overlay. But before we can do this, we must also find a way to load these images into memory. SDL offers a simple function to implement your plans - SDL_LoadBMP (note: SDL_LoadBMP only allows uploading images in * .BMP format, as its name suggests. To upload images of other formats, SDL_image must be connected to the project, as comrade rightly noted in the comments alrusdi in the first lesson, and use the IMG_Load function). Sample code might look like this:
')
Example
SDL_Surface* Surf_Temp; if((Surf_Temp = SDL_LoadBMP("mypicture.bmp")) == NULL) { //! } 


Everything is quite simple here, SDL_LoadBMP accepts just one argument as a parameter - the path to the file you want to load, and it returns the surface containing the specified image. If the function returns NULL , then either the file was not found, or it was damaged, or other, more complex errors occurred. Unfortunately, to the detriment of efficiency, this method does not provide full coverage of various loading errors. Very often the loaded image does not correspond to the pixel format of the surface into which we load it. Thus, during display, there may be a loss of performance, image colors, etc. (It is important that the prepared surface and the loaded image match each other in all respects, that is, (exaggerating) the size of the box would fit the size of the load). Fortunately, SDL has a quick and painless workaround to this problem - SDL_DisplayFormat . This function adjusts the already loaded image, and returns a new surface suitable for the format displayed.
Now you need to open the project created in the previous lesson and add two files: CSurface.h and CSurface.cpp . Open CSurface.h and add the following:

CSurface.h
 #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); }; #endif 


Thus, we have created a simple OnLoad function that will load the surface for us. Now open CSurface.cpp and add:

CSurface.cpp
 #include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; } 


So, a couple of things you should pay attention to:
1. Always reset your pointers before using them in any way (NULL or “0” does not matter). This will help avoid tuevuyuchi most different problems and errors;
2. Remember that SDL_DisplayFormat returns a new surface based on the old one, so do not forget to release the resources occupied by that old surface. Otherwise, we will observe the surface "wandering" in memory as it pleases.
Now we have a way to load surfaces into memory, but we also need a way to map them to other surfaces. As well as for loading images, SDL has a function for this: SDL_BlitSurface . It may not be as easy to use as SDL_LoadBMP , but don't be scared. Open CSurface.h and add the following function prototype:

CSurface.h
 #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); }; #endif 


Open CSurface.cpp again and add the following:

CSurface.cpp
 #include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; } bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR); return true; } 


First of all, let's take a look at the arguments that are passed to the OnDraw function. We see two surfaces, and two variables of type int . The first surface is taken as the base (remember the board in the first lesson?), I.e. the one to which we will display everything in the future. Accordingly, the second surface is the one that we will impose on the base (and here are our stickers). Basically, we just place Surf_Src on top of Surf_Dest , that's the whole secret. X and Y are variables that denote the coordinates of a place on the surface of Surf_Dest to which we will map Surf_Src .
At the beginning of the function, we must make sure that we have surfaces, otherwise we return false . Next, we create a variable of type SDL_Rect . This is the SDL structure, which consists of four properties: X , Y , W , H. Of course you have already guessed that it sets the parameters of the displayed region of the surface. So far we are only interested in the coordinates of the place in which we will display the rectangle, and we don’t give a damn about its size. So, then we assign the coordinates passed to the X , Y function to the structure of the displayed region. If you are wondering what the NULL parameter was in the SDL_BlitSurface (yes, the author, we are interested!), This is another parameter of the type SDL_Rect . We will come back to this later.
Later came! I think that no one will be offended if we analyze the SDL_BlitSurface signature a little earlier. Briefly I will explain: we do not always need to display the entire surface on top of another, there are many cases when you need to select some part of the image (for example, we have a tileset (from the English tileset - a set of images, just a lot of pictures collected in one image) and you need to choose from it a certain square with a texture or character, etc.). And so

 int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect); 

takes as parameters in order, from left to right:

I think now everything has become more or less transparent and understandable.
At the end of the function, we draw the configured surfaces and return true .

Now, to make sure everything works, let's create a test surface. Open CApp.h , and add a new surface, and include the CSurface.h header file we created :

CApp.h
 #ifndef _CAPP_H_ #define _CAPP_H_ #include <SDL/SDL.h> #include "CSurface.h" class CApp { private: bool Running; SDL_Surface* Surf_Display; SDL_Surface* Surf_Test; public: CApp(); int OnExecute(); public: bool OnInit(); void OnEvent(SDL_Event* Event); void OnLoop(); void OnRender(); void OnCleanup(); }; #endif 


Also in the constructor, do not forget to zero our surfaces first:

CApp.cpp
 CApp::CApp() { Surf_Test = NULL; Surf_Display = NULL; Running = true; } 


And remember to clean!

CApp_OnCleanup.cpp
 #include "CApp.h" void CApp::OnCleanup() { SDL_FreeSurface(Surf_Test); SDL_FreeSurface(Surf_Display); SDL_Quit(); } 


It is time to download something already. Open CApp_OnInit.cpp and bring it to this view:

CApp_OnInit.cpp
 #include "CApp.h" bool CApp::OnInit() { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { return false; } if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) { return false; } if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) { return false; } return true; } 


Make sure you actually have a file called myimage.bmp . If not, download or draw it yourself and put it in the directory with the executable file of your game. Open CApp_OnRender.cpp and add the following:

CApp_OnRender.cpp
 #include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); SDL_Flip(Surf_Display); } 


Notice the new SDL_Flip feature. It updates the buffer and displays the Surf_Display to the screen. This is called double buffering. It prepares the created surfaces first in memory, and then displays the prepared ones on the screen. If we didn’t use it, we’d see a flickering screen. Remember the SDL_DOUBLEBUF flag we specified when creating the surface? This is exactly what turns on double buffering.
Now you can compile the project and make sure that everything works correctly. You should see the image in the upper left corner of the window. If so, congratulations, you are one step closer to the real game. If not, make sure that you have myimage.bmp in the same folder as the executable file, and also that it opens normally in the graphics viewer. That's what happened with me:



(And yes, I have a little cleverly modified the code and uploaded my avatar in * .PNG format using IMG_Load . I advise you to experiment with this function too, and with others too. Dare and everything will work out for you!). If you deprecated conversion from string constant to 'char*' -wwrite-strings a message that you want to change the signature of the OnLoad (char * File) function to OnLoad (const char * File) in CSurface.h and accordingly in CSurface.cpp .

Let's move on! We excused ourselves by finally displaying our first image in the window, but very often we only need to display a part of it, as an example — the tilesets listed below:

Tilesets








Those. With just one image, we only need to draw a part of it. Open CSurface.h , and add the following code:

CSurface.h
 #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H); }; #endif 


Open CSurface.cpp , and add the following function (Important, we add the second OnDraw function, and do not replace the existing one! Are you aware of the function overloading ?) :

CSurface.cpp
 bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_Rect SrcR; SrcR.x = X2; SrcR.y = Y2; SrcR.w = W; SrcR.h = H; SDL_BlitSurface(Surf_Src, &SrcR, Surf_Dest, &DestR); return true; } 


See, this is basically the same function as before, except we added another SDL_Rect . This region allows you to specify which pixels from the overlay surface to copy to the main one. Now, along with the coordinates, we also indicate the remaining two parameters - the width and height of 0, 0, 50, 50 and as a result we get the displayed region in the form of a square 50x50 pixels.



CApp_OnRender.cpp
 #include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50); SDL_Flip(Surf_Display); } 


And here is a piece of my avatar with an indent of 100 pixels from the top and left edge of the screen:



Links to source code:


Links to all lessons:

Source: https://habr.com/ru/post/167035/


All Articles