TwinklebearDev
Sunday, November 3, 2013
Deprecation Notice!
Hey all, this is a heads up that updated versions of the tutorials and all future tutorials written will appear on my Github pages site, since blogger has been a hassle to do proper code sample embedding and such. The new lessons also discuss updated versions of the code with some issues/uglyness cleaned up along with how to install SDL2 for your platform now that it's officially released. Not all the lessons are quite updated but I hope to get through them all soon enough. I'll see you there folks!
Saturday, October 6, 2012
Lesson 8: Timers
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.In this lesson we'll add onto our small class library of one (the Window class) by creating a simple timer and then use it in a simple program. To do this we'll be making use of the SDL_Timer functions, specifically SDL_GetTicks which returns the milliseconds elapsed since SDL was initialized.
So how would we be able to measure a time if we can only tell how long it's been since SDL was initialized? Well we could mark down the value of SDL_GetTicks when we start the timer as startTicks and then again when we stop it as endTicks. We can then subtract endTicks - startTicks to get the milliseconds elapsed during the measurement. Seems easy enough right?
Let's begin planning out how we'd like our Timer to function before we start putting it together. We'll definitely need functions for starting, stopping and getting the elapsed ticks (measured in milliseconds), and we'll also want to be able to check if the timer has been started. In addition I think it'd be nice if we could pause and unpause the timer, and maybe a function to restart it that would return the elapsed ticks and start the timer over again all in one go. We'll also need variables to track the start and pause points as mentioned above in how to use SDL_GetTicks to determine the elapsed time, along with some values to track the state of the timer.
So let's try something like this:
That looks pretty good, so let's get started implementing these functions in timer.cpp, beginning with the constructor.
When we construct a new timer we want it to be off, ie. not started or paused. We can do this like so:
In our Start function we'll want to tag the timer as started and record the value of SDL_GetTicks when Start was called, so that we can properly return the elapsed time, which is the difference between the current SDL_GetTicks and the value of mStartTicks. In our Stop function we simply want to tag the timer as stopped, by setting mStarted and mPaused to false.
For our Pause and Unpause functions we'll need to do a bit more work. We wouldn't want to pause the timer if it wasn't started or was already paused as both operations don't really make sense. When we pause the timer we'll also need to store the elapsed time so that we can preserve the timer's value so that when we unpause we continue adding on to the measured time instead of resetting it. We can do this with the method discussed above, were we take SDL_GetTicks - mStartTicks as the elapsed time, where mStartTicks is the value of SDL_GetTicks when the timer was started. So Pause should look like this:
So now mPausedTicks stores the elapsed ticks, so we can resume timing when we call Unpause. In Unpause we'll want to mark the timer as not paused and use the value of mPausedTicks to set the value of mStartTicks to have some offset from the value of SDL_GetTicks at the time Unpause is called so that we preserve the value of the timer. We can do this by setting mStartTicks to SDL_GetTicks - mPausedTicks, so Unpause also ends up being quite simple:
Our function Restart, to restart the timer and return the elapsed time turns out to be quit simple. The function Ticks is used to get the elapsed time, then the timer is restarted and the value is returned. This ordering is very important as we need to get the elapsed time before we reset the timer, or else we'll return 0 every time.
Finally we arrive at the most important function of our Timer, the one that actually tells us how much time has elapsed! We'll only want to return a value if the timer has been started, if the timer is paused we'll want to return the value of mPausedTicks (the elapsed time between Start and Pause), if the timer is running we'll want to return return the elapsed time, with SDL_GetTicks - mStartTicks. If it's not started then we'll want to return 0, as no time has been measured.
We finish off our timer with some simple getters to check if the Timer is started or paused:
And there we have it! A useful, simple timer. Now let's try using it in a program to make sure it works. We'll make a very simple program that will display the elapsed time and read some input for starting/stopping/pausing/unpausing the timer. For this program we'll also need the Window class that we wrote in Lesson 7 to provide the various graphics functions we'll need.
I'll only be posting code relevant to the specifics of using the timer in the lesson, but if you have difficulty with some code that isn't posted you can always find the full source and assets for each lesson on Github.
After opening our Window we'll want to create an instance of the Timer class and some SDL_Textures to hold our messages.
Here we also setup the message box positioning to stick a bit below the middle of the window height (recall that y is the y position of the top-left corner) and set its width and height equal to the texture's width and height.
We also want to display the value of the timer's Ticks function, the elapsed time, after the end of the "Ticks Elapsed: " message. There's one small issue though, Ticks returns an int, but we need a string to render a message with. This can be resolved by using a stringstream; we write Ticks to the stream and then pass it to the message creation function as a string, like so:
We then clear the stringstream by filling it with a blank string and set the positioning of the message to be a bit after the text message.
Within our event polling loop we'll want to check for some key presses to tell the timer to start/stop/pause/unpause as desired.
Now before we render everything we've got one last problem to solve. How are we going to update the texture displaying the number of ticks on the screen? We can do something where we simply destroy the old texture and recreate a new one with the updated Ticks, but we wouldn't want to do this if the timer is paused or stopped since there'd be no reason to change the message.
Sounds like we'll just need an if statement, and to copy down the code we used to create the texture initially:
We can put this bit of code in our logic section right after the input polling. Our rendering section will just draw the two SDL_Textures with their Rect's and we finish off the program as always by destroying our textures, calling Window::Quit and returning.
When you run the program you should see something like this:
Where the timer will start at 0 and begin increasing once you start it. Pausing will cause the timer to stop counting, unpausing will resume it from where it left off. Stopping the timer will stop it, and when restarting it will begin again at 0. Note that the value displayed doesn't reset when the timer is stopped but rather when it's resumed.
Lesson 8 Extra Challenge!
Make an additional message display to state whether the timer is stopped/paused
Hint
You'll need to make another SDL_Texture and use the Timer::Started and Timer::Paused functions to see what the timer's state is.
End of Lesson 8
Thanks for joining me! I'll see you again soon in Lesson 9: Playing sounds with SDL_mixer.Wednesday, September 5, 2012
Lesson 7: Taking Advantage of Classes
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.If you recall from the previous lessons we've used a global SDL_Window and SDL_Renderer so that we could access them from within our various functions throughout main.cpp, however using non-constant globals is a very bad idea, and when we start wanting to write more complicated programs this method will become unusable.
In this lesson we will look to object oriented programming to resolve these issues and put together a useful class to represent our Window that we can use for loading and drawing images and text. In addition to this we will discover SDL's more powerful rendering function, SDL_RenderCopyEx, which will allow us to specify rotations about points and flips to apply to the texture when it's drawn.
For this lesson you'll need to have at least some knowledge of C++ classes along with the C++11 std::unique_ptr, as we will be using both throughout the lesson.
Before we jump into coding we'll need to have a plan of how our class looks and how we want it to be used. For simplicity I've chosen to make all members of the class static, which enables us to call the functions on our window from anywhere in the program that includes its header file. It would be undesirable to do this if you plan to use SDL 2.0's new ability to support multiple windows, but for now this method will be acceptable.
Now we can move on to planning out individual functions for our class. Obviously we'll want to bring in our ApplySurface, LoadImage and RenderText functions from the previous lessons. In addition to this we will need some sort of initialization and quitting functions to create and close the window and start/exit SDL and SDL_ttf. We'll also need to be able to tell the window when to clear and present, and it would be useful to get the window's width and height, so we'll make a function for that as well.
So here's what we've got planned out for the Window class:
Where I've included the appropriate header guards and renamed ApplySurface to Draw and omitted most of its parameters as they'll be a good bit longer now that we also need to pass a rotation angle, point of rotation and flip to SDL_RenderCopyEx.
This looks pretty good for a simple Window class, so lets start turning it into real code.
The first thing I want to cover is how we'll be handling our SDL_Window and SDL_Renderer. Instead of using a raw pointer and calling SDL_DestroyX in the Quit function, we'll be using a new C++11 feature known as the std::unique_ptr. This pointer is included in the <memory> header and allows for use of lifetime managed objects. In the case of the unique_ptr it allows for only one pointer to use the object at a time and once that pointer goes out of scope it will call the destructor function chosen for the pointer, freeing the memory automatically.
It's easy to see how this unique_ptr can be useful, we don't need to worry about managing the objects memory and we don't need to worry about it not being freed in the case that an error arises. Suppose in our program we were to hit some runtime error and crash out of a function or the whole program without calling the necessary memory releasing functions. The memory would never be freed! However with a unique_ptr it would go out of scope, calling either the default destructor of the object, or a specified function passed to it to clean up the memory. Very handy indeed!
This is not to say that the new unique_ptr, shared_ptr and weak_ptr are a magical band-aid to resolve any memory issues in C++11 as they most certainly are not. Raw pointers still have their place and misuse of the new types will still result in memory issues. It's important to use the appropriate type for the application. In this case the unique_ptr is an excellent choice for our use.
Since we want to use the SDL_DestroyX function instead of the object's destructor to free the managed object, we need to specify the function signature of the desired destruction function to our unique_ptr, which in this case is the signature for SDL_DestroyX, which is void(*)(SDL_X*). So mWindow and mRenderer will end up being:
Now that we know how mWindow and mRenderer will be implemented we can move on to writing our functions, and declaring the static member variables in window.cpp.
First, the declaration of our variables: mWindow, mRenderer and mBox.
It looks a bit messy, but it really isn't so bad. We simply set our unique_ptrs to be pointing to the appropriate destructor function and set the data to nullptr.
As mentioned earlier, our constructor and destructor do nothing, so we'll skip covering writing those, as there's nothing to write and jump into putting together our Init function.
For Init we just want to start up SDL and TTF and create our SDL_Window and SDL_Renderer, so we just want to take lines 75-97 of Lesson6 main.cpp and put them into their own function, and allow for a string to be passed that will be set as the window title. Simple enough! So our function definition will look like:
Where I have used doxygen style comments, to enable usage of doxygen to generate easy to understand documentation for the code. Our implementation of the function is simply lines 75-97 of the previous lesson:
This function should look very familiar, the only difference being that we use the unique_ptr reset function to change the data that it points to, instead of the previous methods used. In this case we simply change the nullptr they're currently managing to the created SDL_Window or SDL_Renderer that they should be managing.
The Quit function is very simple, we just take the SDL_Quit and TTF_Quit calls and put them into the function.
The most changed function is our former ApplySurface function (now named Draw), which we'll need to rework to enable us to pass the extra parameters needed by SDL_RenderCopyEx. If we take a look at the documentation for the function we can see that in addition to a texture pointer, destination rect and clip rect we need an angle (in degrees), a pivot point and a flip value.
So now we know what we need, and can write the function, however I've chosen to pass the pivot point values as separate ints instead of an SDL_Point, in the future we'll create a 2d vector class and use that instead, which will also enable us to perform more advanced vector math. So our function will look like so:
Be sure to note the default parameters we've chosen, if we pass just a texture and destination, we'll get an upright drawing of the texture within the destination rectangle, with the rest of the parameters defaulting to no change.
Our function is quite easy to write as it's simply serving as a wrapper around SDL_RenderCopyEx, with one extra thing. For simplicity we want the pivot point we pass to be relative to the center of the dstRect, while SDL will consider the point relative to the texture's X and Y coordinates, so we must add an offset to center it. Alternatively, if NULL is passed as the SDL_Point* parameter it will set the rotation about the destination rect's center.
There's one other new thing tucked away in the call to SDL_RenderCopyEx, which is that in order to access the raw SDL_Renderer pointer that the function expects we use mRenderer.get() to get it from the unique_ptr.
Next we define our LoadImage and RenderText functions:
I leave writing the function to you as it's the same as the definitions in Lesson 7 with the small change to passing mRenderer.get() in place of renderer.
Finally we define our Clear, Present and Box functions. Clear simply calls SDL_RenderClear on mRenderer and Present does the same for SDL_RenderPresent. Box lets us return an SDL_Rect containing the window's width and height, which we can query using SDL_GetWindowSize. Our functions will look like:
If you have issues working out the class, my implementation can be found at the Github repo, window.h and window.cpp. Now that we've got our Window class written, it's time to try it out in an application and make sure it works.
In main.cpp we now will also need to include our window header, "window.h" in order to use the class. Instead of calling SDL_Init and etc. as done previously we can now call Window::Init and catch any errors it may throw to create our window and make sure it went ok.
We can then load images and render text using the LoadImage and RenderText functions from our class.
Here we also perform error catching to respond appropriately to any errors that may be thrown by the functions. Now we can make use of our Box function to find the center of the window and set a position rect to draw our image and text too.
In addition we create a variable called angle so that we can test out drawing things with some rotation. To change the variable we add some keydown checks to increase or decrease the value of angle.
Finally we can use the new Window functions to Clear, Draw and Present our screen with a simple call:
And before exiting the program, we simply call Destroy on the textures and Window::Quit to exit SDL and TTF.
Lesson 7 Extra Challenge!
Make it possible to set the window width and height when calling Init
Hint
Super simple! Just pass a width and height to Init.
End of Lesson 7
Thanks for joining me! See you again soon in Lesson 8: Timers.Saturday, August 18, 2012
Lesson 6: True Type Fonts with SDL_ttf
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.In this lesson we will start learning how to use the SDL_ttf library to render text using True Type fonts. For this lesson you'll need the SDL_ttf library which you should have from when you downloaded and compiled all the libraries on SDL2.0 Mercurial. If you don't have the SDL2.0 version of SDL_ttf, you should download and compile it now. The library is linked up the same as SDL_image was in Lesson 3.
In addition to the library we'll need a true type font to use to draw our text. It's important that you're careful when choosing a font as many fonts are proprietary or have rules about using them in software, and if used without the proper licensing can result in a nasty legal mess. Fortunately for us there are many excellent open fonts available online. For this lesson we'll be using a set of open source fonts recently released by Adobe which you can download directly from sourceforge, the specific font I chose for this lesson is SourceSansPro-Regular, but you can use any font you like.
Now that we've got our font downloaded and libraries linked up lets take a look at how we can work with fonts via SDL_ttf. The library provides functionality for opening and closing TTF fonts and rendering text to SDL_Surface pointers. The library also lets us specify the color and font size we'd like to use when rendering the text. You can find the full library documentation here.
First we'll need to add SDL_ttf.h to our includes:
In order to use the library we need to initialize it in our main function after initializing SDL. Note that we check for errors similar to how we check for issues when starting SDL, except that now to get the appropriate error information we use TTF_GetError, similar to what we did in Lesson 3 to get SDL_image error information.
Next we'll want to make a function to render a message and abstract away the work that goes into it, and simply return an SDL_Texture that we can draw to the screen. In addition to rendering the message the function will also need to convert the SDL_Surface pointer returned from TTF_RenderText_X to a SDL_Texture pointer and return that.
The information needed to render the message is the message itself, a font file name, a font size and a font color. We can now add a nice function to render our desired message to our code:
That's quite a bit of code! Lets step through it and figure out exactly what's going on. First, we know that we need a message, font file, font color and font size so that we can properly draw our message. SDL provides the SDL_Color structure that is used for specifying what color we want and takes three values for RGB which can each range from 0-255.
The first step in drawing the text is to open our font, which we do on line 4 using TTF_OpenFont, which takes the font file and the font size to open the font as. Note that the same nullptr error checking that we've used before in LoadImage is used to make sure the font opened ok. If it fails we throw an error and include the font file name that caused the error along with TTF_GetError so that we can have enough information to diagnose the issue.
If the font loaded ok we use TTF_RenderText_Blended to write the message to an SDL_Surface and get the pointer back. There are a few methods provided by SDL_ttf for rendering text, blended is the slowest but provides a nice smooth look. The other methods can be found in the documentation linked above.
However because we use SDL_Textures in our rendering we need to convert the surface over. This isn't too difficult because we've made our SDL_Renderer a global and so it's easy to pass it into the function SDL_CreateTextureFromSurface. After creating the SDL_Texture we need to clean up things we no longer need to keep in memory, the SDL_Surface and the TTF_Font. We free the surface the same as before and can use TTF_CloseFont to close the font we opened.
Once everything is cleaned up we return the SDL_Texture pointer for usage in the program.
When we want to render some text in our program we can now perform a call like so:
Here we render the message "TTF fonts are cool!" using the font we downloaded above in white with a font size of 64.
You can then treat the texture as any other texture and draw it the same as we've done previously with our ApplySurface function. If you draw the message centered, you should see something like this when you run the program:
Try playing around with the other TTF_RenderText_X functions and see how they look!
End of Lesson 6
Thanks for joining me! I'll see you again soon in Lesson 7: Taking Advantage of Classes.Thursday, August 2, 2012
Lesson 5: Clipping Sprite Sheets
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.It's very common in sprite based games to use a large image file containing many smaller images, such as tiles for a tileset, as opposed to having a separate image file for each tile. This type of image is known as a sprite sheet, and is very useful because we don't need to have a separate image file for each image in the game but instead can just draw the subset of the sheet that we want.
In this lesson we will learn how to use a simple sprite sheet in our program and how we can specify the desired subset, known as a clip, to draw. The sprite sheet for this lesson is a simple one containing four circles:
In a sprite sheet images are divided up into sections of some sort of basic shape that we can clip. In this sheet each circle is within a 100x100 rectangle that we can pick to draw, instead of the entire image.
The code from this lesson will be building upon the code from Lesson 4, if you haven't done Lesson 4 you should head back and do it. If you already know the material covered in Lesson 4 grab the code and let's get started.
Within our ApplySurface function from Lesson 4 you'll notice that there's one last NULL value being passed to SDL_RenderCopy.
This parameter is the source rect, more commonly known as the clip, and specifies a sub-rectangle of the image to draw, ie. the clip's position, width and height. In order to pass a clip for the image we'll want to add a clip parameter to ApplySurface.
But what if we had an image where we did want to draw the whole thing? Instead of forcing ourselves to pass a clip of the whole image, we can instead specify a default parameter and detect when we get it. We'll take the clip parameter as an SDL_Rect pointer with a default value of NULL, that way if no clip is passed we can still pass the parameter to RenderCopy and the call will be the same as it was previously when we passed NULL to draw the whole image.
We'll also want to add one more thing to our function: If we pass a clip parameter we clearly would want to use the clip's width and height as our destination values instead of the whole texture's width and height. If we used the texture's width and height our clip would be stretched to match the size of the original texture.
We'll load our image the same as before with or LoadImage function.
We now need to set up our clip rects, which we'll store inside an array. Instead of typing all of our clips out by hand which is very tedious for images with lots of clips, we can take advantage of the pattern in which our image file is constructed to automate the creation of the clip rects with a for loop. If you did the extra challenge from Lesson 2 this method should look quite familiar.
First we specify the width and height each clip should be, in this case 100x100 and we create an array to store four SDL_Rects to hold our clips. Next we use our knowledge of the arrangement of our clips to create a for loop with a column counter to appropriately set the x,y coordinate for each clip.
If you didn't do the Lesson 2 challenge or are feeling a bit lost, let me explain how this works. We want to create four clips, so we set our loop to run four times, from zero to three to match the array indices. We also need to track which column we're going down so that we can correctly set the x coordinate for our clip, we'll begin with column zero and will increment the column value when we move over, which would be at the third clip, ie. i % 2 == 0 since we start counting from zero. We also want to make sure we don't increment column when we first start out, because 0 % 2 is also 0, so we put a condition to ignore that case.
Now we'll want to calculate the x and y coordinates for our clip in the image. We want to increment the x coordinate by the image width each time we move over a column, and increase the y each time we move down one in the column. The x coordinate setting should be clear, we can just use tile width * column. The y coordinate setting uses our current loop iteration mod the number of clips per column to determine which row we're on. We then multiple this row number by the tile height to get the location in pixels. The width and height are uniform for all clips so we can just set them, without any extra calculations.
Thus when we run this loop we will create four clips and each clip will take the appropriate coordinate for the sub-rectangle. Still not sure how it works? Try running the for loop in your head and calculating the column, x and y values for each iteration and see where they line up on the sprite sheet.
The last step is to set a value which can track which clip we want to draw, in this case an integer corresponding to the clip's index in the array. Let's begin by drawing clip 0.
Before beginning our main loop we'll also need to set an x and y position to draw the image at. I chose to calculate the center position for the image using the same method as in previous lessons. We'll also want to create a bool quit variable and an SDL_Event.
In order to make sure our clips were set correctly and display correctly we'll want to be able to draw each one individually, to do this we can set up some event polling to change the value of useClip so that we can draw each clip.
Here we check if the input type is a key down event, and then use a switch statement on the key symbol to pick the appropriate response.
Finally in our render section we clear the screen, pass the clip's value to our ApplySurface function in the rendering section of our loop:
And finally, present the renderer to display the changes.
When you run the program you should be able to push the number keys 1-4 and see the different colored circles appear individually on screen!
End of Lesson 5
Thanks for joining me! I'll see you again soon in Lesson 6: True Type Fonts with SDL_ttf.Monday, July 23, 2012
Lesson 4: Event Driven Programming
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.Now that we've learned how to draw images to the screen it's time to learn how to read user input. Our game won't be very good if there's no way to play it! In this lesson we will cover simple event handling using SDL and begin designing a primitive 'main loop'.
Before we begin it's important to mention that the majority of the code being used for this lesson is the code that we've built up from previous lessons, and as such the repeated code will not be shown as it is assumed you have it already and are using what you're learning here to add on to the previous lesson's code. If you don't have the code, head back to the earlier lessons, or if you already understand the material grab the code from Lesson 3 off Github and let's get started modifying it.
We'll create the renderer and window the same as before, and our LoadImage and ApplySurface functions remain unchanged. The first change to our program is the loading of a different image, note that you will need to change the filepath to match the location of the image relative to the executable on your system, or the program will fail to run.
We also use the same equation as before for centering our image in the window. For this lesson our image will be:
Now before we dive into writing our main loop and event handling code, we'll want to get an understanding of how the SDL event system works.
An event is any interaction with the program done by the user: keypresses, mouse motion, mouse presses, joystick movement, window resizing, minimizing or closing, etc. SDL provides a structure for storing these events called SDL_Event. Let's create one now.
Whenever an event occurs it's added to the queue in chronological order. If we want to read an event from this queue we can pull the oldest event using SDL_PollEvent. This may be a bit confusing, hopefully this handy graphic will help make it a bit clearer.
So if each frame of our game we want to process all events in the queue we'll want to loop through until there's nothing left to read, then continue on and do our other stuff like logic and rendering. This is starting to sound like something we could call our main loop!
In each iteration of the loop we'll want to process our event queue, do some program logic and draw the frame and then do it all over again until the user quits.
Next we'll want to do our event polling. Our goal was to run over all the events in the queue each frame, so let's use another while loop, as PollEvent will return 1, 'true', if there are any pending events, so our loop will run until there are no more events in the queue.
First we get an event from the queue with SDL_PollEvent, which we pass an address too as its signature is SDL_PollEvent(SDL_Event*) and then we do whatever we want to with the input. In this case we're just going to quit the program if the user pushes any key, clicks the mouse or closes the window, so if just about anything happens we set quit to true. Note that clicking the X on the window is read as SDL_QUIT.
SDL's event structure is able to read many different types of events and provides all the valid information tied to each event that may be needed in deciding what to do with the input. For more information on the various event types and the data that comes with them, check out the SDL_Event documentation.
The next step in our loop would be to do various calculations that our program needs to do, such as determining movement, collision outcomes and etc. However since our program is very primitive we don't need to do any logic at the moment, so we'll continue on to rendering.
The code is identical to before, but we must be sure each time to clear the screen before drawing our new scene or we could have images left over from previous frames start showing up.
You'll want to wrap up the program by destroying the texture, renderer and window and quit SDL.
When you run the program you should be able to quit by clicking the X on the window, clicking in the window or pressing a key while the window is selected. You may also notice something a bit interesting when running the program if you check its CPU usage. If you've seen tutorials where the main loop is discussed or have written SDL1.2 code you'd expect that our program for this lesson would max out one of your CPU cores because there's no framerate limiting being done. This is no longer the case with our program as we have created our renderer with the SDL_PRESENTVSYNC flag which tells the renderer to delay and match the framerate of our monitor, letting SDL take care of the framerate limiting for us, and letting us skip writing a line or two of code.
Lesson 4 Extra Challenge!
Try getting the image to move on the screen.
Hint
What if you incremented the image's x and y values each frame in the logic section?
End of Lesson 4: Event Driven Programming
Thanks for joining me! I'll see you again soon in Lesson 5: Clipping Sprite SheetsThursday, July 19, 2012
Lesson 3: SDL Extension Libraries
Deprecation Notice:
I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.You may have been wondering throughout the previous tutorials if it is possible to load images that aren't BMPs. Although SDL does not provide functionality for loading non-BMP images itself, there exists a large amount of robust and powerful libraries that extend SDL's functionalities to include loading many image formats via SDL_image, rendering true-type fonts with SDL_ttf, support for more audio formats with SDL_mixer and even networking from SDL_net.
By using these libraries we can add much more functionality to our programs and do many things much easier. For now we will only be using SDL_image, but will cover the other extensions in later lessons.
In Lesson 0, when you cloned from the Mercurial and built the libraries you should have also downloaded and built the source for SDL_image, SDL_ttf and SDL_mixer, although I was unable to get SDL_mixer to compile with Visual Studio at the time of writing. This part of the tutorial will be updated when SDL 2.0 is officially released to simply contain links to download the libraries.
If you have not done this, head over to the SDL Mercurial and clone and build the extension libraries available there.
To include the headers and library files on Windows simply paste them in the include and lib directories of your SDL 2.0 folder and add SDL_image.lib and etc. to your linker settings. On Linux following the configure, make, make install pattern will spit out a location where the libraries were installed that you can link against.
In addition to setting up the includes and libraries you will also need to put the dlls that are located in the externals folder along with the dll that was created when building the project. Some dlls are dynamically loaded and as such, only needed if you intend to use their functionality, you may consult the extension library page for the specifics on which dlls behave this way.
In addition to exploring the SDL_image library in this lesson we will also begin to learn about throwing and catching exceptions by adding some error handling to our LoadImage function from last time, instead of checking if the returned pointer was still null. This will allow us to get more information about the exact error that happened and help us track down the cause of the error faster.
For this lesson we will simply be adding some extra stuff to the code we wrote in Lesson 2, so let's get that open. IDE users will want to add SDL_image to their linker settings as described above, and for those of you compiling with g++ add -lSDL_image or -lSDL2_image depending on how your 2.0 libraries are named.
We'll need to add the extra includes for the new things we want to use, SDL_image and stdexcept.
From here we setup as before our screen parameters and create a global window and renderer. If you're feeling ill from the use of global objects you may want to take a moment.
Now let's rework our LoadImage function to use SDL_image's IMG_LoadTexture to directly load a texture from an image. SDL_image supports many image formats: BMP, GIF, JPEG, LBM, PCX, PNG, PNM, TGA, TIFF, WEBP, XCF, XPM, XV and as such is a very powerful and useful library, as we are no longer limited to the BMP format
Reworking our function is surprisingly simple, we just take out the SDL_Surface business and load the texture directly. Note that we now use IMG_GetError() instead of SDL_GetError(), this is because we're now using the SDL_image library to load the image, and as such the appropriate error information would be stored there instead of in SDL.
But we've got one more new thing here. Since we've decided to be smart and add more informative error checking if our image fails to load we will throw a runtime_error which will tell us what happened and more helpfully, which image caused the error. However if you leave this error uncaught when you call the function later in your program and it occurs, it will crash the program. Depending on the situation this may be desirable, but at the moment we'll want it to tell us what happened as opposed to just crashing.
We're almost ready to draw our images, for this lesson we'll be using this image as our background:
and our foreground image will be:
If you look at our foreground image you'll notice that it has a transparent background, I've set it like this to demonstrate that IMG_LoadTexture will respect alpha channels and that when the texture is drawn the transparent properties will be retained.
Now that we've got our images we can write our loading code, but recall that we'll need to add a try/catch structure to handle the error if one is thrown. Also, don't forget to set the file paths to match the image locations on your system.
In our catch statement we tell the program to print out e.what(), which will display the string that we put into the runtime_error's parentheses. Note that depending on how you've configured your project to build you may not get stdout displayed, if that is the case you can simply write the information to a file instead.
The rest of the code is not shown as it is identical to what was written for Lesson 2, so you can just drop in this new functionality into the old program. If you want to mess with the image positions, feel free to play around. When running the program with the same position settings as Lesson 2 you should see this:
Note that the transparency of the foreground image was preserved. This is a very useful effect as it means we won't have to use color keying to cut out the background of our images but instead can use simple transparency. However if you do wish to use color keying, it will be covered in later lessons.
End of Lesson 3: SDL Extension Libraries
If you have issues compiling or running the code make sure that you've added the include paths and linker settings correctly, along with putting the dlls from SDL_image and its dependencies, located in the externals folder of the source code, into the same folder as your executable.I'll see you again soon in Lesson 4: Event Driven Programming!
Subscribe to:
Posts (Atom)