Wednesday, July 18, 2012

Lesson 2: Don't Put Everything in Main

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 begin organizing our code from the previous lesson by writing some very useful functions, along with discussing how images are positioned and scaled in the SDL window.

As always we'll need to start our program off by including SDL. We'll also need the string class in this lesson so we include that as well.
We'll also declare some constant values for our screen width and height along with global declarations of a window and renderer so that they're accessible by our functions. Again we initialize the pointers as nullptr for safety. If you're not using C++11 initialize them as NULL.

Note: You should avoid using non-constant global values or global values in general as much as possible, ie. you should never ever declare a global SDL_Window or SDL_Renderer. However, for this simple lesson we'll let it slide. But it's ok if you feel grossed out. We'll cover a solution to global objects in a few lessons.

Remember from Lesson 1 where we loaded a texture? It wasn't so bad to just have it in main for loading one image, but what if we had lots images to load? We'd have to type it out every time! We can do much better, instead let's define a function for loading textures from a filename:
The function here should look very familiar as it's the exact same code we wrote in Lesson 1, but now we have wrapped it up in a nice function. With this function we can pass a file name as a string and get back a pointer to the loaded SDL_Texture. Note that the pointer will be nullptr if the loading fails because we initialize both pointers as nullptr for error checking.

Next we'll want to write a function to simplify our draw calls and also allow us to specify a position to draw the image too on the screen. We'll want it to be able to take an x, y coordinate position along with a texture pointer and a renderer pointer and then draw the texture to that position.
In order to specify a position for the texture to be drawn too we need to create a SDL_Rect that we can pass the address to SDL_RenderCopy's destination rect parameter. This is done because the last two parameters of SDL_RenderCopy are SDL_Rect pointers, so it's expecting an address.

To create our rectangle we take the x and y values that we passed in and the set rectangle's values equal to them. However we must also specify the width and height we want the texture to be drawn with as SDL 2.0 also gives us the ability to scale our textures. Try playing with the width and height values once the tutorial is done and see what happens!

For now we just want to pass the texture's width and height so that we draw it at a 1:1 scale. We can get these values by using SDL_QueryTexture. This function takes the texture pointer we want to query, the two parameters we pass NULL to are the format and access level parameters respectively, which we can ignore. Finally we must pass the addresses of the variables that we want to fill with the width and height of the texture.

Now that we've got our SDL_Rect set up we can pass it, along with the renderer and texture as before, to SDL_RenderCopy so that our texture will be drawn at the point specified and with its original width and height. The remaining NULL parameter in this function is for taking a clip of the source texture, which we'll cover later.

Let's see our functions in action. First we've got to start up SDL and create our window and renderer as before. We've also got something new here, SDL_WINDOWPOS_CENTERED. This is an option we can use when creating our window to tell SDL to center its position on the specified axis, here we do it for x and y.
Now let's load up our images. For this lesson we'll be drawing a tiled background image and drawing a centered image on top of it. Here's our background:

And this will be our foreground image:

Let's load them up with the LoadImage function we just wrote.
Note that you will have to change the file paths to match the relative location of the image to the executable on your system.

Before we draw the images we'll need to know where we want to position them, specifically how we'll tile our background and also how we can draw the foreground image centered on the screen. First we'll have to understand how SDL's coordinate system works, as it is a bit different than the standard 2D Cartesian coordinate system. SDL's coordinate system looks like this:
With 0, 0 being in the top-left corner of the screen. Y values increase as we move down the screen and X values increase as we move to the right on the screen. Another thing to note about SDL's coordinate system is that the x, y point specified to draw an image at is used as the location to draw the top-left corner of the image, as opposed to the center of the image like some other libraries.

Before we draw: A note about SDL's drawing order: The order in which things are drawn will be the order in which they sit on top of each other, so the first thing drawn is the bottom-most image, and the last image drawn will sit on top of everything.

If you've taken a peek at the background image yet you'll have noticed that it has a width of 320 and a height of 240, with a screen that is 640x280 we'll need to draw the background image four times to tile it over the window scooting it over by its width or height each time.

Before drawing anything, we'll want to clear the screen, next we'll want to setup the positioning of elements on the window. We can get the width and height of the image again by using the QueryTexture function on the background, this gives us a more versatile method in which we could instead create a for loop to iterate over the positions based on the image width and height, perhaps if we were placing many small tiles. For this case we're only drawing it four times, so we can take the 'dumb' route and just type it out.
Now we want to draw our foreground image on top of the background, and centered in the window. We can calculate the center point quite easily but because the point we pass to ApplySurface is the location of the top-left corner of the image we must also apply an offset to the point relative to the image's width for x and height for y to put the real center of the image at the center point of the screen.
In order to see our drawing we'll have to present the renderer and then have SDL wait for a second or two so we have a chance to see the image.
Finally, we wrap up the program by freeing the memory being used by the textures, renderer and window, quit SDL and return.
When you compile and run the program your window should look like this:


Lesson 2 Extra Challenge!

Find a way to convert our dumb method of tiling the background into a lean mean for loop! While it may not be so effecient for four background tiles, using a for loop for tiling is a very nice method for placing a large amount of tiles.
Hint


End of Lesson 2: Don't Put Everything in Main

If you have trouble compiling or running the program make sure you've set up the includes, include directories, linker settings and linker directories correctly, along with setting the correct path to the images and placing the SDL.dll in your executable folder. For Linux users there is no SDL.dll but instead make sure you have the runtime libraries in the correct place in your system.

I'll see you again soon in Lesson 3: SDL Extension Libraries!

10 comments:

  1. Hi!
    I'm having a problem with the paths of the images, they didn't load so I have my program exiting on the 4th return (where it checks if there is a not null pointer to the image)

    What's the correct way to pass the path to the function? i've tried writting the full file path (C:/blhablha/image.bmp) and it still didn't works...

    Sorry for the grammar if there are mistakes, english is not my first lenguaje :)

    ReplyDelete
  2. Fixed! hahaha it was just a mistake writing the file name >.<

    ReplyDelete
  3. So, the Renderer is actually "the thing" (lack of naming expertise) that draws everything from itself to the screen? Like storing in a queue drawing commands and performing them in the order they were sent to it?
    It's a bit different from flipping the screen like in SDL1.2 but it makes more sense.

    ReplyDelete
    Replies
    1. Yea I guess that's a way to think about it. It acts as a wrapper around the OpenGL rendering context being used. I'm not sure if it actually queues the commands, or just draws them to the framebuffer immediately, you could ask around at the SDL forums if you were curious to find out exactly how it works.

      Delete
    2. I would hope that the renderers would correspond to GPU backbuffers, especially when using SDL_RENDERER_ACCELERATED. This is probably why an SDL_Renderer is bound to an SDL_Window?

      Delete
    3. Yea I'd think so, you can check the source for exactly how it's used, I didn't see much in the documentation.

      Delete
  4. SDL_RenderPresent(renderer); takes about 25 ms which equates to about 40 fps. This seems slow? Is this because SDL 2.0 is in development? Or does it have to do with the options that are configured? How do you know if it takes advantage of the video card?

    ReplyDelete
    Replies
    1. Interesting, I've never profiled SDL_RenderPresent. The SDL_Renderer/Texture method of drawing is hardware accelerated, I'm not sure how it's written though, it could be because it's in development. The folks at the #sdl IRC channel could probably answer why if you're curious.

      Delete
  5. Damn Visual Studio for using Ctrl+F5 to run, which happens to be force-reload on Firefox!

    I rambled on about a few things, but here are the main ones I was going to say:
    - For LoadImage() it would be good style to make the argument be "const std::string& file", which avoids a copy while still preventing modification of the source.
    - From what I can tell, it's still okay to check pointer (in)validity via "if (somePtr)" and "if (!somePtr)", because the C++11 standard mandates that nullptr be castable to bool. Some people prefer not to use this style, but I find it to be an intuitive shortcut.

    ReplyDelete
    Replies
    1. Oh whoops, yea that should be const std::string &. Whichever style you want to use is up to you, I lean to making it more explicit.

      Delete