In the previous post I have set up a raw architecture for the game. There is a Game class that has a function called execute, which allows basic stuff to happen:
– responding to events
– updating state
– and rendering state
In code it is:
while (running) { handleEvents(); update(); render(); }
The end result of the first phase was, well, a black window. Nothing to be excited about just yet…
One of the very basic needs is drawing on the screen. One way to get something on the screen is by using something called blitting. Blitting is basically taking a picture in memory (called in SDL a surface, or in Allegro a BITMAP) and then copy it onto another surface which functions as the screen.
We already have this screen SDL Surface defined in our Game class. It is not the actual (hardware) screen buffer. But, by doing SDL_Flip we can make this screen visible.
In essense this means the render function will be like this:
void Game::render() { // draw some stuff on the screen // flip screen at the end SDL_Flip(screen); }
I want to be able to draw surfaces on the screen. SDL requires the usage of SDL_Rect‘s which allows you to draw pieces of surfaces. At this point I don’t want deal with these SDL_Rect’s directly. What i want is:
draw (source surface, dest surface, int x, int y);
This is where I introduce a new class (again Single Responsibility Principle) that allows drawing on the screen and providing easy methods to do so. I call this the surfaceDrawer. At this point in time, its class definition looks like this:
#ifndef SURFACEDRAWER_H #define SURFACEDRAWER_H #include <SDL/SDL.h> class SurfaceDrawer { public: void draw(SDL_Surface * from, SDL_Surface * dest, int x, int y); }; #endif
The implementation looks like this:
#include "surfacedrawer.h" void SurfaceDrawer::draw(SDL_Surface * from, SDL_Surface * dest, int x, int y) { if(from == NULL || dest == NULL) { return; } SDL_Rect rect; rect.x = x; rect.y = y; SDL_BlitSurface(from, NULL, dest, &rect); }
This implementation is easy; the SDL_BlitSurface accepts:
– a from surface
– a from rectangle (ie, what to copy from the from surface?, NULL = everything)
– a destination surface
– a position given by a rect. (where to draw this? Starting with upperleft corner of from surface)
In this case, we provide NULL as 2nd argument, saying we want to copy the entire from surface. Then, the last parameter is the position where to draw it in the form of an SDL_Rect.
Next step is to actually use this function. One thing that we always need in an RTS is to draw the mouse. What we need is an SDL_Surface with a mouse bitmap loaded. I will be using:
Loading this image is done by using SDL_LoadBMP . We need to make sure that the surface we have loaded is in the same bit/color format as our screen. We can do that by using SDL_DisplayFormat. We don’t want to be bothered with this everytime, so we have to make some class responsible for loading resources into SDL_Surface’s which are suitable for drawing by the surfaceDrawer. For that I have introduced a surfaceDao class, which has the following class definition:
#ifndef SURFACEREPO #define SURFACEREPO // Data Access Object for fetching SDL Surfaces #include <SDL/SDL.h> class SurfaceDao { public: SDL_Surface * load(char * file); }; #endif
SDL supports BMP formats out of the box. We need to use the SDL_Image library to get support for other formats. Using a DAO we can move all this format specific stuff out of our game into this single class later. For now I will be using BMP, as the main focus is drawing surfaces.
The implementation of the surfaceDao looks like this:
#include "surfacedao.h" #include <iostream> using namespace std; SDL_Surface * SurfaceDao::load(char * file) { SDL_Surface * temp = NULL; SDL_Surface * result = NULL; if((temp = SDL_LoadBMP(file)) == NULL) { cout << "Failed to load [" << file << "]." << endl; return NULL; } result = SDL_DisplayFormat(temp); SDL_FreeSurface(temp); return result; }
We load the BMP, when that is succesful, we convert it to the current display format. We have to free the temp surface using SDL_FreeSurface, to prevent memory leaks.
Now, in the Game class, this all comes together, first the class definition is expanded and gets (at the private section) the following code:
#ifndef GAME_H #define GAME_H #include <SDL/SDL.h> #include "surfacedao.h" #include "surfacedrawer.h" class Game { .. snip .. private: .. snip .. SDL_Surface * mouse; // Dependencies SurfaceDao surfaceDao; SurfaceDrawer surfaceDrawer; }; #endif
As you can see, I already have prepared the SDL_Surface for the mouse. Now in the Game implementation, loading the mouse is using the surfaceDao:
int Game::init() { ... snip ... // load resources mouse = surfaceDao.load("resources/images/MS_Normal.bmp"); return 0; }
In the render function I use the surfaceDrawer to draw the mouse at the current X and Y position of the mouse:
void Game::render() { int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); surfaceDrawer.draw(mouse, screen, mouseX, mouseY); // flip screen at the end SDL_Flip(screen); }
This all finally results into:
Finally, we have something to draw! yay! But we need to make this a little bit better first:
– remove the system cursor (you don’t see this on the picture, but when running this your system cursor is on top)
– make sure we don’t have a trail of the mouse, ie, clean the screen before drawing
– we also see the purple background of the mouse, we don’t want to draw that…
Lets fix these things:
Hide system cursor
int Game::init() { ... snip ... SDL_ShowCursor(0); // load resources ... snip ... }
Clean screen before drawing
Add function to the surfaceDrawer
class SurfaceDrawer { public: ... snip ... void clearToColor(SDL_Surface * target, Uint32 color);
And implement this:
void SurfaceDrawer::clearToColor(SDL_Surface * target, Uint32 color) { if (target == NULL) return; SDL_FillRect (target, NULL, color); }
Don’t draw the purple background
What we want is to skip a certain color when blitting a surface to another surface. We can do this by specifying a color key. This color will be skipped with drawing. The nice thing about the surfaceDrawer is that all have to do is add a function there to do this and change the call in Game to use the new function:
Add function to the surfaceDrawer
class SurfaceDrawer { public: ... snip ... void drawTransparant(SDL_Surface * from, SDL_Surface * to, int x, int y);
One thing to note, in D2TM the assumption is that all colors with RGB: 255,0,255 (purple) will be skipped. That is the reason the surfaceDrawer does not have a color parameter. We could add this if it is needed later on.
Implementation:
void SurfaceDrawer::drawTransparant(SDL_Surface * from, SDL_Surface * dest, int x, int y) { Uint32 colorkey = SDL_MapRGB(from->format, 255, 0, 255); SDL_SetColorKey(from, SDL_SRCCOLORKEY, colorkey); draw(from, dest, x, y); SDL_SetColorKey(from, 0, 0); }
The first line actually creates the purple color. The code is not that selfdescribing. Later on I will introduce a Colors class that basically says “give me purple”, and does the SDL_MapRGB in its implementation.
Of course, now in the Game we have to make a few adjustments.
void Game::render() { surfaceDrawer.clearToColor(screen, Colors::black(screen)); int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); surfaceDrawer.drawTransparant(mouse, screen, mouseX, mouseY); // flip screen at the end SDL_Flip(screen); }
Two lines are added, first clear the screen to a specific color (black), and then for drawing the mouse we use the new drawTransparant function.
And after compiling, it looks like this:
Next blog post
Although we are able to draw stuff on the screen, it is far from efficient. If we want to draw a terrain we have up to 16 different surfaces for one terrain type (lets say, rocks). It is not doable to hold 16 different SDL_Surface’s and then draw the correct one. Besides, we also have spice, mountains, sand, hills, spicehills. So we need to do something clever. Thats where a tileset comes in. The next blog will be about that.
A side note about including SDL in every header file
One thing you’ll notice about my class definitions, is that I seem to include SDL all the time. Tutorials will say you’ll provide this at the top of your project in your main class (in this case it has to be main.cpp). Although I understand why (since the preprocessor will put the SDL code there, and will be accessible to all other files), it violates the SRP. In fact, you cannot compile a single CPP class anymore using SDL, since you do not refer to this.
As you can see in all my header files, there is a piece:
#ifndef SOMETHING #define SOMETTHING // here is code #endif
This basically says “whenever I have not yet defined SOMETHING include the piece of code, and define SOMETHING”. This allows us to include the same files over and over again, but we are sure the preprocessor only includes it once. This is needed because else your program will not compile due multiple definitions of the same class.
So now:
– I can compile any CPP file seperately (and, the big plus: I can use that for testing later on! :))
– I know what all my dependencies are, they are not somewhere else hidden
End of side-note