Exploring Game Development in C++ with SDL (libsdl / SDL2) – Beginner Tutorial from Scratch
This lesson picks up from an initial exploration of Simple DirectMedia Layer (SDL), a library commonly used to develop video games.
The library header files for development were installed using apt in Ubuntu.
The function SDL_Init was used to initialize SDL. You can retrieve the errors emitted by SDL using the function SDL_GetError. You might find it useful to use the function SDL_Log for logging, the act of recording events that took place in the program.
One of the very first things you do is create a window to hold the game screen. That can be done with the SDL_CreateWindow function. Some of the arguments it takes are the title of the window, its position, and its dimensions.
Many of the functions from SDL return a pointer, so it is common to check if anything went wrong by comparing the return value to NULL, as was done with the result of SDL_CreateWindow.
The surface of the window can be retrieved using the function SDL_GetWindowSurface. You just need to pass in the pointer to the SDL_Window struct.
You can paint a filled rectangle on a surface using the SDL_FillRect function. It takes the pointer to the surface you want to paint, the pointer to the rectangle where you want to paint, and the color. You can pass NULL instead of SDL_Rect* to paint the whole surface. For the color, you can map to the required Uint32 using the function SDL_MapRGB.
The rectangle that was painted over the window surface will not appear to the user until the function SDL_UpdateWindowSurface is called.
A call to SDL_Delay was used to halt the execution of the program for a few milliseconds so the user could see something happen before it quit.
When you allocate resources such as an SDL_Window, it is important to remember to deallocate them. In this case, we used SDL_DestroyWindow to deallocate the memory used by the window and its surface.
Before the program quits, it is also important to make a call to close SDL. That is, using SDL_Quit().
Based on that initial exploration of libsdl, the lesson goes on to show how to paint a filled rectangle on a specific area of the window surface. An SDL_Rect is defined and its address is passed as the second argument to the SDL_FillRect function.
As part of more practice, concentric filled rectangles are painted at the center of the window surface.
A small square is used to represent the player on the screen. Its position is determined by an SDL_Rect structure. Its color is determined by a Uint32 variable whose value is mapped using a call to SDL_MapRGB().
We also learn to handle player input by polling events with the function SDL_PollEvent. Simply create a variable of type SDL_Event. Then pass the address of that variable to the SDL_PollEvent function. Keep calling that function as the condition of a loop, so long as the return value is not zero, meaning there is still some input to process.
We check if the event is a call to quit the program or if the event was a keyboard event where the key was pressed down.
The SDL_QUIT event type signals that we need to end the game loop and stop the execution of the program.
The SDL_KEYDOWN event type tells us we should look into player keyboard input. we check what key was pressed using the value from event.key.keysym.sym. For example, If the value is SDLK_RIGHT, it means the right-arrow key was pressed down. We then update the player position rectangle structure accordingly, based on that input signal.
We improve the player input with collision detection at the borders of the screen. That is, we cannot allow the player to walk outside the screen boundaries. That is done with simple if statement checks on the next position coordinate requested by the user.
By the end of the lesson, we have a screen with concentric filled rectangles. A small square, representing the player, can move across that screen with the keyboard arrow keys.
Video Transcript
Welcome to another live stream from NBK TechWorld.
Let's continue our exploration of the SDL library.
SDL 2 can be used in game development.
I'm going to be using the programming language C++.
So this is picking up from something we already started.
Let's just recap the previous session.
I installed LiveSDL using my package manager.
In my case, it's a Linux Ubuntu distribution.
I did sudo apt install live SDL 2.
And I think there's a dash 2.0 dash 2.0.
With that, there's a slash ursus slash include slash SDL 2.
There are all the headers there.
The one we care about is this one, SDL dot h.
That can be included in our file.
If we do the angle brackets, SDL 2,
that's the directory from usr slash include.
And SDL dot h is the file we just mentioned.
We learned to initialize SDL, create a window,
and then paint that window blue.
To do that, we did the following.
We called SDL init past the subsystem we want to initialize.
SDL init returns a non-zero value if any error occurs.
Because of that, we do SDL log to log a message.
And we use the SDL get error to obtain the error message.
And that's replacing the percent s for the message that is logged.
We use SDL create window to create in a window whose
style is hello world, position at the center of the screen
with 640 height 480.
Check if the window, if anything went wrong,
the window will be null.
So we display an error message.
I probably want to return some other error here
from the main function.
So I return to right now.
I just added that.
Finally, we take the window surface.
That's a pointer to SDL surface.
And we fill with the rectangle over the whole surface.
No here means filled entire surface.
And with the color blue, I did SDL map RGB using the format
of the window surface with r0 g0 but b blue 255
entire blue.
Don't forget to use SDL update window surface
to actually update the window surface
and we can see something.
Because there's nothing in particular going on,
we call us the L delay to pause the execution of the program
by 5 seconds.
That is 5,000 milliseconds.
So we can see the blue screen.
And then after that, the window is
destroyed to free up memory space, destroy window.
And finally, you have to check down SDL with SDL quit.
That's what we did.
Now let's go on.
Let's explore how to fill out multiple rectangles
on the screen.
So what I want to do is try to make perhaps
concentric rectangles.
We already have the blue one.
Can we make another one?
So before updating the window, let's do SDL fill rack.
Window surface.
I don't want the entire one.
So I need to define an area of the window surface
on which I want to paint.
To do that, we need to know about the SDL rack.
So let's go into wiki.
I'm going to look for SDL fill rack.
And we have here the second argument,
parameter SDL rack pointer.
So let's create one.
Say the screen is 640 by 480.
If we want to make something, let's
do half of the screen.
How about that?
Half of the screen width and height.
So let's create a new SDL rack.
Now to do that, look at the structure.
SDL rack is, you can do it like this.
It's a structure.
You can set the x, y, w, and h.
Let's try.
So I'm going to call it rectangle 1.
I can do rectangle 1.x.
It's going to be where I want the position x for this to start.
Let's do 0, 0 for now.
Rectangle 1.y is 0.
Now the width and height, rectangle 1.width.
We can type 3, half of the 640 and half of the 480 here.
Rectangle 1.height, that's 240.
Or we could put that in a constant.
That'll probably be better.
Let's just try it out for now.
Give the address of the rectangle 1.
And give the color, right?
SDL map RGB.
Window surface format.
Give the format of the window surface.
Now we give the color.
Let's do red.
Red to 55.
Green 0, blue 0.
Let's try g++ main.cpp.
And I forgot the linker, dash, L, SDL 2.
Important to, if you use g++, the dash L
has to be after the .cpp files.
If it's before, it doesn't work.
Something, some mistake I did last time.
A dot out, let's see.
Now you can see there's a red rectangle filled, half the width
and half the height of the screens starting at 0, 0.
Let's put these, the window height, width and height
in a constant.
So I'm going to say int window width is 640.
Int window height is 480.
With that, we can use this here in the create window.
And we can use this here for the rectangle.
Height divided by 2.
Window width divided by 2.
Let's try it out.
No problem, right?
OK, so let's try moving the x and y.
How can we center that into the screen, for example?
Mind you that the axis here starts from the top left.
So x goes from top left to top right.
y goes from top left to bottom left.
So if I increase x to be, for example, 100,
you should expect it to translate to 1,000.
Translate to the right.
If you do y 200, you expect it to translate where?
Down, right?
Because the 0, 0 is at the top left.
x goes from top left to top right.
y goes from top left to bottom left.
Let's try to center that red rectangle in the screen.
The screen has a window width, right?
Now, if I want to center that, I need to translate it
by half of the width.
Half of the width.
Like I have to make sure I could translate it
to window width divided by 2.
But what's going to happen?
It's going to be in the wrong place.
Let me remove the y so we can start with just the x direction.
Let's just center lies in the x direction.
Now, you can see if I do half the width,
it's going to go to the right-hand side.
How can I bring it to the center?
Well, what if I do the following?
I take the width of the actual rectangle.
Now, the width of the actual rectangle
is window width divided by 2.
So perhaps we want to put that in a variable
to make it less confusing.
Let me say let's do the following.
Let's do the width and height first.
And then we can reference it here.
So we can do the x.
Let's translate it by rectangle dot width divided by 2.
What happened?
Rectangle was not this squared.
Rectangle 1.
Now, it exactly matched the center.
And that's because I used the width being half of the screen.
Maybe let's try to make it less confusing.
If the rectangle width was perhaps not half of the screen,
but let's do a little more than half, maybe 3 quarters.
About that.
If you do that, that's what you get.
And that's not right, right?
It's not centered.
So how can we center that?
Let me increase the delay so you can see more.
So we have to bring it to the left.
Let me think about that.
If I had some way of drawing here.
So if you shift that by the window width divided by 2,
and then you subtract that width, what do we get?
Not right.
Let's see.
OK.
So you have a rectangle here.
And you want to move to the center here.
Here's the 0, 0.
So you want to be able, half the screen is here.
Let's say this is divided by 2.
By how much should I move this?
So we center the rectangle horizontally.
If I move by the width of the actual inner rectangle,
it would move by what?
It would be like here.
That's wrong.
If I move by half of the screen,
it would be like here, which is also wrong.
What if I move by half of the screen,
and then I subtract half of the actual rectangle thing.
That would go that way.
And it would be like this.
Let's try that.
So take the width of the screen, divide by 2,
and subtract half of the width of the rectangle.
And now it's centered.
You can do the same for the y.
You can do the same for the y.
You can do the window height,
divide by 2 minus rectangle 1 dot height,
divide by 2.
And if you want, you can refactor that.
Now it's centered.
You can simplify this, right?
Both are divided by 2,
so maybe you want to do window width minus rectangle width
and divide by 2.
Window height minus rectangle height and divide by 2.
That should be the same.
There you go.
Let's make the height,
let's increase that to 3 quarters.
So it takes more space up in the bottom.
And let's make more concentric rectangles.
You can see now it's like that.
Let's make more inner rectangles, maybe two more.
So essentially I'm doing the same thing.
I have to define as the L-rect for rectangle 2,
and I have to define the width and the height.
Rectangle 2 dot width.
In this case would be, let's do instead of 3 quarters,
let's do actually half of the screen.
Window width divided by 2, rectangle 2 dot height,
window height divided by 2.
And then you do the same procedure here essentially.
You just copy and paste.
And you change this to 2.
You can see I could start making a loop
and making reusable functions for this.
And then you do the same thing again, right?
Call SDL fill-rect.
You have to give the surface you want to paint on.
In this case, window surface.
And then you give the address, right?
Because it expects a pointer to SDL-rect.
Other rectangle 2.
This is rectangle 2 that finds the area of the surface
that we want to paint.
And this is the area like in the inner area.
Then you give the color.
You can get a color using the SDL map RGB.
Give the format of the window surface and the surface
dash greater than format.
And give the color.
Let's do red, green.
Let's do green.
So 0 to 55, 0.
So red is 0.
Green is 255 full.
And blue is 0.
With that, we can do the thing again.
And what happened?
Something happened there.
Oh, it didn't work.
Because I put one here, I forgot to change this to 2.
Let's try again.
There you go.
Now there's a green inside.
Anyways, I think you get the point.
It's just the same thing over and over.
You can make some kind of inner concentric rectangles
if you make a loop.
That would be kind of cool.
Nice exercise you could do.
Essentially, you just loop and keep painting rectangles
inside and smaller and smaller.
And here's the code.
Just keep doing the same thing over and over.
Now this is cool now, but let's try to get user input.
Let's try painting some rectangle that moves on the screen.
That's like a precursor to a player moving on the screen
as you press the keyboard.
That's something interesting.
To do that, we have to deal with events.
SDL events.
This is what we're going to use.
They say you have to pull the events.
Then you figure out what to do based on what the user typed
or whatever they did.
To do that, let's do the following here.
We're going to have to have some sort of game loop
so it doesn't quit.
Then we're going to handle the quit.
Then we're going to handle user input.
Maybe the keyboard errors.
We're going to do the following.
You're going to create an event up here.
Let's do it here after all these rectangles.
SDL event.
Let's call it event.
Now I have to figure out the game loop right now
because it's just quitting the application.
So let's do the following.
Let's make a bull here.
Let's figure out...
Let's do quit false.
Let's assume the user doesn't want to leave the program just yet.
So as long as user doesn't want to quit,
not quit, right?
While not quit.
Eventually the user is going to quit
so we have to set quit to true so it exits.
Otherwise it's going to be repeating forever, right?
Infinite loop.
Now while we're there,
we can start painting stuff.
As we did before, we can create the window.
It's fine.
We have the surface of the window.
Maybe I want to get this fill rectangle and whatnot.
I want to keep repainting all the time.
So this I want to move into the loop.
Let's move that inside.
Because it's going to repaint, repaint, repaint, repaint all the time.
Now with that,
I want to check for input.
Let's figure out...
We could check after the painting or before.
Maybe I should do it before.
How about that?
So before that I'm going to do...
I'm going to pull the event.
So I'm going to use SDL, pull event.
You give the address of your event variable.
Okay.
Now when you do this, it returns some value.
If the value is non-zero, that means
the user inputs something.
So this you check...
if it's not zero.
So you're going to go through all the events.
So while, pull event.
So you're going to check what's the event type.
So...
let's say the quit one.
If event.type is...
this const as dll quit.
We're going to set quit to true.
Okay.
And then with that we can
paint something and then it quits.
It stops the loop.
The surface update should be inside the loop too.
There's no more need for delay.
So we can comment that out and remove later.
Let's think about this.
So quit is...
while as long as the user doesn't want to quit,
go through all the events.
Input, for example.
See if the user want to quit.
If so, quit is true.
That's the only event we have right now.
And then paint whatever
update to surface keeps repeating.
Check events, any events.
Paint, update window surface.
Check events, paint, check events, paint.
Until finally, eventually the user
wants to quit.
Let's try that.
So you can see now it stays there
because it's continually painting.
So the program doesn't exit at all.
If I click this X, it exits.
And that's one signal, right?
As the dll quit.
That's one way you can quit.
By clicking the X in your
graphical user interface.
Now let's make a
small rectangle to indicate it's the user.
The player, right?
So what I'm going to do is
make an SDL rack here.
For player position.
So player position.
I can initialize.
Maybe I can do like this.
Let's do 003232.
So I can initialize.
I can initialize.
I can initialize.
I can initialize.
So we can do 0232
sdlf Quest
at player position.
Sdl map RGB. What's the color user.
We can make it outside.
Let me figure out what the type of color is.
is stl map RGB. It returns a Uwint 32. So Uwint 32 is the type player color. Let's do stl
map RGB, window surface format color. Let's choose whatever you want. Maybe you can do
some sort of combination. I don't know. Let's try something random. I'll do 128, 128, 128.
And then you pass that player color.
Let's see what we get. See the player is this square here in the top left.
And that's where we're going to use to move around. So we want to handle input so that the square moves
to the right, moves to down, and so on. Let's do that. So we're going to have the polling of events.
We have to check is there a keyboard, right? Event.
Let's think about that.
So let's do an else if, else if the event type was stl key down, meaning you press the key down
in the keyboard. Now there could be any key, right? So we got to, you got to figure out which
key depending on the key you pressed, you can do something about it. So if the event key,
dot key, dot key sim, dot sim, okay, it's kind of long is let's say you pressed to the right,
right arrow key. Let's do sdlk underscore right. If that was the right arrow key was pressed down,
we do something. Let's try to move that rectangle. Now we need to put player position somewhere
store. We are not date player position. But we don't have the variable here, the variable is
later comes later. So we want to bring that up there. So instead of having it here, I'm going to
bring it up and having at the beginning, actually even outside the loop, we don't have to redefine
every time. So put it outside. And we can say that player position dot x, we can add its equal
player position x plus, because you're going to the right, right, we need to increase some amount.
Let's increase the amount of the step, let's see its own width could be one way. I know it's 32
right now. So maybe you can do that 32. Let's try. So let me quit my program here. Where is it?
Press right. You can see I'm pressing the right arrow key and it goes to the right.
Because it goes really fast if you keep holding it right. If you hold it, it goes really fast.
Let's do that for the bottom going down. Let's do together. So what would you do here? There's
already the case for right. I would do for for the down. Else if event key key sim dot sim e
double equals that's the okay down player position dot y equals player position dot y as it is plus
32 because the axis goes down the screen.
Press the down arrow key and it goes down. Press the right. So
nice. Now we can't go left. You can go top to the up. Let's do that too.
So it's just the same thing. Just copy right else if event dot key dot key sim dot sim is sdl
k left player position dot x subtract right equals player position dot x minus 32.
Else if that's do the up event key key sim dot sim sdl k up player position what dot y equals player
position dot y. Now be careful here. What is it going to be? If you're going up and the axis goes
down you have to subtract minus 32. Now you can try going right down now go left now go up. Okay now it works.
Okay.
Now that's very nice. Let's see what's going to happen. Look at this.
It went off the screen when I press right. We have to figure out a way to prevent the player from going
out of bounds. Oops he went up. Oh it's off the screen and so on. So we have to cap the position.
Let's do that.
For if you're going right and the last position at the right is the width of the screen minus the width of the player.
So we cannot allow more than that. So for the going right you're going to check.
Right. You can do this. So there's two ways. You can first do the calculation and then
check right away or you can calculate and change the value first and then revert the value back if
you go out of bounds. Either way is fine. So for example let's do a precheck and not change anything
unless you're still in bounds. So for that let me put it here. So we're looking at this gray
player thing and we're figuring out the out of bounds here. We don't want the player to go out of
bounds. So if you take the player position X and you add 32 and this goes greater than or equal to
right. The window width. Well got the window width. Let me show you. Let me do another.
So we got the whole screen and you are here and this is what this is.
The window width right minus the player width.
So if you try to go here out of bounds. So that means that your X for the player is right at the
X for the width of the whole screen. So that's why I do that. If that is the case I don't want to do
anything. So I should actually be handling just the case where this is still less than the window width.
In that case I would still do the calculation. Let me see if that was working.
So now I'm going to go to the right. I'm going to press right until I reach the last tile.
Now I'm the last one. I'm going to try going right again. I cannot. I'm pressing the right
arrow. It doesn't work because of that check. The check says if I add 32 right now. If I add 32
and that's less than the width of the if that is greater right greater than or equal right here
then it won't do anything. This will have been false and the position will not change.
We have to do this the same for going up and for going down and for going to the left.
So we can cap. So let's do that. Essentially the same pattern. We only do the calculation,
the computation here and change the value if it doesn't go out of bounds. So if we're going down
you're right here. You don't want this to happen right. I'm pressing down and it goes beyond
out of bounds. So if player position dot y plus 32 right I need it to be as long as it's less than
the window height. I am fine and I'm always assuming that the step is fixed okay. If it
weren't fixed we would have to make a better computation because you've had partial
out of bounds right. Part of the player would be out of bounds. So you've got to be careful.
I'm making an assumption that the steps are 32 pixels okay.
Save it. Let's try again.
Wait.
I'm going we did down right. Okay it's fine now. I'm pressing down down down now it doesn't go.
Let's do for the other ones. If player position dot x minus 32 as long as this is greater than
zero next time right because zero is the beginning of the screen there. As long as greater than zero
we're good. Save it close here.
Oops seems like I messed up something. Why why does it happen because I subtracted 32
right. I'm trying to go to the left. I subtract 32. That means the player is now at position zero
but position zero is fine actually. That's the beginning so I need to have a greater than or
equal to. If it's equal to zero that's fine as well. Try again.
I'm pressing left it doesn't go. Do the same now for up.
If player position dot y minus 32 is still greater than or equal to zero that's totally okay.
Okay.
Close this recompile and execute. Going down going up going left going right.
So we're capped where we thin bounce we cannot get our bounds down.
All right.
So with that I think this is going to be the end of this live.
Let's recap everything we did. We learned how to make more paint more rectangles
in the window surface. We learned about creating an SDL rect to define an area,
a specific area of the surface on which we want to paint. As the L rect is a structure you can
define the w for width h for height x for x coordinate y for y coordinate. We learned to
make a computation to centralize in the horizontal and vertical. If we got the width or height of
the screen subtract the width or height of the object and take that divide by two.
We learned to define a position for the player and we added the game loop
that once you click the x it will quit for us. Now the program continuously paints and doesn't
exit until we tell it to do so. We learned how to handle events with the SDL pull event,
create an SDL event and then give the address. As long as it's not great in the zero that means
there's something there we have to process and we process now the key down events. Meaning the
user pressed down a key in the keyboard. In this case we handle the arrow keys right left up and
down. And then we do when what we do is we change the position of the player depending on the key
that was pressed and that is reflected now in the application program. The gray rectangle or square
represents the player and he can now move around. We also learned how to cap the player to the bounds
of the screen so it doesn't go out of bounds. So we can log a go out of bounds if you try to do so.
All right.
So that's it and I hope you see you next time. Goodbye.