Remember our last lesson? With putting things on the screen? Good times, good times. If you’re an astute observer, you noticed that to actually put the image on the screen took some strange commands – blit() in Allegro, and spriteBatch.Draw() in XNA. They each took a number of pieces of information to do exactly what we wanted them to. Wouldn’t it be nice for me, and for you, if we had a way to use the same command in both languages to do the same thing? Fortunately, we do – we’ll wrap them up in a Function.
Functions (or Methods, as your language may call them) are like little black boxes that take some input, and do something to that input (or with that input), and give you back some sort of output. A function looks something like this:
return-type FunctionName(input-type input1, input-type input2...) { // do something with the input here return (something of return-type); }
Not to sidetrack too much, but remember when we discussed types of variables,like int and bool? There’s another relatively important one we’ll introduce now, called void. A void type can’t be directly declared, because it is… nothing. There are some strange uses for void types that we may touch on later, but for right now, it’s important if we have a function that DOES something, but RETURNS nothing.
So back to the matter at hand… we were discussing how convenient it would be to make the same call in both languages, and have it do the same thing? Let’s add that to our project. We’ll switch the order around today so nobody feels slighted.
- XNA
Open up your project, and in your Game1.cs file, find the Draw() function. (Hey, it’s a function too!) It’s the one that looks like this:
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); // TODO: Add your drawing code here spriteBatch.Begin(); spriteBatch.Draw(titleBitmap, titlePosition, Color.White); spriteBatch.End(); base.Draw(gameTime); }
Add a blank line, then we’ll add ourselves a DrawSprite() function:
public void DrawSprite(Texture2D spriteToDraw, int X, int Y) { Vector2 spritePosition = new Vector2( (float)X, (float)Y ); spriteBatch.Draw(spriteToDraw, spritePosition, Color.White); }
Shall we go over it line by line again?
public void DrawSprite(Texture2D spriteToDraw, int X, int Y)
Public is a keyword that lets us control where we can call this function – we’ll come back to that later, when we talk about scope and ownership. Basically, we can call DrawSprite anywhere in this program right now. Void is that “don’t-return-anything” type we talked about, since it’s not giving any value back when it’s called. Texture2D spriteToDraw means that it takes a Texture2D (the name of the bitmap type in XNA), that in this function we’re going to call “spriteToDraw”. Doesn’t matter what it’s called outside, when it’s handed off to this function, it’s spriteToDraw. Finally, int X and int Y are the position we’re going to draw it on the screen.
Vector2 spritePosition = new Vector2( (float)X, (float)Y );
XNA does a lot of things with float types – it’s a decimal, or rational, number. int types only take whole numbers. They also define a type called a Vector2, which is essentially a container that holds two floats – a point in 2d space, with an X and Y. The notation
(float)X
is used to turn X (an int by default) into a float. This line makes a coordinate XNA can use for drawing out of an X and Y position on the screen.
spriteBatch.Draw(spriteToDraw, spritePosition, Color.White);
Ah, back to something we’ve done. We draw whatever sprite we’re given and told to draw, at the X and Y position on the screen we’re given. This will always draw the WHOLE sprite, with the top left corner of X and Y. Shall we make one more change, while we’re modifying our code all willy-nilly? Go back to your Draw() function, find this line:
spriteBatch.Draw(titleBitmap, titlePosition, Color.White);
which is what we were using before to draw the title, and replace it with this:
DrawSprite(titleBitmap, titleX, titleY);
And a few more small changes near the top of the program – where we had
Vector2 titlePosition;let’s add these lines:
int titleX = 260; int titleY = 230;
Save it, make sure it still builds and runs – it should do exactly the same thing it did yesterday – but now a little more easy to understand on the inside. Now every time we need to draw a whole sprite somewhere we can just call this function!
- Allegro
Open up your project, and open main.cpp, so we’re looking at our code again. At the VERY END of everything, after the last closing brace, we’ll add our new function, like so:
void DrawSprite(BITMAP* spriteToDraw, int X, int Y) { blit(spriteToDraw, screen, 0, 0, X, Y, spriteToDraw->w, spriteToDraw->h); }
Now for the line-by-line!
void DrawSprite(BITMAP* spriteToDraw, int X, int Y)
The function is a void type, since it doesn’t return anything, just draws to the screen. It takes a BITMAP* type, which is Allegro’s shorthand for a bitmap object, and two coordinates. Allegro prefers ints for drawing on the screen, so no weird conversions are necessary here.
blit(spriteToDraw, screen, 0, 0, X, Y, spriteToDraw->w, spriteToDraw->h);
Like we discussed last time, blit() takes a bitmap object (which we’re given), a destination (the screen for now), the SOURCE bitmap X and Y coordinates (we’ll start at the top left corner which is 0, 0), the DESTINATION coordinates (which we give to the function), and a size to draw (which is the whole thing, as we discussed briefly before). In fact, it almost seems too easy – a waste to write a function that only does one line of code, doesn’t it?
There are at least three reasons (two good, one mediocre) to do it this way:
- The DRY principle: “Don’t Repeat Yourself”. Anything you have to do multiple times in a project, throw into a function. You shouldn’t see duplicate code all over the place, and this saves from having to worry about things like the size of the sprite we’re drawing (the bitmap already KNOWS its own size!) or where we’re drawing it – which leads us to…
- “Fix It Once” – Let’s say for some reason we decide we aren’t going to draw to the screen anymore. (To be honest, I already know we are going to change that sooner or later.) Instead of fixing every single blit() call in the program, we can change it HERE, and ever DrawSprite call WE make will draw to wherever we decided the right place would be.
- (This is the mediocre one) I’m lazy. For the sake of readability, and convenience, I wanted a way to make the drawing calls the same in both programming languages. Of course, it works in YOUR favor too – if you build your code in this style, and decide to switch to another programming language, you’ll have to fix these functions (the ones that call language-specific code), but the overwhelming majority of your code will just pick up and move over with no significant changes.
Enough lecturing, back to the code! Find our main game loop – you know, the one that looks like this:
while (!key[KEY_ESC]) { clear(screen); blit(titleBitmap, screen, 0, 0, x, y, titleBitmap->w, titleBitmap->h); }
Let’s change a line in there, get rid of that crummy old blit(), and use our new function, like so:
while (!key[KEY_ESC]) { clear(screen); DrawSprite(titleBitmap, x, y); }
And one final change to make. C++ encourages requires us to keep a list, of sorts, of all the functions we’ve added to a program at the top – it’s basically the first line of the function (the name and parameters) with a semicolon at the end. Look for a section that says
void init(); void deinit();
We’re going to add one more line right after those:
void DrawSprite(BITMAP* spriteToDraw, int X, int Y);
That way the compiler knows we have a DrawSprite function, since we technically “call” it before we tell the compiler what it does. Save it, hit the big compile button, and gaze upon its (strikingly similar to last time!) majesty!
Today we’ve written our first function (of many, many, many more to come). We’ve also brought our two opposing factions of language a little closer together. Granted, there wasn’t much notable change in WHAT our program does, but we made some important changes to HOW it is done. Until next time, Save early, Save often!