Introduction
Pocket 1945 is a classic shooter inspired by the classic 1942 game. The game is written in C# targeting the .NET Compact Framework. This article is my first submission to Code Project and is my contribution to ongoing .NET CF competition.
As well as being my first article this is also my first game ever. My every day work consists of building data centric business applications, so game writing is something completely different. So, go easy on me.
One of my goals when starting this project was to make a game that other developers could use as a starting point when getting into C# game development. I focused on keeping the code as clear and simple as possible. I also wanted to build this game without introducing any third party components such as the Game Application Interface (GAPI). The reason I did this was that I wanted to see what I could do with the core framework. Another goal was to take this game/example a step further than most tic-tac-toe examples and actually build a game that’s fun, challenging and looks good.
One of the things I realized when working on this project is that games take time, no matter how simple they are. The game is still not at version 1.0, but it is playable in it's current state. I’ve put the game up as a GotDotNet workspace and I encourage everyone that finds the game fun to join the workspace and help me build a fun shooter for the Pocket PC platform.
How to install/play Pocket 1945
In order to play you need a Pocket PC enabled device with the .NET Compact Framework 1.1 installed. To install simply copy the Pocket1945.exe file and the level XML files to a new folder on your device. No installation is required.
To play the game, you use the direction keys on your device. To exit, click the calendar button (first hardware button). To fire, click the second hardware button. Since I don’t own a real device I’m not sure what the “name” of these buttons are. But, just give it a go!
The current game is far from “finished”, but it is safe to run the code and it is playable. The game consists of 4 levels. To add levels of your own, simply make new level XML files and copy them to the game folder on your device. Since I don’t have a level editor yet I would suggest that you build your new level based on the existing one. If you make any fun levels, place share them with us.
Game design
The game consists of one Visual Studio .NET solution called Pocket1945. The project contains 13 classes, 3 interfaces, 1 structure and 7 enumerators. I’ve supplied a screenshot of the class view in VS.NET to illustrate the class design of the game.
The GameForm
class is the main class of the application. This class takes care of drawing and running the actual game. The Level class loads a level XML file and parses the XML out to objects. The Background class draws the map and background elements to the screen. The Player class defines the player character in the game. The Enemy class defines all enemy planes used in the game. The Bonus class is used for bonus elements such as an extra life or shield upgrade. The LevelGui
class is used to draw a simple in-game user interface. This class is used to display information about health, progress, score and such to the player.
The Input and StopWatch
class are taken from one of the MSND articles and gives you access to detected hardware buttons and a high performance counter. These classes are exactly the same as in the MSDN examples.
The IArmed
interface is implemented on game objects that can fire bullets and get hit by other bullets. The ICollidable
interface is implemented by items that can collide, such as bonus items. The IDrawable
interface is implemented by all objects that can be drawn to the screen during the game.
The different enums are used for properties such as type of bonus, weapon, enemy, movement and so on.
Level design
Each level is a XML file containing an ASCII table with the level map and XML nodes for background elements (such as islands), enemies and bonus elements. The Level class takes a path in the constructor and loads the XML file passed into the constructor and builds objects based on the nodes.
The ASCII table contains a table with 8 columns and an unknown number of rows. Each character in the table represents a 32x32 pixel background tile. So if the ASCII table is 56 rows high and 8 columns wide the background size will be 1792x256 pixels. The level file also has a setting called speed which sets the speed in pixels per second. An average map is 1800 pixels high and scroll at 15 pixels per second giving you approximately 2 minutes of game play. An example of the ASCII map:
<Map>
<![CDATA[]]>
</Map>
The enemy nodes contains all the settings for each enemy. The Y attribute tells the game engine when the enemy comes into play. So, for instance an enemy with Y=1500 starts to move when the player have scrolled to point 1500 on the map. Both enemies and bonus elements as positioned this way. An example enemy node looks something like this:
<Enemy X="140" Y="1700" Speed="80" MovePattern="1"
EnemyType="1" BulletType="1" BulletPower="5" BulletSpeed="150"
BulletReloadTime="1000" Power="10" Score="100" />
X
= the horizontal start position for this enemy.
Y
= the vertical start position for this enemy (when it gets focus).
Speed
= the speed the enemy moves at (pixels pr second).
MovePattern
= the way the enemy moves. At the moment straight ahead is the only pattern supported. I’ll add patterns like zigzag, swipe, kamikaze and simple AI. The MovePattern
enum defines the different move patterns.
EnemyType
= the type of enemy. There are currently 6 supported enemies. The EnemyType
enum defines the different types of enemies.
BulletType
= the type of bulled fired by this enemy. The BulletType
enum defines the different types of bullets.
BulletPower
= the power of the bullets fired by this enemy. This indicates how damaged the player gets by a hit.
BulletSpeed
= the speed the bullet is traveling at (pixels per second).
BulletReloadTime
= how long it takes for the enemy to reload (milliseconds).
Power
= how thick the enemy shield is (how hard it is to kill).
Score
= the score you collect by killing this enemy.
As you probably can guess I’m planning on writing a XML based level designer to build new levels for the game. I’m also thinking about making a XML Web Service based game server where you can upload and download new level sets. The XML format also needs to be formalized by making schemas. This is all on the TODO list.
Bonus elements are implemented almost the same way as enemies so I won’t go into details on the bonus nodes. An example level file can be downloaded here: Level1.xml(zipped)
Points of interests – Sprite list
One of the things that might be useful to look at is how I’ve implemented sprites. All the game graphics are embedded bitmap resources. At first I only used one single bitmap with all sprites, but I soon realized this would make the file hard to maintain and adding new sprites would cause problems with sprite indexes. I spited the image into logic sections like bullets, enemies, player, tiles, and bonuses.
All sprites are managed by the SpriteList
class. The class implements the singleton pattern to ensure that there is only one instance of this class trough out the game. The class consists of one public method called LoadSprites()
and several public Bitmap arrays holding each sprite. The LoadSprites()
method reads the embedded resources and call a private method called ParseSpriteStrip()
that reads a sprite strip (one large bmp with several sprite on it) and splits it into a Bitmap
array. Each game object (like a bonus item, a bullet or an enemy) draws it self by reading a bitmap from one of the public Bitmap
arrays.
By handling sprites this way you have a consistent way to access your graphical resources. By making the class a singleton you can be sure there is only one instance of the class trough out the application. All loading is done on game initialization making this a fast way to read sprites.
The following code shows the LoadSprites()
method and the ParseSpriteStrip()
method.
public void LoadSprites()
{
if(!doneLoading)
{
Assembly asm = Assembly.GetExecutingAssembly();
Bitmap tiles = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.Tiles.bmp"));
Bitmap bonuses = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.Bonuses.bmp"));
Bitmap bullets = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.Bullets.bmp"));
Bitmap smallPlanes = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.SmallPlanes.bmp"));
Bitmap smallExplotion = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.SmallExplotion.bmp"));
Bitmap bigBackgroundElements = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.BigBackgroundElements.bmp"));
Bitmap bigExplotion = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.BigExplotion.bmp"));
Bitmap bigPlanes = new Bitmap(asm.GetManifestResourceStream(
"Pocket1945.Data.Sprites.BigPlanes.bmp"));
Tiles = ParseSpriteStrip(tiles);
Bullets = ParseSpriteStrip(bullets);
Bonuses = ParseSpriteStrip(bonuses);
SmallPlanes = ParseSpriteStrip(smallPlanes);
SmallExplotion = ParseSpriteStrip(smallExplotion);
BigBackgroundElements = ParseSpriteStrip(bigBackgroundElements);
BigExplotion = ParseSpriteStrip(bigExplotion);
BigPlanes = ParseSpriteStrip(bigPlanes);
tiles.Dispose();
bullets.Dispose();
bonuses.Dispose();
smallPlanes.Dispose();
smallExplotion.Dispose();
bigBackgroundElements.Dispose();
bigExplotion.Dispose();
bigPlanes.Dispose();
doneLoading = true;
}
}
private Bitmap[] ParseSpriteStrip(Bitmap spriteStrip)
{
Rectangle spriteRectangle = new Rectangle(1, 1,
spriteStrip.Height - 2, spriteStrip.Height - 2);
Bitmap[] destinationArray = new Bitmap[(spriteStrip.Width - 1)
/ (spriteStrip.Height - 1)];
for(int i = 0; i < destinationArray.Length; ++i)
{
destinationArray[i] = new Bitmap(spriteRectangle.Width, spriteRectangle.Height);
Graphics g = Graphics.FromImage(destinationArray[i]);
spriteRectangle.X = i * (spriteRectangle.Width + 2) - (i - 1);
g.DrawImage(spriteStrip, 0, 0, spriteRectangle, GraphicsUnit.Pixel);
g.Dispose();
}
return destinationArray;
}
Points of interests – Double buffering
Another thing worth mentioning is how I draw each game frame. I’m using a common technique called double buffering. Basically what this mean is that I draw the entire frame in memory before moving it onto the screen. By doing this I avoid unwanted flickering. I don’t own a real pocket pc, but I’ve been told that the game performs really well on them. I’m hoping to win a Pocket PC so that I can test this for my self.
The GameForm
class (the main class of the game) has three private fields used for drawing:
private Bitmap offScreenBitmap;
private Graphics offScreenGraphics;
private Graphcis onScreenGraphics;
The offScreenBitmap
is the bitmap used to hold the in-memory version of each game frame. The offScreenGraphics
is a Graphics
object used to draw to the in-memory bitmap. onScreenGraphics
is a Graphics
object used to draw the in-memory bitmap onto the screen at the end of each game loop. All game elements that can be drawn implements the IDrawable
interface which has one method called Draw(Graphics g)
, which is used to draw itself onto the game form. In the game loop you call player.Draw(offScreenGraphic)
to make the player draw itself onto the off screen bitmap. Here is an example of the level loop showing how you pass the offScreenGraphcis
object to game object and move the offScreenBitmap
onto the screen at the end of the loop:
private void DoLevel(string filename)
{
CurrentLevel = new Level(GetFullPath(filename));
StopWatch sw = new StopWatch();
bool levelCompleted = false;
bool displayMenu = false;
while((playing) && (!levelCompleted))
{
Int64 startTick = sw.CurrentTick();
input.Update();
TickCount++;
CurrentLevel.BackgroundMap.Draw(offScreenGraphics);
HandleBonuses();
HandleBullets();
HandleEnemies();
Player.Update(input);
Player.Draw(offScreenGraphics);
playing = (Player.Status != PlayerStatus.Dead);
levelGui.Draw(offScreenGraphics);
onScreenGraphics.DrawImage(offScreenBitmap, 0, 0,
this.ClientRectangle, GraphicsUnit.Pixel);
Application.DoEvents();
}
}
TODO
There are tons of things that need to be done before this can be considered a “real” fun and exiting game. But, we’re getting there. I won’t go into details of everything that needs to be done, but I’ll add some important points:
- A good level editor.
- A set of XML Web Services to upload and downloads levels and post scores.
- New move patterns (how the enemies move).
- A game GUI (main menu, title screen, high score list etc).
- XML Schemas defining the rules for the level files.
- Better designed levels that are well balanced and challenging.
- Bosses. We need big bad bosses.
- Much more.
Any suggestions are greatly appreciated, either here on Code Project or on the workspace site.
Resources
I’ve used several online resources when building this game. First of all I have to credit Ari Feldman for the great graphics I’ve used in the game. Ari has published several sprite sets on his website under the SpriteLib GPL foil. The sprites can be found on http://www.arifeldman.com/games/spritelib.html.
I would also like to mention everyone on #ms.net on EFNet. Special thanks to ^CareBear for instant feedback on how the game is performing on a real device.
Other resources used are series of game articles published on MSDN:
Closing comment
There are still several things I’d like to mention, but in order to get this article/game submitted in time to be a part of the competition I really need to finish it up now.
Part II of this article will be available in X days/months/years/or maybe never. The game is far from finished, but is playable in it's current state. I hope you download it and give it a go. If you find the project fun and promising I would encourage you to join the workspace up on http://workspaces.gotdotnet.com/pocket1945 and take part of the on-going development of this game. The workspace will also be the place to get your hands on the latest releases of the game.
All comments on this article and the game in general are greatly appreciated.