📜 ⬆️ ⬇️

Translation SDL Game Framework Series. Part 1 - SDL Tutorial Basics

I searched for Habra translation of lessons from this site, but there was only one mention, and even then - in the comments:



That's why I decided to correct the situation, tried to supplement and diversify my examples with my own groundwork, and at the same time I practiced translation. Also, since my favorite OS was WinXP first and now Ubuntu , I will try to make cross-platform examples, capturing as many nuances as possible of the settings for these platforms. In this series of lessons we consider the creation of a frame for enough to start developing 2D games.
What came of it
')

“We all started with something”


These lessons allow you to learn how to program using the SDL library to people who already have any programming experience in C ++ or other languages ​​(note of the translator: there are many SDL bindings to other PLs, a list of which is given here and it seems to me that it is far from complete) ). If, as you read the lessons, you will have some difficulties with understanding the code itself and the concepts used (not the game being developed, namely the program code), I recommend you first familiarize yourself with our C ++ Programming Lessons . It is not necessary to read all these lessons, but each of them, in the future, will contribute to a deeper understanding of what we will create.

Tools used

In the lessons, the IDE will use Code :: Blocks (hereinafter CB), which includes GCC and MinGW for compiling projects. You can use your favorite IDE and compilers, but this can go sideways if you do not have enough experience in connecting libraries. First you need to download CB from here by selecting a binary build that includes MinGW , for example codeblocks-12.11mingw-setup.exe . It is recommended to use a stable version in order to save time.
As you already understood the focus will be concentrated around SDL (Simple DirectMedia Layer) - a cross-platform library for 2D graphics display. Then there are a lot of words about what a wonderful library (well, this is already known), a lot of words about the setting (which for some reason did not start for me), the author’s apology for copying the SDL header files into the example directory and so on in their examples. As promised, I'll write some ad-libbing, how can I configure a bunch of CB & SDL on Windows and Ubuntu . I will also use the relative path to the header files in the MinGW folder (well, it's just more convenient for me).

Customization

Windows
  • Download fresh CB with MinGW enabled from the official site, install MinGW in the folder with CB;
  • Download the latest SDL development libraries (in the Downloads section, select the Development Libraries section, the Win32 subsection, and the SDL-devel-XXX-mingw32.tar.gz file, where XXX is the current version number);
  • Unpack the archive into a temporary folder (after unpacking, the folder SDL-XXX will appear);
  • The entire contents of this folder should be moved to the folder where MinGW was previously installed;
  • Copy SDL.dll from the MinGW folder subfolders to the system folders C: \ Windows and C: \ Windows \ System32 ;
  • Open CB, create a new empty project;
  • Add the CApp.h and CApp.cpp files with the content listed just below under the spoilers;
  • In the Project> Build options menu, in the dialog select the tab Linker settings ;
  • In the link libraries, click add and enter mingw32; SDLmain; SDL ;
  • (you may have to change CApp.h and write SDL / SDL.h instead of SDL \ SDL.h if the compiler will swear for the lack of header files)


Ubuntu
  • sudo apt-get install codeblocks libsdl-ttf2.0-0 libsdl-ttf2.0-dev libsdl-image1.2 libsdl-image1.2-dev libsdl-mixer1.2 libsdl-mixer1.2-dev libsdl1.2-dev libsdl1 .2debian-all libgl1-mesa-dev libglu1-mesa-dev libglut3-dev xorg-dev libtool gforth
  • For lovers of picking hands
  • Open CB, create a new empty project;
  • Add the CApp.h and CApp.cpp files with the content listed just below under the spoilers;
  • In the Project> Build options menu, in the dialog select the tab Linker settings ;
  • In Other linker options, enter -lSDLmain -lSDL ;
  • (you may have to change CApp.h and write SDL / SDL.h instead of SDL \ SDL.h if the compiler will swear for the lack of header files)



Source

CApp.h
#ifndef _CAPP_H_ #define _CAPP_H_ #include <SDL/SDL.h> class CApp { public: CApp(); int OnExecute(); }; #endif 


CApp.cpp
 #include "CApp.h" CApp::CApp() { } int CApp::OnExecute() { return 0; } int main(int argc, char* argv[]) { CApp theApp; return theApp.OnExecute(); } 



Game concept

A little retreat from the programming itself and note that the CApp class being created defines the behavior of our program. The overwhelming majority of games consist mainly of 5 functions called in the main cycle of the program, which process the whole game process. Brief description of each of them:

Initialization function
Handles all data downloads, whether textures, maps, characters, or any other (audio / video for example).

Event handler
Handles all incoming messages from the mouse, keyboard, joysticks, or other devices, or software timers.

Game cycle
Handles all process updates, such as changing the coordinates of a crowd of opponents moving across the screen and seeking to reduce your health, or something else (calculating the appearance of bonuses, collision detection).

Scene drawing
It is engaged in displaying scenes calculated and prepared in the previous function on the screen, and, accordingly, it is not necessary to make any manipulations with the data (about output).

Memory clear
It simply removes all loaded resources (maps, images, models) from the RAM, and ensures the correct completion of the game.

It is important that you understand that the games are executed in one cycle. As part of this cycle, we process events, data updates, and image visualization. Thus, the basic structure of the game can be considered as follows:

Gameloop
 Initialize(); while(true) { Events(); Loop(); Render(); } Cleanup(); 


As can be seen from this scheme, initialization occurs first, then at each iteration of the cycle, event processing takes place, data is manipulated and, accordingly, drawn, after which the program terminates correctly, unloading data. Sometimes to create a game, event handling is not required, but it is necessary when you want the user to manipulate data (for example, moving a character).
Let me explain this idea by example. Let's say we have a game hero, let's call him DonkeyHot. All we want to do is just make it move around the screen (i.e. if we press the left arrow, it goes left, etc.). We need to figure out how to do this in a loop. First, we know that we need to check a specific message (in this case, a message from the keyboard about a keystroke). Since we already know that the event handler of our game is responsible for updating the data (changing the coordinates of DonkeyHot in particular), we, accordingly, need to change this data. Then it remains to display our DonkeyHot with updated coordinate values. You can imagine this:

Run, DonkeyHot, run !!!
 if(Key == LEFT) X--; if(Key == RIGHT) X++; if(Key == UP) Y--; if(Key == DOWN) Y++;//... -   ... RenderImage(DonkeyHotImage, X, Y); 


It works as follows: in the cycle, it is checked whether the keystroke LEFT, RIGHT, etc., was checked, and if so, we decrease or increase the variable responsible for the X or Y coordinate. So, if our game is played at a frequency of 30 frames in second, and we press the LEFT key, our DonkeyHot will move to the left at a speed of 30 pixels per second (about FPS is perfectly written here ). If you still do not understand the device of the game cycle, then do not worry - you will soon understand.

Much water has flowed since we moved away from the project in CB and began to comprehend the device of a typical game cycle. It is time to return to it and add the following files with the following content:

CApp_OnInit.cpp
 #include "CApp.h" bool CApp::OnInit() { return true; } 


CApp_OnEvent.cpp
 #include "CApp.h" void CApp::OnEvent(SDL_Event* Event) { } 


CApp_OnLoop.cpp
 #include "CApp.h" void CApp::OnLoop() { } 


CApp_OnRender.cpp
 #include "CApp.h" void CApp::OnRender() { } 


CApp_OnCleanup.cpp
 #include "CApp.h" void CApp::OnCleanup() { } 



Open CApp.h and CApp.cpp, and bring them to this view:

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


CApp.cpp
 #include "CApp.h" CApp::CApp() { Running = true; } int CApp::OnExecute() { if(OnInit() == false) { return -1; } SDL_Event Event; while(Running) { while(SDL_PollEvent(&Event)) { OnEvent(&Event); } OnLoop(); OnRender(); } OnCleanup(); return 0; } int main(int argc, char* argv[]) { CApp theApp; return theApp.OnExecute(); } 


You see some new variables and methods, but let's see what happens first. First, we try to initialize our game, if initialization has not occurred, return -1 (error code), thereby ensuring the completion of the game. If all is well, we go into the game cycle. Within the game cycle, we use SDL_PollEvent to check messages, and pass them one by one, to the OnEvent method. Then, we go to OnLoop to manipulate the data, and then draw our game onto the screen. Repeat this cycle until a message is received which means that the user leaves the game, after receiving such a message (for example, the user pressed the cross or the ESC key) we go to OnCleanup to clear the resources occupied by our game. It's simple.
Now, let's look at SDL_Event and SDL_PollEvent . The first is a structure that contains information about the messages. The second is the function that will select messages from the message queue. A queue can contain any number of messages, it is for this reason that we use a cycle to loop through them all. For example, suppose the user presses and moves the mouse during the execution of the OnRender function. SDL will detect this and place two events in a queue, one per key and one per mouse movement. We can select this message from the queue using SDL_PollEvent , and then pass it to the OnEvent method in which it needs to be processed accordingly. When there are no events in the messages, SDL_PollEvent returns false , thus leaving the message queue processing cycle.
Next we look at the variable Running. It controls the exit from the main game loop. When this parameter becomes false , we will terminate the program, and transfer control to the exit function of the program. So, for example, if the user presses the ESC key, we can set this variable to false , thereby marking the exit from the game.
At this stage you can try to compile the project, but since we do not have any processing of the messages, you will most likely have to use the available tools to complete the program.
Now that everything is set up, let's start by creating a window for our game, which will be displayed. Open CApp.h and add a variable of type SDL_Surface . Everything should look like this:

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


I suppose now is the time to explain what SDL_Surface is . SDL_Surface is a structure in which we draw a frame of our game and which we display after drawing. Suppose we have a board, a piece of chalk, and a bunch of stickers, so SDL_Surface is our “board” (displayed surface), we can do anything with it: stick stickers on it, draw on it. In turn, stickers can also be represented as SDL_Surface : we can draw on them and glue other stickers over. Thus, Surf_Display is just our pristine clean board, on which we will draw and glue stickers.

Now, let's open CApp_OnInit.cpp to create this surface:

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; } return true; } 


The first thing we did was initialize the SDL , so now we can access its functions. The SDL_INIT_EVERYTHING parameter means the initialization of all library subsystems (there are other parameters, such as SDL_INIT_AUDIO , SDL_INIT_VIDEO , but for the time being we will not focus on them). The next function, SDL_SetVideoMode , creates our window, and the base surface. It takes 4 parameters: the width of the window, the height of the window, the number of bits used (16 or 32 is recommended), and the display flags listed through the operator "|". There are other flags, but we still have enough of these. The first flag tells SDL to use hardware acceleration, and the second to use double buffering (which is important if you do not want to end up with a flickering screen). Another flag that might interest you now is SDL_FULLSCREEN , it switches the window to full screen.

Now that the initialization is complete, it's time to take care of the cleanup. Open CApp_OnCleanup.cpp and add the following:

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


So we complete the work with the SDL . You should note that this function releases all surfaces and resources used by the game.
It is also recommended to set Surf_Display to NULL in the class constructor, in order to avoid unpleasant moments. Open CApp.cpp and add the following:

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


Try to compile the project and look at its work. Your first SDL- created window should start up (although it’s still empty). However, you still cannot use the internal functions of the program’s shutdown, and therefore you will again need the available tools.
The final touch is that we have the window created, and now we need a way to close it. Open CApp_OnEvent.cpp and add the following:

CApp_OnEvent.cpp
 #include "CApp.h" void CApp::OnEvent(SDL_Event* Event) { if(Event->type == SDL_QUIT) { Running = false; } } 


The SDL_Event structure is broken down into types. These types can range from keystrokes to mouse movements, and in this example, the type of event is checked. In CApp_OnEvent.cpp, we expect a closing message (specifically, when the user clicks the cross in our window header) and if this happens, the Running variable is set to false , the game ends its main cycle, the ball is over, and the candles are off. In the following lessons we will look at the processing of messages in more detail.
That's all! Now you have a good framework for creating games. Even it would be cool to create a template for CB based on our project. That's just not going to talk about how this can be done, feel free to google a little (google in help: File-> Save project as template ...).
If you have mastered this lesson well enough, go on to the next one to learn more about the surfaces.

PS One more thing, after a question from a friend of Fedcomp, I was also curious why the author uses classes to create an instance of the game?
My opinion on this matter: the ability to change the resolution without restarting the game, because if you think logically, we only need to re-initialize the surface without the need to reload resources. Well, or a similar task.
I would like to see in the comments reflections on this. Thanks to everyone who read to the end!

UPD 0.

Link to source code (for * nix is ​​also suitable)



The result of the program.

UPD 1.

It is recommended to use SDL_WaitEvent instead of SDL_PollEvent in CApp.cpp . This will allow for prompt response to incoming messages and not polling the message queue at each iteration of the game cycle.

UPD 2.

Links to all lessons:

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


All Articles