Click here to Skip to main content
Click here to Skip to main content

Bunnyaruga: GAPI, Hekkus, Basics of Deployment

, 25 Apr 2004
Rate this:
Please Sign up or sign in to vote.
This article shows an example of a game that uses the GAPI and Hekkus libraries. It also shows a nice and free way of deploying your games/applications without requiring the .NET Framework installed on the end user machines.

Bunnyaruga in action Bunnyaruga in action

Introduction

Bunnyaruga (bunny-ah-roo-gah) is a simple game that is essentially a merge of Escape and Ikaruga. This sample/article will attempt to give an example of what is required to make a game using the Compact Framework. Hopefully you can use it as an example of everything from using the GAPI library with .NET wrapper, adding sounds, and creating an installer for the game all in one package.

Background

I doubt much background is required in understanding this article. Using the references listed in the Points of Interest section should help explain any issues/questions you may have. An important note is that this sample will NOT work on the emulator. Apparently the emulator's don't have the gx.dll so all the functions that use it will fail. I found some file that was supposed to let you emulate the gx.dll calls on the emulator but it never worked for me. So sadly you will have to do your development/testing using a real Pocket PC.

Compiling

Nothing special to compile this game but like I said before it won't work on the emulator. If you want to try it without compiling the source for yourself download the demo project which is just a installer file to install onto your Pocket PC. It will deploy all the proper DLL's that are required. If you compile the source yourself you will need to copy the proper HekkusSS.dll and GAPINet.dll to the same directory the game is copied to on your Pocket PC (by default it's /Bunnyhug/Bunnyaruga). If you don't copy these an error message will be displayed when attempting to run the game.

If you compile in Debug mode the CAB files and installer will not be created. If you want to create the CAB files and installer you will need to compile in Release mode. Release mode will only build if you extracted the source code to the C:\projects\bunnyaruga directory (refer to the Deployment section for more information). If you did extract to the proper folder the first time you compile in Release mode the pre-build event on the Customer Installer project will fail. Once you build the 2nd time it should all work fine.

How To Play

Though this game isn't it to hard to play I just wanted to go over the rules and controls. There are two game modes, chomp and survival. In both you tap and hold the shark and move the stylus around to move the shark. In survival your goal is dodge the fish. Each level more and more fish will get added making it more difficult to survive. In chomp your goal is to eat all the fish. A blue shark can eat blue fish and a white shark can eat the white fish. To switch the color of your shark press any button. If you want to quit at any time press any direction on the directional pad.

Using the code

Editor Note - Source code lines have been wrapped to avoid horizontal scrolling

MainForm

I was going to put a splash screen in this game but it really doesn't take long to load and frankly there are already lots of tutorials for this (look at any of the articles on MSDN in the references section). Anyways, the first thing I wanted to do was make the form full screen with no title bar or menu. To do this the OnLoad method should contain the following:

private void MainForm_Load(object sender, System.EventArgs e)
{
  graphics = this.CreateGraphics();
  currentState = State.Title;
  game = null;

  // set the form to full screen
  this.ControlBox = false;
  this.Menu = null;
  this.WindowState = FormWindowState.Maximized;
  this.FormBorderStyle = FormBorderStyle.None;
}

Another thing we want to do in this class is override the OnPaint, OnPaintBackGround, OnMouseDown, OnMouseUp, OnMouseMove events so that we can handle them ourselves. For example, the OnPaintBackGround we want to draw the title screen with the menu if the game is in the title state, if the game type is the game type selection we want to draw the game type selection screen, play state we will let our Game class handle the drawing. Finally, since both the options and scores are just normal forms we want to just call the base OnPaintBackground.

/// <summary>
/// Stubbed out so we can handle drawing
/// </summary>
/// <param name="e" />
protected override void OnPaintBackground(PaintEventArgs e)
{
  switch (this.currentState)
  {
    case State.Title:
      // draw the black background
      base.OnPaintBackground(e);

      // draw the title screen
      System.Drawing.Bitmap titleScreen = new System.Drawing.Bitmap(
        System.Reflection.Assembly.GetExecutingAssembly().
        GetManifestResourceStream("Bunnyhug.Bunnyaruga.
        Images.title.gif"));
      this.graphics.DrawImage(titleScreen, 0, 0);
      titleScreen.Dispose();

      // draw the menu bitmap transparent over the title screen
      Bitmap menu = new Bitmap(
        System.Reflection.Assembly.GetExecutingAssembly().
        GetManifestResourceStream("Bunnyhug.Bunnyaruga.
        Images.menu.gif"));
      System.Drawing.Imaging.ImageAttributes imageAttributes = 
        new System.Drawing.Imaging.ImageAttributes();
      imageAttributes.SetColorKey(Color.Black, Color.Black);
      this.graphics.DrawImage(menu, new Rectangle(0, 
       this.ClientRectangle.Height - menu.Height, menu.Width,
       menu.Height), 0, 0, menu.Width, menu.Height, 
       GraphicsUnit.Pixel, imageAttributes);
      menu.Dispose();
      break;
    case State.GameType:
      // draw the black background
      base.OnPaintBackground(e);

      // draw the title screen
      System.Drawing.Bitmap gameTypeScreen = new System.Drawing.Bitmap(
        System.Reflection.Assembly.GetExecutingAssembly().
          GetManifestResourceStream(
           "Bunnyhug.Bunnyaruga.Images.gameType.gif"));
      this.graphics.DrawImage(gameTypeScreen, 0, 0);
      gameTypeScreen.Dispose();
      break;
    case State.Play:
      // let GXGraphics handle the drawing
      break;
    case State.Options:
      // draw the black background
      base.OnPaintBackground(e);
      break;
    case State.Scores:
      // draw the black background
      base.OnPaintBackground(e);
      break;
  }
}

I guess another nifty thing to mention is making a bitmap transparent (used a lot in drawing the sprites during the game) so that we can make a base background image and display different menu bitmap on top of the background image. I only did this for the title screen as I did the options and scores screen display a completely new form. Anyways, to draw a bitmap transparent you have to use the ImageAttributes class.

// draw the menu bitmap transparent over the title screen
Bitmap menu = new Bitmap(
  System.Reflection.Assembly.GetExecutingAssembly().
 GetManifestResourceStream("Bunnyhug.Bunnyaruga.Images.menu.gif"));
System.Drawing.Imaging.ImageAttributes imageAttributes = 
 new System.Drawing.Imaging.ImageAttributes();
imageAttributes.SetColorKey(Color.Black, Color.Black);
this.graphics.DrawImage(menu, new Rectangle(0, 
  this.ClientRectangle.Height - menu.Height, menu.Width, menu.Height), 
  0, 0, menu.Width, menu.Height, GraphicsUnit.Pixel, imageAttributes);

The way I handle menu clicking is quite poor as I just hard coded the locations of each of the buttons. It would be a lot nicer coming up with a better way of doing this but I didn't want to spend time on doing it for this.

OptionsForm and ScoresForm

Not much to mention about either of these. The OptionsForm was originally used for testing purposes so all the code for changing the different variables is still in this class but I hid the tab pages so unless you go and uncomment them the extra options won't be displayed. To display them all uncomment the two lines in this section of the InitializeComponent method.

      //
      // tabControl
      //
      this.tabControl.Controls.Add(this.generalTabPage);
//      this.tabControl.Controls.Add(this.fishTabPage);
//      this.tabControl.Controls.Add(this.sharkTabPage);
      this.tabControl.SelectedIndex = 0;
      this.tabControl.Size = new System.Drawing.Size(240, 256);
My problem with this form was I wanted to let the user enter their name but being that I didn't have the menu bar displayed I had no way of displaying the input panel. So, I listen to the GotFocus event and display the InputPanel. This works well but lacks the ability to get rid of the InputPanel nicely so you will have to select somewhere else on the screen for the InputPanel to minimize.

The ScoresForm has nothing special in it. I would really have liked to keep track of more than just the top score for each game type but for some reason the XmlSerializer from the OpenNetCF library wasn't working for me so I didn't spend any extra time on testing it more.

Game

When I first started on this game I wasn't quite sure what size of "fish" or "shark" I would use so I had a bunch of options that you could change on the game options screen (which are now commented out). However, the code to handle all these options is still in the Game class. Some of the options the game supports are: where the fish spawn, where the shark spawns, volumes for sound and music, fish speed is random between minimum and maximum or fish speed is constant using just the minimum speed, the shark toughness, whether the screen edge causes instant death or not, number of fish of each color and how much they increase in speed and count each level. If you look at the GameOptions constructor you can play around with the default settings for the game but I figured it would be best to have it more concrete because otherwise if I get to level 25 with the fish count increasing 2 each level while you have it increase by 15 fish each level the scores won't ever be close.

Mouse Events

Getting the shark to move relative to the stylus movements was quite easy. On the MouseDown event I check if the shark was tapped or not. If it was tapped I set a boolean mouseDownOnShark to true. In the MouseMove event if mouseDownOnShark is true then move the shark otherwise just ignore the MouseMove events. On the MouseUp just set mouseDownOnShark to false. That's about it for moving the shark.

/// <summary>
/// Mouse move event while the game is in play
/// Check to see if the user pressed the mouse down on the shark
/// </summary>
/// <param name="e" />
public void MouseMove(MouseEventArgs e)
{
  Point p = new Point(e.X, e.Y);
  if (mouseDownOnShark)
  {
    // move the shark relative to the last point clicked
    shark.CurrentX = shark.CurrentX - (lastPoint.X - p.X);
    shark.CurrentY = shark.CurrentY - (lastPoint.Y - p.Y);
    shark.LocationX = (int)shark.CurrentX;
    shark.LocationY = (int)shark.CurrentY;
    lastPoint = p;
  }
}

The Draw Method

This is where the bulk of the game processing goes on. After initializing any variables it goes and displays a count down starting at 3. Once the count down is done it starts calculating how each object should move each frame and updating the position of the fish. The main game loop does the following:

  1. Draw the shark
  2. Check if the shark is touching any edges and whether it should die or not
  3. Update the fish position and direction. Draw each existing fish and check if it's colliding with the shark
  4. Check if the game should finish because the shark ran out of hit points, the time ran out (survival), or the shark ate all the fish
  5. Draw the status bar
  6. Process all the key events
  7. End the game if shark dead

Instead of rehashing all the code here just check the Game class Draw method for more details.

Hekkus

The Hekkus sound system was incredibly easy to implement into the game. If you download the sample included with the Hekkus Sound System .NET Wrapper you can use it is a good start. Just to go over the steps, this is what you need to do. First, declare an instance of the HekkusSound. You need this if you plan on playing any sounds or music. If you want to play music declare a HekkusModule variable. Finally, if you want to play sound effects declare HekkusSoundFX variables for each sound effect you want to have. To initialize the sound library you will need to just call the Open method on the HekkusSound object.

try
{
  hss = new HekkusSound();
  music = new HekkusModule();
  soundFishEaten = new HekkusSoundFX();
  soundSharkHit = new HekkusSoundFX();
  hss.Open(44100, 2, 4, true);
}
catch
{
  MessageBox.Show(
"Error opening sound library.  Make sure that HekkusSS.dll " + 
 "is in the application directory");
}
To load the sound effects is also very simple.
try
{
  soundFishEaten.LoadFile(@"sfx/blink.wav");
  soundSharkHit.LoadFile(@"sfx/chomp.wav");

  if (options.SoundsVolume == 0)
  {
    soundFishEaten.Volume = 0;
    soundSharkHit.Volume = 0;
  }
}
catch (System.Exception)
{
  // no sound effects to load
}
For the music I wanted to be able to add music easily without making any code changes. My LoadMusic method searches the Music folder for any mod files and randomly chooses one to play for each game. This way you can head over to Mod Archive and download any mod files that you want/like and just put them in the music directory.
try
{
  // filter all the mod files from the music directory
  string appPath = Path.GetDirectoryName(
    Assembly.GetExecutingAssembly().GetName().CodeBase);
  appPath = appPath + "\\";
  string[] files = System.IO.Directory.GetFiles(
    appPath + @"music", "*.mod");

  if (files.Length > 0)
  {
    // randomly choose a file to play
    Random rnd = new Random();
    string file = files[rnd.Next(0, files.Length)].Replace(appPath, "");
    music.LoadFile(file);
    music.Loop = true;
    if (options.MusicVolume == 0)
    {
      music.Volume = 0;
    }
  }
}
catch (System.Exception)
{
  // failure loading music
}

To start the music or play a sound effect is very simple.

// play sound effect
hss.PlaySfx(soundFishEaten);

// start music
hss.PlayMod(music);

That's about all I'm going to say about the Hekkus library. It's very easy to use and lets you focus on other aspects of your game instead of worrying about how to play the music and sound effects.

Deployment

Now on to the deployment of the game. There are lots of articles on this already but I wanted a way that didn't require the user to have .NET installed on their Windows PC but still let me build the packages from within Visual Studio. So, what I started with is this article on MSDN for deploying Compact Framework applications. It's a great place to start but the problem is the MSI file requires the user to have .NET installed for the custom actions to work (such as calling active sync to send the cab files to the Pocket PC). To replace the MSI file I found a program called EZSetup from SPB Software. EZSetup makes a really simple installer that displays a readme.txt, a EULA and then installs the correct CAB files for the device. If you're interested, here is a EzSetup Guide that provides a quick how to. Anyways, since the MSDN article covers how to make the Custom Installer and editing the INF file for the CabWiz and the EzSetup Guide covers everything for EZSetup I'm just going to cover what needs to be added to the INF file for GAPI and Hekkus games/applications.

Most Pocket PC's should already have the gx.dll file so you shouldn't have to deploy it with your game. What you will need to deploy is the GXGraphics.dll, GXInput.dll, GAPINet.dll, HekkusSS.dll, sounds effects and music. Since GAPINet.dll and HekkusSS.dll are specific to the processor type you will need to include each supported type in your installer. I just include ARM, MIPS, and SH3. When Visual Studio creates the .INF file for you (when you right click on a project and go to "Build Cabs" it will create it for you) it will create ARM, MIPS and SH3 sections for you to fill the files into. If my case I store all these DLL's in the Library directory with each processor type in a different folder. The .INF file contains the following:

Editor Note - Lines have been wrapped to avoid horizontal scrolling
[SourceDisksNames.ARM]
2=,"ARM4",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
3=,"ARM_Setup",,"C:\Program Files\Microsoft Visual Studio 
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\ARM\"
10=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\ARMRel\"
13=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\ARM\"

[SourceDisksNames.SH3]
4=,"SH36",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
5=,"SH3_Setup",,"C:\Program Files\Microsoft Visual Studio 
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\SH3\"
11=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\SH3Rel\"
14=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\SH3\"

[SourceDisksNames.MIPS]
6=,"MIPS8",,"C:\projects\Bunnyaruga\Bunnyaruga\obj\Release\"
7=,"MIPS_Setup",,"C:\Program Files\Microsoft Visual Studio 
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\wce300\MIPS\"
12=,"Hekkus",,"C:\projects\Bunnyaruga\Library\Hekkus\MIPSRel\"
15=,"GAPINet",,"C:\projects\Bunnyaruga\Library\GAPINet\MIPS\"

[SourceDisksFiles.ARM]
vsd_config.txt.ARM=2
vsd_setup.dll=3
GAPINet.dll=13
HekkusSS.dll=10

[SourceDisksFiles.SH3]
vsd_config.txt.SH3=4
vsd_setup.dll=5
GAPINet.dll=14
HekkusSS.dll=11

[SourceDisksFiles.MIPS]
vsd_config.txt.MIPS=6
vsd_setup.dll=7
GAPINet.dll=15
HekkusSS.dll=12

GXGraphics.dll and GXInput.dll are both .NET CF libraries so they don't need to be specific for ARM, SH3 or MIP3 so just include them in the common files area in the .INF file.

[SourceDisksNames]
1=,"Common",,"C:\projects\Bunnyaruga\Bunnyaruga\bin\Release\"
8=,"Music",,"C:\projects\Bunnyaruga\Music\"
9=,"SFX",,"C:\projects\Bunnyaruga\SFX\"
16=,"SFX",,"C:\projects\Bunnyaruga\Configuration\"

[SourceDisksFiles]
Bunnyaruga.exe=1
GXGraphics.dll=1
GXInput.dll=1
HekkusNetPpc.dll=1
blink.wav=9
chomp.wav=9
heavybutcool.mod=8
bluemon.mod=8
settings.ini=16

Another thing I do is set the pre-build event for the CustomInstaller project (the CustomInstaller project is the same as the article on MSDN describes) so that it will build the CAB files for me and pending that's successful the post-build event will generate the new installer package by calling the Deployment\build-setup.bat file. So I pretty much just use the CustomInstaller project as a way to build the installer in Release mode.

And finally, all my deployment scripts are hard-coded to work in the C:\projects and having Visual Studio 2003 installed to the default directory. So if you want to build the installer package you will need to either extract the source code to the C:\projects\bunnyaruga directory or go and change the .INF file to point to the directory where you extracted the source code. The project will build fine but the CAB's and installer package won't be built if you don't do this.

Points of Interest

Where did the idea come from?

I got the idea for this game from this addictive little game where all you do is click on the red box and dodge the other boxes moving around. I thought that it would be pretty nifty to play on Pocket PC. Well, after I made a quick version for PPC and showed some people at work they started tossing suggestions/ideas my way, one of which was, "Hey, this thing needs sharks with lasers!" Since I had to call the rectangle that you moved around something I decided to call it "the shark". Soon after the rectangles that I had the shark dodging became called "the fish". Sadly, the shark does not have lasers.

The next feature I added was the ability to change the sizes of all the fish and the shark to try it out. It turned out I preferred having smaller fish (5x5 pixels instead of 20x20 pixels) to dodge. After I showed this to people at work the next thing they suggested, "Why don't the rectangles look like fish? Why is it called a shark if it's just a rectangle?" So using my incredible lack of graphical skills I attempted to make a bitmap with animated fish. The shark is almost as bad. Oh well.

Finally, I remembered playing Ikaruga for Game Cube (also on Dreamcast) which is a game like 1942 but with a twist. There are two colors of bullets, black and white that you have to avoid. The other twist is that you can change your ship color from black and white. When your ship is black it can absorb the black bullets but white kills you and as a white ship you absorb white and black kills you. So I added 2 colors of fish and made it so the shark can switch between the colors. It was with this the name "Bunnyaruga" came into being.

The problem in the end is that I originally just wanted a simple game dodging static sized rectangles and now I have a game with smaller multi-colored fish and the ugliest "shark" ever.

References

Bugs

I'm sure there a lot more bugs than I know about in this game but for now these are the ones I've come across.

  • Every now and then when you are a blue shark and eat a blue fish you end up losing a hit point. Although it should be really rare that two fish have exactly the same velocity it does happen. A cheap solution was to make sure no two fish spawn in the same spot.

Enhancements/Features to Add

These are a couple of enhancements I would like to add at some point

  • Better hit detection. Right now the hit area is still a rectangle. I just have to follow the suggestions from the MSDN: Writing Mobile Games Using the Microsoft .NET Compact Framework article.
  • Better score keeping and serialization of the settings file. I was planning on using the XmlSerializer from the OpenNetCF library but for some reason when I deserialized the XML files it never deserialized properly. So instead of attempting to figure out what was wrong I used a very dirty way of serializing/deserializing the settings file and for scores I'm just keeping the top score for each game type.
  • Better/Reusable menu buttons. Instead of hard coding all the button areas for the title and game type screens it would be nicer to have this implemented in a way that I could reuse it in other games/applications.

Notes

After I wrote this sample game MSDN posted 2 great articles for making games on Pocket PC. The one is a breakout game called "Bust This" and the other is a side scroller called "Ultimate GMan". I recommend giving both of those articles a look.

History

  • April 19, 2004 - Initial Version

License

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

Share

About the Author

Jeffrey Vanneste
Web Developer
Canada Canada
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 26 Apr 2004
Article Copyright 2004 by Jeffrey Vanneste
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid