To get started you will want to create a new Windows forms application. You can call it whatever you would like, I called mine CSharpGameProgramming. If you call yours something different you will have to adjust the namespace for the code.
I receive a comment on the first one on why I was using DLLImport to get the TickCount when I could have used Environment.TickCount. The reason was I was going to update this to use a timer with better accuracy. I was going to use DLLImport to import the high resolution timer. Instead I will use the Stopwatch class for the timer. I again will be making a class for the timer. To your project add a class called HiResTimer. I always like to give code to be read and then explain why I did what I did. This is the code for class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace CSharpGameProgramming
{
class HiResTimer
{
Stopwatch stopwatch;
public HiResTimer()
{
stopwatch = new Stopwatch();
stopwatch.Reset();
}
public long ElapsedMilliseconds
{
get { return stopwatch.ElapsedMilliseconds; }
}
public void Start()
{
if (!stopwatch.IsRunning)
{
stopwatch.Reset();
stopwatch.Start();
}
}
public void Stop()
{
stopwatch.Stop();
}
}
}
The Stopwatch class is in the System.Diagonstics namespace so there is a using statement for that namespace. The only field in this class is an instance of the Stopwatch class called stopwatch. In the constructor I create a new instance of the Stopwatch class and call the Reset method of the Stopwatch class. There is a get only property of type long which returns the number of milliseconds that have elapsed since the timer was last reset. The first method is the Start method. What that method does is check to make sure that the stopwatch is not running. If it is already running resetting it and starting it over might not be a good idea. If it is not running I call the Reset method of the Stopwatch class and then the Start method of the Stopwatch class. The second method is the Stop method. The Stop method just calls the Stop method of the Stopwatch class.
What I will do next is modify the Program.cs file. Like in the last tutorial I will be using a game loop to control the flow of the game. In this version though I will be making a slight modification so that when the program exits all items of the form that can be disposed will automatically be disposed of. Change the main method of the Program.cs file to the following.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (Form1 form1 = new Form1())
{
form1.Show();
form1.GameLoop();
}
}
By using the using statement for creating the form when the form is closed everything in the form that can be disposed of will be. Inside the using statement you need to make sure that the form is visible. You do this by calling the Show method. Next I call the GameLoop method which I will show you in a moment.
Before I get to the GameLoop method there are a number of fields that I want to add to the code of Form1. I will be using these fields in the game, which unlike my other tutorial, will do a little logic and a little rendering so you can see that the game is actually doing something. What you will want to do is add the following fields to the code of Form1.
HiResTimer timer = new HiResTimer(); long startTime; long interval = (long)TimeSpan.FromSeconds(1/30).TotalMilliseconds; Graphics graphics; Graphics imageGraphics; Image backBuffer; Image blackScreen; int clientWidth; int clientHeight; Rectangle image = new Rectangle(0, 0, 40, 50); Point direction = new Point(1, 2);
I will need a timer to control the speed at which the game runs so there is a HiResTimer field. The next field is of type long and it will be used to determine the millisecond at which each game loop starts. The next field is also of type long. I use TimeSpan to set the frame rate at which the game will run. I use the FromSeconds method and pass in 1 over 30 and get the result in milliseconds. What I am aiming for here is a frame rate of about 30 frames per second. Next there are two fields of type Graphics which are used for drawing with GDI+. The first one is for actually drawing to the form. The second one is for drawing to a back buffer. To prevent flickering it is best to draw to a back buffer and then copy the back buffer to the form. The second one is for drawing to the form itself. I also have an Image field for the back buffer. I will frequently need to know the width and height of the client area so there are two fields to hold them. The next two fields will be used to render and preform game logic.
There is one more thing I want to do before I get to the GameLoop method. I want to initialize a few things in the constructor for Form1.
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
this.MaximizeBox = false;
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.ClientSize = new Size(300, 300);
clientWidth = this.ClientRectangle.Width;
clientHeight = this.ClientRectangle.Height;
backBuffer = (Image)new Bitmap(clientWidth, clientHeight);
graphics = this.CreateGraphics();
imageGraphics = Graphics.FromImage(backBuffer);
}
You should do things with the form after the call to InitializeComponent because until that is called nothing truly exists on the form. The first thing I do is set the DoubleBuffered property of the form to true to stop flickering. GDI+ is slow, really slow, and it slows down even more in larger areas. To help things along I set the form so it can't be maximized or resized. I then set the client size to 300 pixels by 300 pixels. I set the two fields for the width and height of the client area. Next I create the back buffer image by creating a new Bitmap and casting the result to an Image. The Graphics class does not have a constructor. You need to create them by other means. Controls have the ability to create instances of the Graphics class using the CreateGraphics method so I do that with the form. The Graphics class also has the ability to create an instance using the FromImage method and I do that for the back buffer.
Now it is time for the GameLoop method. The method is pretty much the same as in the last tutorial. This is the code.
public void GameLoop()
{
timer.Start();
while (this.Created)
{
startTime = timer.ElapsedMilliseconds;
GameLogic();
RenderScene();
Application.DoEvents();
while (timer.ElapsedMilliseconds - startTime < interval) ;
}
}
What this does is first start the timer. It then loops while the form is open. Each time through the loop it sets the startTime field to be the number of elapsed milliseconds since the timer was started. It then calls a method called GameLogic which will control objects in the game. It is important to update objects in a game before rendering them. It then calls a method RenderScene that will draw the game objects. It then tells the form to process any events that are in the queue of events. The last part is what keeps the game running at a relatively constant speed. It just loops until the elapsed milliseconds in the timer minus the starting value is less than the interval or frame rate.
Like I said the GameLogic method is what controls the objects in the game. In this simple game there is only one object, a rectangle that floats around the window. This is the code for the GameLogic method.
private void GameLogic()
{
image.X += direction.X;
image.Y += direction.Y;
if (image.X < 0)
{
image.X = 0;
direction.X *= -1;
}
if (image.Y < 0)
{
image.Y = 0;
direction.Y *= -1;
}
if (image.X + image.Width > clientWidth)
{
image.X = clientWidth - image.Width;
direction.X *= -1;
}
if (image.Y + image.Height > clientHeight)
{
image.Y = clientHeight - image.Height;
direction.Y *= -1;
}
}
I use the direction field to handle moving the rectangle. I set the initial values to (1, 2). That means that X value is 1 and the Y value is 2. When you are working with graphics in 2D the X coordinate increases as move across the screen. The Y coordinate increases as you move down the screen. So at the start the rectangle will be moving right 1 pixel at a time and 2 pixels down at a time. If this was to continue the rectangle would eventually disappear. So I will do some bounds checks to see when the rectangle reaches the edges of the window. Checking for the left hand side and top of the form are the easiest cases. You know the X and Y coordinates of the rectangle because they are properties of the rectangle. To see if the rectangle will go off the left side of the screen you check to see if the X property is less than zero. If it is you set it to zero to keep it from going off the screen. Just staying in the same position is boring though. So what I do is multiply direction.X by -1 which will reverse the direction. To keep it from going off the top of the screen what you do I check to see if the Y property is less than 0. If it is set it 0 and then multiply direction.Y to reverse the direction the square is travelling. Checking for the right hand side is a little more difficult what you do is check to see if the X property plus the width of the rectangle is greater than the width of the client area. If it is set it the the X property to the width of the client area minus the width of the rectangle and again reverse the direction. Finally for the bottom of the window you check to see if the Y property of the rectangle plus the height is greater than the height of the client area. If it is set the Y property to the height of the client are minus the height of the rectangle and reverse the direction.
That just leaves the RenderScene method. This is where I actually draw something. This is the code for the RenderScene method.
private void RenderScene()
{
imageGraphics.FillRectangle(new SolidBrush(Color.Black),
this.ClientRectangle);
imageGraphics.FillRectangle(new SolidBrush(Color.Blue), image);
this.BackgroundImage = backBuffer;
this.Invalidate();
}
What the RenderScene method does is first fill the back buffer with black using the FillRectangle method of the Graphics class. The overload that I used requires a Brush and a Rectangle. For the brush I created a solid black brush. For the rectangle I use the ClientRectangle property of the form. I then draw a solid blue rectangle using a solid blue brush and the rectangle for the image. I then set the BackGroundImage property of the form to the backBuffer. I then call Invalidate method to tell the form that it needs be be redrawn.





MultiQuote







|