Introduction
The aim of these tutorials is, initially, to show you how to create a simple game, from scratch, without the help of higher level APIs like XNA or DirectX which automate half the process for you. All we will be using is a Windows Form and GDI+ to do some basic drawing, and for a little extra convenience, a few of the Form's events.
In this tutorial, we will cover double buffering to remove any flickering, setting up a simple FPS counter, getting input from the player, and drawing sprites to the screen.
Step 2: Drawing A Scene
Reducing The Flicker
If we had been using a separate thread to run the logic and draw to the screen, we may have seen that the screen flickers as new items are drawn to it. You may even have seen your applications flicker if there are a lot of controls on it. A solution to this problem is to use Double Buffering.
We already have one buffer which is the screen. This buffer is used by the graphics card to update your display. If we draw directly to this buffer, then we can see any change almost immediately, which results in flickering. With only small amounts of drawing like we are doing at the moment, you may not notice any flickering, but as the amount of drawing being done increases, the effects become much more obvious.
With double buffering, we have two buffers, a back buffer and the screen. We do all our drawing on the back buffer, and then draw the back buffer to the screen in one go. This removes the flickering problem. A variation of double buffering is Page Flipping. In page flipping, the graphics card does the extra work. It will have two buffers in its VRAM. One of them will be displayed on the screen while we draw to the other. It will then swap the buffers. So, the card displays buffer1 while we draw to buffer2. It will then swap and display buffer2 while we draw to buffer1.
The main difference between the two is that page flipping will always wait for the card's VSync (so it waits until the vertical refresh before drawing the new buffer). This will limit the frame rate to your monitor's refresh rate - while this is not a bad thing, waiting for the VSync may still slow your game down. The advantage of waiting for the vertical refresh though, is that you don't get shearing. Shearing is when we copy the new buffer during a refresh, so the top half of the screen uses the old scene, and the bottom half gets the new scene.
So, after implementing double buffering, our code will look a little like this:
Image buffer;
GetInput();
PerformLogic();
DrawGraphics();
...
DrawGraphics()
{
Graphics g = Graphics.FromImage(buffer);
g.Draw...
g.Dispose();
}
It's Still About Timing - FPS Counter
How about that simple FPS counter then? All we need to do for this is to count how many times our drawing code is run every second. So, we will need a variable to count up how many frames we draw, and another timer that will run once every second. If we go back to the code we had at the end of the previous article and add these in, we will get this:
Timer MainTimer;
Timer FpsTimer;
MainTimer.Interaval = 1000/60;
FpsTimer.Interval = 1000;
bool runGame = true;
volatile uint speedCounter = 0;
uint fpsCounter = 0;
uint fps = 0;
Main()
{
while(runGame)
{
if(speedCounter >0)
{
GetInput();
PerformLogic();
speedCounter--;
if(speedCounter == 0)
DrawGraphics();
}
}
}
DrawGraphics()
{
...
fpsCounter++;
}
FpsTimer()
{
fps = fpsCounter;
fpsCounter = 0;
}
Timer()
{
speedCounter++;
}
See, it's quite simple really.
Uh oh
If you were to add in the FPS counter now, you would probably see that your actual FPS is lower than what you tried to set it to. This is because the standard timers that .NET supplies are only accurate to about 1/18th of a second. Because of this, we will have to use a timer from the olden days, which is much more accurate. This timer is included with this article's demo, so there's no need to worry about it.
Press Those Buttons - User Input
We need to get input from the player. For this, we will be using the KeyDown
and KeyUp
events of our Windows Form. Seems quite simple, but remember, we should only run logic from the main loop and at the right time. You can't just perform logic as soon as a key is pressed. As a really obvious example, if your game ran at 2FPS and the player moves at a speed of 4px per frame, then the player should move 8px a second, right? But, if we move the player as soon as the button is pressed, then the player can move as fast as they can press the button. Also, using the events alone, we don't know if a button is still being held.
So, we need a way to store which buttons have been pressed so that we can check them in our loop when we run our logic. You could use an array of bool
s to keep track of the buttons, but it's much easier to use flags. What I mean by flags, is that we have an int
or a long
, and each bit in these variables represents a key. So, in an int
, we can keep track of 32 keys, and in a long
, we can keep track of 64. If we use an int
, the first bit may represent 'Left' and the second 'Right', the third 'Up', and so on. This int
would show that Left and Up are pressed:
00000000000000000000000000000101
So, we can see from the binary representation of our int
, that two buttons are pressed. The actual value of the int
would be 5. We will use an enumeration to keep track of our game keys, and use bitwise operators to add and remove keys. The enumeration and implementation will look like this:
[Flags]
public enum GameKeys : int
{
Null = 0,
Up = 0x01,
Down = 0x02,
Left = 0x04,
Right = 0x08
}
...
GameKeys pressedKeys = GameKeys.Null;
...
KeyDown(KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Left:
pressedKeys |= GameKeys.Left;
break;
...
}
}
KeyUp(KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Left:
pressedKeys &= ~GameKeys.Left;
break;
...
}
}
We can then check if a key is being pressed, by using:
if((pressedKeys&GameKeys.Left) == GameKeys.Left)
This statement would check if the left arrow key was being pressed. To add a key, we use the OR
operator, that means that we combine the two values together - this is not quite the same as addition.
0010
OR 1001
= 1011;
When we check if a key is pressed, we use the AND
operator, which means that the result only contains a 1 where both of the bits are 1.
0100 (GameKeys.Key)
AND 1111 (pressedKeys)
= 0100
So, if the result of our AND
operation is the same as the key we are checking for, then the button is being pressed.
There is at least one exception to performing logic in the events themselves - closing the game. In the demo, you will see that I have set Escape to change m_playing
to false
so that our thread will close, and then I call Application.Exit()
to close the form. It is important to make sure we exit our thread before closing the form, or we will likely get an error, which won't look very good.
What We're All Here For - Animating A Sprite
Now, we're going to get on with drawing and animating a sprite.
What Is A Sprite?
In a 2D game, a sprite is basically just an image which represents an object in the game. So, to draw a sprite with no animation, you simply draw an image or part of an image to the screen.
If we wanted to draw a space invaders alien to the screen, we would load the image from the hard disk, and then use Graphics.DrawImage(...);
to draw it to our buffer. So, if we where using this image (and we are):
then, we would need to find the X and Y coordinates in the image for the alien, and its width and height. I can tell you that the first alien is at (48, 89) and is 16*16 in size. To draw it to the screen, we would use this overload of the DrawImage
method: DrawImage(Image, destination rectangle, srcX, srcY, srcWidth, srcHeight, GraphicsUnit)
.
Bitmap spaceInvaders = new Bitmap("path to image");
...
DrawGraphics()
{
Graphics g = Graphics.FromImage(buffer);
g.DrawImage(spaceInvaders, new Rectangle(50,50,16,16),
48, 89, 16, 16, GraphicsUnit.Pixel);
}
That would draw the little alien at (50,50) on our screen. And, of course, if you wanted to stretch or shrink the sprite, just change the width and height of the rectangle.
That's Nice, But It Doesn't Do Much
To animate it, we simply draw the image, and then on the next frame, we draw the next image in the sequence. So, we need to keep track of what frame of the animation we're on, how many frames are in the animation (so we know when to go back to the start), and the width and height of an individual sprite. To animate our alien here, we draw the image at (48, 89), and then on the next frame, draw the image at (48, 105), and repeat.
int numberOfFrames = 2;
int currentFrame = 0;
int height = 16;
Logic()
{
currentFrame++;
if(currentFrame == numberOfFrames)
currentFrame = 0;
}
DrawGraphics()
{
Graphics g = Graphics.FromImage(buffer);
int srcY = 89 + (currentFrame*height);
g.DrawImage(spaceInvaders, new Rectangle(50,50,16,16),
48, srcY, 16, 16, GraphicsUnit.Pixel);
}
The code in Logic()
will increment currentFrame
, and set it back to 0 when it reaches the number of frames in the animation. currentFrame*height
will give us the difference in Y-Position from the first frame. On the first frame, currentFrame
will be 0; 0*16 is 0, so srcY
stays at 89. On the second frame, currentFrame
is 1; 1*16 is 16, so we add this to 89 to give us the new Y-Position in the image.
We're Still Missing Something
You may have noticed in games when you press different directions, that the character usually faces those directions. Implementing this is very similar to animating a sprite. If you had this image:
then, to change the direction that the character faces, instead of changing the X-Position of the sprite to animate it, you would change the Y-Position so that you loop through a different row in the image.
Updating Our Source Code
Well, now that we have gone over animation, it's probably best if we update the source code to use the more accurate timer, and a separate thread to use the proper loops for our logic. In the demo, you will now see that there is a method GameLoop
that is launched in a separate thread and contains the loops you saw in the previous article. You may also notice that we use Thread.Sleep(1);
this is so the game doesn't use up 100% of the processor, waiting for our timer to signal us to run the logic. We are also no longer using the OnPaint
override and Invalidate
methods to draw our scene, we will be doing everything ourselves.
The Demo
This articles demo includes user input that you can use to move our little alien around the screen, a sprite class to animate and render the alien, as well as a new Timer so that we can set the fps properly. It's also got the FPS counter, but it averages out the fps over two seconds, and uses a float
for decimals instead of an int
.
More Homework, Aww
Load and animate the man using the sprite class or otherwise (each frame is 32*32), then try editing the sprite class so that you can change the row or column that it is looping through, so that you can change the direction our man is facing. Hint: The class has the member variables srcX
and srcY
, you will need to write a new method to update these values.
Coming Up
And that's the end of another article, look out for the next article where we'll be covering:
- Collision Detection
- Tiling, Backgrounds, and Parallax Scrolling
- Setting Up Some Basic Player/Enemy Classes
What Do You Want?
Feel free to post about what you'd like to see in these articles. I am here trying to help, so if I know how to do what you are asking, I'll try and include it. These articles are heading through the world of 2D games, and up into the realms of 3D - though we might have to hop over to C++ and OpenGL for that one, I've never much liked DirectX, and I've never used XNA for 3D. Remember, I'm not trying to give you code to make a game, but showing you how to construct code that can be used to make a game - if you follow, the language shouldn't be so important.
History
- 9th May 2008 - Article submitted.