Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / Windows Forms
Article

Game Programming - Two

Rate me:
Please Sign up or sign in to vote.
4.90/5 (49 votes)
9 May 2008CPOL10 min read 97.3K   1.6K   104   29
Introduction to the methods used to create a simple game.

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.

« PreviousNext »

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:

C#
Image buffer;

GetInput();
PerformLogic();
DrawGraphics();

...

DrawGraphics()
{
    Graphics g = Graphics.FromImage(buffer);
    g.Draw...
    g.Dispose();
    //We will then draw 'buffer' to the screen
}

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:

C#
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 bools 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:

C#
[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:

C#
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.

C#
   0010
OR 1001
=  1011;
//We basically just gather all of the 1's together

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.

C#
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):

SI256.png

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).

C#
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.

C#
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:

sprite4.png

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
England England
*blip*

Comments and Discussions

 
GeneralMy vote of 5 Pin
Reza Ahmadi17-May-12 0:55
Reza Ahmadi17-May-12 0:55 
GeneralCREATIVITY Pin
vk123777-Jul-09 7:36
vk123777-Jul-09 7:36 
GeneralWhy not draw in OnPaint Pin
Ramin Tarhande4-Mar-09 1:03
Ramin Tarhande4-Mar-09 1:03 
QuestionNeed help in animating cartoon Pin
umeed.e.sahar8-Sep-08 1:10
umeed.e.sahar8-Sep-08 1:10 
QuestionHow come? Pin
Megidolaon20-Aug-08 3:59
Megidolaon20-Aug-08 3:59 
AnswerRe: How come? Pin
Anthony Mushrow20-Aug-08 4:48
professionalAnthony Mushrow20-Aug-08 4:48 
GeneralRe: How come? Pin
Megidolaon24-Aug-08 21:02
Megidolaon24-Aug-08 21:02 
QuestionSo when can we see the next article? Pin
RossIRSA8-Jul-08 4:02
RossIRSA8-Jul-08 4:02 
AnswerRe: So when can we see the next article? Pin
abzy99925-Jul-08 4:59
abzy99925-Jul-08 4:59 
Generalnot debugs Pin
MicaelG14-Jun-08 3:18
MicaelG14-Jun-08 3:18 
GeneralRe: not debugs Pin
Anthony Mushrow14-Jun-08 3:58
professionalAnthony Mushrow14-Jun-08 3:58 
GeneralRe: not debugs Pin
Devinmccloud7-Jul-08 18:11
Devinmccloud7-Jul-08 18:11 
Question[Message Deleted] Pin
R_L_H6-Jun-08 11:23
R_L_H6-Jun-08 11:23 
AnswerRe: Help (Possibly)-- GDI seems a little slow? Pin
Anthony Mushrow6-Jun-08 14:13
professionalAnthony Mushrow6-Jun-08 14:13 
GeneralRe: Help (Possibly)-- GDI seems a little slow? Pin
Anthony Mushrow6-Jun-08 14:20
professionalAnthony Mushrow6-Jun-08 14:20 
QuestionWhat's the purpose of Thread.Sleep(1)? Pin
Liu Junfeng18-May-08 20:58
Liu Junfeng18-May-08 20:58 
AnswerRe: What's the purpose of Thread.Sleep(1)? Pin
Anthony Mushrow18-May-08 23:07
professionalAnthony Mushrow18-May-08 23:07 
Questioncan my friend play too? Pin
william h johnson iii17-May-08 9:47
william h johnson iii17-May-08 9:47 
GeneralEfficiency Pin
Ri Qen-Sin13-May-08 15:08
Ri Qen-Sin13-May-08 15:08 
GeneralRe: Efficiency Pin
Anthony Mushrow13-May-08 15:18
professionalAnthony Mushrow13-May-08 15:18 
GeneralErrata Pin
brianvp13-May-08 10:50
brianvp13-May-08 10:50 
GeneralOops! Pin
Anthony Mushrow13-May-08 12:40
professionalAnthony Mushrow13-May-08 12:40 
GeneralVery helpful Pin
AussieScribe13-May-08 2:35
AussieScribe13-May-08 2:35 
GeneralCool Pin
MarkB77712-May-08 20:25
MarkB77712-May-08 20:25 
GeneralGreat Job! Pin
Steven Bralovich10-May-08 3:38
Steven Bralovich10-May-08 3:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.