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

Original author: Tim Jones
  • Transfer
  • Tutorial
I searched the translation of lessons from this site in a hub, but there was only one mention, and even that was in the comments:



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

“We all started somewhere”


These lessons allow you to learn how to program using the SDL library to those people who already have any programming experience in C ++ or other languages ​​(note translator: there are many SDL bindings to other languages that are listed here and it seems to me that it is far from complete ) If, as you read the lessons, you will have some difficulty understanding the code itself and the concepts used (not a game being developed, namely program code), I recommend that you first read 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.

Used tools

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

Customization

Windows
  • Download fresh CB with MinGW enabled from the official website, install MinGW in the folder with CB;
  • Download the latest SDL development libraries (in the Downloads section, select the Development Libraries section , the Win32 sub- section 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 SDL-XXX folder will appear );
  • The contents of this folder should be completely moved to the folder where MinGW was previously installed ;
  • Copy SDL.dll from the bin subfolder of the MinGW folder to the system folders C: \ Windows and C: \ Windows \ System32 ;
  • Open CB, create a new empty project;
  • Add CApp.h and CApp.cpp files with the content indicated below under the spoilers;
  • In the menu Project> Build options , in the dialog select the Linker settings tab ;
  • In 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 swears that there are no 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 hand picking
  • Open CB, create a new empty project;
  • Add CApp.h and CApp.cpp files with the content indicated below under the spoilers;
  • In the menu Project> Build options , in the dialog select the Linker settings tab ;
  • 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 swears that there are no header files)



Source

CApp.h
#ifndef _CAPP_H_
    #define _CAPP_H_
#include 
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

I digress a little from programming itself and note that the CApp class being created defines the behavior of our program. The vast majority of games consists mainly of 5 functions called in the main cycle of the program, which process the entire game process. A brief description of each of them:

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

Event Handler
Processes all incoming messages from the mouse, keyboard, joysticks, or other devices, or program timers.

Game cycle
It processes all process updates, such as changing the coordinates of a crowd of opponents moving around the screen and trying to reduce your health, or something else (calculating the appearance of bonuses, detecting collisions).

Rendering scenes
It is engaged in displaying scenes calculated and prepared in the previous function on the screen, and accordingly, it is not required to perform any manipulations with the data (except output).

Clearing memory
Simply deletes all loaded resources (maps, images, models) from RAM, and ensures the correct completion of the game.

It is important that you understand that games run in a single cycle. As part of this cycle, we handle events, data updates, and image rendering. 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 first occurs, then in each iteration of the loop, events are processed, data is manipulated and, accordingly, drawn, after which the program finishes correctly by unloading the data. Sometimes, to create a game, event processing is not required, but it is necessary when you want the user to be able to manipulate data (for example, moving a character).
I will explain this idea with an example. 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. Firstly, 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), then we need to change this data accordingly. Then it remains to display our DonkeyHot with the updated coordinate values. You can imagine it like this:

Run, DonkeyHot, run !!!
if(Key == LEFT) X--;
if(Key == RIGHT) X++;
if(Key == UP) Y--;
if(Key == DOWN) Y++;//... где-то в коде ...
RenderImage(DonkeyHotImage, X, Y);


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

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

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 make them look like this:

CApp.h
#ifndef _CAPP_H_
    #define _CAPP_H_
#include 
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 did not happen, return -1 (error code), thereby ensuring the completion of the game. If all is well, we go into the game cycle. As part of the game loop, we use SDL_PollEvent to check messages, and pass them one at a time to the OnEvent method . Then, we go to OnLoop to manipulate the data, and then draw our game onto the screen. We repeat this cycle until a message is received which means that the user quits the game, after receiving such a message (for example, the user pressed the cross or the ESC key) we go to OnCleanupto clean the resources occupied by our game. Everything is simple.
Now, let's take a look at SDL_Event and SDL_PollEvent . The first is a structure that contains information about messages. The second is a function that will select messages from the message queue. A queue can contain any number of messages, for this very reason we use a loop to iterate through them all. For example, suppose the user clicks and moves the mouse while the OnRender function is running . The SDL will detect this and place two events in the queue, one per key and one per mouse move. We can select this message from the queue using SDL_PollEvent , and then pass it to the OnEvent methodin which it needs to be processed accordingly. When there are no events in the messages, SDL_PollEvent will return false , thus leaving the message queue processing loop.
Next we look at the variable Running. It controls the exit from the main game cycle. When this parameter becomes false , we will end the program, and transfer control to the program exit function. So, for example, if the user presses the ESC key, we can set this variable to false , thereby indicating the exit from the game.
At this stage, you can try to compile the project, but since we do not have any message processing, 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 
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 rendering. Suppose we have a board, crayon, 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 on top. Thus, Surf_Display is simply our virgin blank board, on which we will draw and glue stickers.

Now, let's open CApp_OnInit.cpp To already 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 that now we can access its functions. The SDL_INIT_EVERYTHING parameter means the initialization of all subsystems of the library (there are other parameters, for example, SDL_INIT_AUDIO , SDL_INIT_VIDEO , but for now we will not focus on them). The next function, SDL_SetVideoMode , creates our window and 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 display flags that are listed through the "|" operator. There are other flags, but so far we have enough of these. The first flag indicates SDLuse hardware acceleration, and the second - use double buffering (which is important if you do not want to get a flickering screen in the end). Another flag that may interest you now is SDL_FULLSCREEN , it switches the window to full screen mode.

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 finish work with SDL . You should note that in this function all surfaces and resources used by the game are freed.
It is also recommended that you 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 window created using the SDL should start (though it's empty so far). However, you still cannot use the internal functions of shutting down the program, and so again you will need available tools.
The final touch - we have a created window, 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 vary from keystrokes to mouse movements, and in this example, the type of event is checked. In CApp_OnEvent.cpp we expect a message about closing (specifically, when the user clicks the cross in the title bar of our window) and if this happens, the Running variable is set to false , the game ends its main loop, the ball ends, the candles go out. In the following lessons, we will look at message processing in more detail.
That's all! Now you have a good framework for creating games. It would even be cool to create a template for CB based on our project. But I’m not going to talk about how this can be done, do not hesitate to google a little (google in the help: File-> Save project as template ...).
If you have learned the material in this lesson well enough, continue to the next to learn more about surfaces.

PS One more point, after the question of Comrade Fedcomp I also wondered why the author uses the 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 reinitialize the surface without having to reload the resources. Well, or a similar task.
I would like to see reflections on this subject in the comments. Thanks to everyone who read to the end!

UPD 0.

Link to the source code (also suitable for * nix)



The result of the program.

UPD 1.

It is recommended to use SDL_WaitEvent instead of SDL_PollEvent in CApp.cpp . This will allow you to quickly respond to incoming messages and not to poll the message queue every iteration of the game cycle.

UPD 2.

Links to all lessons:

Also popular now: