As part of my ongoing adventures in C#, I decided to write a game, largely because people seem to like them, and I had an Asteroids game already written using C++/DirectX, and I figured that meant I had the program logic and the graphics, I just needed the parts I wanted to learn, such as handling keyboard input, resources, etc. Someone else beat me to the punch though, so I decided to go for something a little more simple - a sideways scrolling game in which the object is simply to avoid oncoming asteroids.
For those who don't remember, parallax scrolling was a method of making things look cool in the days prior to 3D. Basically it involves scrolling a number of different bitmaps at differing rates, which gives the illusion of 3D, in the same way that two moving objects moving at the same rate would travel at different speeds if they were different distances from you.
The first step in an action based game is to make it run the same speed regardless of the computer it's running on. Using C++, I would do this by catching WM_IDLE, and drawing my objects whenever the processor is idle, but moving them based on time elapsed since I last moved them. In this manner the speed is always the same, and we get the highest frame rate possible for the processor. A slower machine slows down the frame rate, not the game.
Well, I don't know if C# has an OnIdle message, although I know it's possible to catch the message using some fancy footwork as documented in the Petzold book. However, for the sake of the exercise, I've decided to use a timer instead. In C#, we set up a timer like this:
private Timer timer = new Timer();
timer.Tick += new EventHandler(OnTimer);
timer.Enabled = true;
timer.Interval = 1000/60;
The variable is set as a class member and the rest is done on initialisation. We are defining an event handler for the timer, turning it on and setting it to go off 60 times a second. A timer will only fire if the system is not busy, so we are not guaranteed that we will get precisely 60 shots a second. To get a better degree of accuracy, we will use a
DateTime object to time our timer. We create a member
m_DateTime and set it using
DateTime.Now. Then at the start of our timer function we do this:
TimeSpan ts = DateTime.Now - m_DateTime;
if (ts.Milliseconds > 1000/m_nFPS)
m_DateTime = DateTime.Now;
m_DateTime.AddMilliseconds(ts.Milliseconds - (1000/m_nFPS));
In other words, if the time that has passed is more than the time between our desired frames per second value, we spring into actoin, do our drawing and also reset the variable to be Now() again, plus any remaining offset. As we will see later, although I tested this code using some simple drawing, as soon as we scroll the bitmap it is moot, because after turning the timer off, so it goes flat out, I get about 2-3 fps. C# is not fast enough for action games, or at least it does not provide an way I can see to perform the scrolling on the bitmap quickly.
The .NET platform has an interesting way of dealing with resources. Basically a resource is added by selecting Project | Add Existing Item, and then by clicking on that item in the Solution Explorer, it can be changed to a Build Action of 'Embedded Resource, i.e. it becomes part of your .exe. To load a bitmap resource, I use this line:
m_bmPlanets = new Bitmap(GetType(), "Planets.jpg");
Now my bitmap is loaded from the resources. Depending on your default namespace, you may need to specify a namespace name, such as Collision in this case, in order to load a resource. I must admit I spent an hour on this before it started to work, and I'm not sure what I did....
My initial strategy was to create a system where the background was constantly changing, and so I've built two resource bitmaps, one that holds star patterns of the same resolution in a row, and one that holds a planet per tile, all tiles again the same size. Then I built a bitmap that was a tilewidth wider than the screen, and filled it every time the screen had scrolled a tilewidth, drawing new images off screen so they scrolled into view. I soon discovered it was very slow. The only way I found to scroll a bitmap without writing an image filter was to make a clone, and copy it back to itself offset by a certain number of pixels. For speed I now have a repeating system, where I draw both bitmaps twice over, and use the
TranslateTransform to move the aspect of the Graphics object before drawing my two bitmaps. Sadly, it is also very slow, leaving me to conclude that there is no quick way to do what I am trying to do. The timing code I presented before is turned off in the demo, because it doesn't get used.
The other thing I found is that in order to draw the planets over the stars with transparency, I needed to use the
ImageAttributes object. Bitmaps have a
MakeTransparent method, which takes a colour to make transparent, but as I used resources saved as a jpeg, I found this did not work. JPEG is a lossy compression, which means what you get out is not what you put in. Specifically, instead of all being hard black, my transparent area was in a range of black, such that I needed to filter 0,0,0, through to 35, 35, 35 to get the masking effect I needed.
ImageAttributes iattrib = new ImageAttributes();
iattrib.SetColorKey(Color.FromArgb(255, 0, 0, 0),
Color.FromArgb(255, 35, 35, 35));
gr.DrawImage(m_bmPlanetLayer, new Rectangle(0, 0, 640, 320),
m_nPlanetPos, 0, 640, 320, GraphicsUnit.Pixel, iattrib);
Where to from here?
Having found that GDI+ is too slow to do what I wanted, I have decided to simplify. The next installment will have planets which I track on the screen, and a ship I can move in it. It will not scroll the stars anymore, hopefully providing a speed increase sufficient to prepare for the last installment, where I will be doing a per pixel hit test to know when I've flown into a planet.