Freigeben über


Writing a retro 2D arcade game for Windows 8 using MonoGame

When I start using a new platform, I like to do one specific thing: write a version of Asteroids for it. I find that Asteroids is both simple enough to write quickly, but it is a good enough test of the 2D rendering features available. Also, I like Asteroids. Pew, pew, pew.. *crunch*.

Unfortunately, the last time I wrote a version of Asteroids, I received an email from some Men in Dark Suits asking me not to write versions of Asteroids, so these days I try to do something different. When it came to writing a game for Windows 8, I reached back to my youth and a computer called the BBC Micro, Model B. This was an 8-bit computer, based on the 6502 CPU, popular in schools in the UK.

Although it was always too expensive for me to actually own one, I played with the Beebs in my school quite a bit (and then went home to my ZX81, Jupiter Ace, borrowed ZX Spectrum, Amstrad CPC, or Amiga 500 depending on the year.). Anyway, one of my favorite games on the Beeb was called “Frack”. No wait, that’s not actually true – but Frack was a funny game. No, my favorite game (not counting Elite of course) was “Starship Commander”. This 2D game had you fly a space ship around, shooting other spaceships. So far, so the same, right?

What made “Starship Commander” different was that your ship stayed in the middle of the screen, always facing upwards, while the entire universe rotated and moved around it (now that makes me think of Futurama, and the way the Professor explained how the drive in the Planet Express Ship worked – Good news, everyone! It’s not impossible, merely improbable.).

This viewing angle provided a unique playing experience, and I wanted to pay homage – so I knocked up a game called “Sector 7G”. It’s available on the Windows Store right now - here's the link - in case you wanted to see what all this is about.

 

 

Sector 7G and MonoGame

I have previously written apps in JavaScript and HTML5, and C# and XAML, but it was clear that although very useful, these languages and frameworks are not ideal for gaming. I even wrote a version of Asteroids in C#/XAML (don’t tell!) but it felt clumsy. Yes, you can use the XAML canvas to position XAML controls (e.g. Image) but it wasn’t right. And yes, you can write really good games in JavaScript, using the HTML5 canvas. Now, I like JavaScript, but I find that I'm a sloppy programmer, and anything too large quickly gets me into a spaghetti coding nightmare. Also, call me old fashioned, but I like a language which supports returning more than one value from a method without getting all hacky about it. But that's just me. :)

So, clearly, it was time to learn DirectX.

Haha, no, only kidding. No, this time I decided to use MonoGame, the freely distributable replacement for the late, lamented XNA.

MonoGame is good at several things:

  1. Making it easy to set up a high performance game loop,
  2. Drawing sprites,
  3. Handling input (keyboard, touch),
  4. Playing sounds,
  5. Supporting C#.

Is that enough for a game? Heck, yes! And even better, MonoGame is cross-platform. Using various other tools such as Xamarin, you can publish your C#/MonoGame project game to iOS, Android and probably the Amiga too.

The Problem (or not.. )

The biggest catch with using MonoGame is the way it uses a “content pipeline”, which is a fancy way of saying all the images and sounds it uses must be converted into a special format. That means no PNGs or JPGs or WAVs, I’m afraid. And as MonoGame renders text as sprites, you need to do this for fonts too.

 Update: See the first comment. I missed the bit about there being support for Content Pipeline in MonoGame, although I've not been able to get it working properly just yet. You should definitely try it first, and if it doesn't work, try the following technique.

As far as I can tell, the only way to convert your files into this format is to use XNA. Which sounds nuts, because there is no XNA anymore: but there’s a solution: download the free Visual Studio 2010 Express  for Windows Phone  edition, as this version still supports creating XNA projects. Drag all your content into an XNA project, and hit build. The converted files will be created and you can then copy them directly from the build directory (when you dig down deep enough) into your project. Feels weird, but it works.


Make sure you set the Build Action correctly, once you add the freshly built .xnb files.

 

So. Games?

Games. We all love game. Playing them, and writing them. Actually, to be honest I enjoy writing them more than playing them. I have been immersing myself in Unity3D (see the last set of blog postings for how and why) but I still like MonoGame. While Unity3D takes the approach of “add a bunch of objects to a scene, and then script each object”, MonoGame is pure 1980's era one-guy-in-his-bedroom programming (which isn't meant to be a criticism, honestly, more of a lifestyle choice). You get a game loop and a routine to draw on the screen, and that’s yer lot. Everything else is up to you. And this might be a problem, except the game looping and the drawing is fast and easy to use, and you don’t need to learn C++ to do it.

Here’s what I’ve found about writing games: they start simple, and then get out of hand. Here's why. You have your gameloop – usually one or two functions that get called every frame. In the loop you handling several things:

  •          Drawing everything on screen
  •          Moving everything
  •          Doing collision check and game logic checks
  •          Reading input from the user

It’s simple enough, and if were writing a game that only had one screen and few changes over time, then you can probably do the entire thing in one class, and keep that dirty little secret to yourself. However, I’ve found that even a relatively simple game soon amasses so many “edge cases” when you need to do something specific only when something happens, that things quickly spiral out of control. So by all means, write everything in one class for your prototype to demonstrate that a particular game mechanic or animation works, but then be prepared to throw away that version and write a new one that nicely encapsulates things into classes. Your goal is to make sure that your gameloop isn’t a maze of if() statements, because if it is you will lose track of things very quickly, and never be able to debug it properly.

To try and keep things under control, I like to create a variable which I called “game state”, and use an enum type so I can write the values in a week’s time and still make sense of it. Game state will store the current activity – such as playing a typical level, or at the menu screen, or at the game over screen. I have a method which is called when I want to change game state, and this looks after getting whatever resources I need ready and maybe doing some screen transitions.

 

private enum GameStateType

        {

            Init,

            Menu,

            PrePlay,

            Play,

            Pause,

            Fade,

            GameOver,

            NextSector,

        };

 

private GameStateType GameState = GameStateType.Init;

private GameStateType NextGameState = GameStateType.Init;

 

Incidentally, in MonoGame there is no high-level support for switching between different game modes. If you want to fade screens, draw a black square over the entire screen and fade in its alpha, switch to the new mode, and fade it out again. Told you it was low-level stuff! This is why I have a variable called "NextGameState", because when I set GameState to Fade, I also set NextGameState to what I want to happen next - and when the Fade routine has finished, it switches to it, like this:

 

private void initFade()
        {
            fading = true;
            fadeCounter = 0;
            fadeout = true;

        }

 

 private void Fade()
        {
            //To use fade, set NextGameState and then initFade();

            if (fadeout)
            {
                fadeCounter += 5;
                if (fadeCounter >= 255)
                {
                    fadeout = false;
                    GameState = NextGameState;
                }
            }
            else
            {
                fadeCounter -= 5;
                if (fadeCounter <= 0)
                {
                    fading = false;
                }

            }

            _spriteBatch.Draw(_faderTexture, GraphicsDevice.Viewport.Bounds, new Color(Color.Black, fadeCounter));

        }

Spritebatchin' it

This recap will be nothing new to you if you know XNA of course, but a SpriteBatch is how you draw things to the screen in a MonoGame project. It’s a special object that handles rendering the textures – you call Begin() on it at the start of the game redraw event, and End() on it at the end of the game redraw event, and in between all your sprites (and fonts) appear on the screen.

Here is a typical draw routine, which draws a sprite using a tezture called "Star" after clearing the screen:

 

  private SpriteBatch _spriteBatch;

...

  _spriteBatch = new SpriteBatch(GraphicsDevice);

...

void DrawingMethod()
{

    _spriteBatch.Begin();

      // Draw Stuff
      _spriteBatch.Draw(_star, new Vector2((int) halfWidth - 8, (int) halfHeight - 8), null,
                                  Color.White*playerBeingHit, 0, new Vector2(28, 28), (float) 2.5, SpriteEffects.None, 0);

      _spriteBatch.End();
}

 

Now you can have multiple spritebatches inside the one drawing function, but you can't nest them.

How many sprites can you draw with MonoGame? Lots. In my game I’ve hundreds of animated stars, a dozen baddies, a dozen explosions each made up of hundreds of particles and it doesn’t skip a beat on an Atom based tablet (the ASUS Vivotab, which by the way, I continue to like. What’s not to like about a tablet that runs Visual Studio?).

 My game draws a starry background which moves and rotates, and lots of particles when things explode. MonoGame doesn't even blink.

 

Lines, Particles and Explosion Effects

MonoGame is great at drawing textures at a given X,Y position – but drawing lines is a bit more of a hassle. For the game, I wanted there to be lines emanating from the player’s ship to the bad guys and the energy nodes to provide a sense of their direction and how fast they are moving. Also, I thought it looked cool. I found some sample code from the MonoGame site which did all this for me in a few classes, which was nice. In fact, the lines drawing allowed the lines to start with one color, and fade to a second color which was a bonus. If you are using someone else's library for this kind of thing, be sure that it is available for the platforms you want to support. For example, a line drawing library which comes with an X86 compiled library isn't going to work well on a Surface RT which runs on ARM.

What’s an arcade game without explosions? Well, probably Pacman. Anyway, I love me some particles and explosions and stuff. When you shoot an enemy ship in this game, I trigger a particle explosion and then draw some extra-large circles of varying alphas and colors which start bright and fade over time. The effect is pretty good.

Kahboooom!!!! It's game over man, game over!

 I created a class to handle all the explosions, so I could trigger one wherever I wanted with minimal hassle. Here's what the class looks like. I have different types of explosion depending on what is exploding, set in the CreateExplosion() method. I handle multiple explosions, by creating a mutable list of explosions: when I need a new one, I just CreateExplosion() it, add it to the list. I enumerate through the list at draw and update time, and then remove the explosion from the list when it's run its course. I've limited the number to 10 in this game, but I suspect I could up that to 50 easily with no performance issues.

 

 

 

using System;
using System.Collections.Generic;
using System.Collections;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Diagnostics;

 

namespace Game
{
    class Explosion
    {
        private const int explosionSize = 50;
        struct shrapnel { public float x, y, dx, dy; };
        private shrapnel[] debris = new shrapnel[explosionSize];
        private int life;
        private const int limit = 60;
        private int explosionType;

        public bool alive;

        public void CreateExplosion(Vector2 position, int type)
        {
            alive = true;
            life = 0;
            explosionType = type;
            Random r = new Random();

            debris[0].x = position.X;
            debris[0].y = position.Y;

            for (int i = 1; i < explosionSize; i++)
            {
                debris[i].x = position.X;
                debris[i].y = position.Y;
                debris[i].dx =(float)( r.NextDouble()*6-3);
                debris[i].dy =(float) (r.NextDouble()*6-3);
            }
        }

        public void Move()
        {
            if (!alive) return;

            life++;
            if (life>limit)
            {
                alive = false;
                return;
            }

             for (int i = 1; i < explosionSize; i++)
            {
                debris[i].x += debris[i].dx;
                debris[i].y += debris[i].dy;
            }

        }

        public void Draw(SpriteBatch sb, Texture2D chunk)
        {
            if (!alive) return;

            float c = (float)(((limit - life) / (float)limit));

            if (explosionType==4)
                sb.Draw(chunk, new Vector2((int)debris[0].x - 14, (int)debris[0].y - 14), null, Color.White * 0.4f * c, 0, new Vector2(28, 28), (float)8.0, SpriteEffects.None, 0);

            sb.Draw(chunk, new Vector2((int)debris[0].x-8, (int)debris[0].y-8), null, Color.LightGreen *0.4f* c, 0, new Vector2(28, 28), (float)2.0, SpriteEffects.None, 0);
            sb.Draw(chunk, new Vector2((int)debris[0].x-8, (int)debris[0].y-8), null, Color.Green *0.4f* c, 0, new Vector2(28, 28), (float)1.0, SpriteEffects.None, 0);
           
            for (int i = 1; i < explosionSize; i++)
            {
                switch (explosionType)
                {
                    case 0:
                        sb.Draw(chunk, new Rectangle((int)debris[i].x, (int)debris[i].y, 10, 10), Color.White * c); break;

                    case 1:
                        sb.Draw(chunk, new Rectangle((int)debris[i].x, (int)debris[i].y, 10, 10), Color.Red * c); break;

                    case 2:
                        sb.Draw(chunk, new Rectangle((int)debris[i].x, (int)debris[i].y, 10, 10), Color.Green * c); break;

                    case 3:
                        sb.Draw(chunk, new Rectangle((int)debris[i].x, (int)debris[i].y, 20, 20), Color.Purple * c); break;

                    case 4:
                        sb.Draw(chunk, new Rectangle((int)debris[i].x, (int)debris[i].y, 20, 20), Color.White * c); break;
                }
            }
        }
    }
}

 

Windows 8 Specific Features

 

 

Storing the High Score

To store and recall the high score, I used code like this:

// Load previous high score

if (ApplicationData.Current.LocalSettings.Values.ContainsKey("HighScore"))

            {

               player_high = (int) (ApplicationData.Current.LocalSettings.Values["HighScore"]);

            }

 

// Save current score as high score

ApplicationData.Current.LocalSettings.Values["HighScore"] = player_score;

 
This allows the score to persist between app launches. I got the LocalSettings information on MSDN, and this topic includes details on how you could even let the score be shared between multiple computers automagically.

Dealing with Snap View

In Windows 8, the user could split the current screen to allow two apps side-by-side, in a kind of 70/30 split. Running two apps simultaneously on a tablet? I know, cool right? This was called "Snap View". Windows 8.1 goes one further, and pretty much lets you split up the screen however you want. So in theory, your app should pay attention for a message that says "hey, you've been resized!" and then measure the new screen size and behave accordingly. You could use this new view to create a utility bar on the side of the screen to display information, while still using IE or Mail if you wanted.

With a game, I thought it was unlikely to be the case that the user wanted to continue to play but with a tiny viewport onto the game. So instead of trying to resize the screen and deal with it that way, the game goes into pause mode. Here's how you listen for the message that the screen has changed:

  ApplicationViewChanged += (sender, args) => ViewChanged(args.ViewState);

and here is how you act upon it:

 private void ViewChanged(ApplicationViewState viewState)

        {

     switch (viewState)

            {

                caseApplicationViewState.Snapped:

                    GameState = GameStateType.Pause;

              break;

            }

        }

 

As you can see, it simply reacts to the Snapped state by going into the Pause game state. Because I had already written all the game state handling code, this was a piece of cake to do.

 

Store Review issues

Supporting this screen snap view is an important point, because you game must do certain things before the Windows Store will certify it and make it available. Another thing your app should do is work. Yes, if it doesn't work or if it crashes a lot, it'll get rejected. So test it, and test it on as many different computers as you can. Test it on a small touch device (they cost bout $250 these days, and I always like an excuse to buy her hardware! and test it on a non-touch Desktop to make sure it's working on both. Does your app include a features that don't have a keyboard equivalent? That's going to work for a lot of potential users, so code accordingly. 

Conclusion

Sector 7G  is really only one or two levels at the moment, but I hope to get around to adding more features and more baddies to it when I get more spare time. However, it proved to me that MonoGame is an excellent tool for writing 2D games. No matter if you are working on a retro arcade game or a letter-tile-based puzzle game, using MonoGame will help you get graphics moving around the screen.

Thanks for reading. If there is specific code you would like me to share, just ask.

BTW, here's a GREAT book on using MonoGame development: Windows 8 and Windows Phone 8 Game Development by Adam Dawes.

Comments

  • Anonymous
    September 19, 2013
    Monogame does have its own version of the XNA content project. You can add it to your solution, add your assets in there, build it and it creates XNB files just like you would expect. You just need to add them to your actual game project manually. more info here: monogamey.blogspot.be/.../bunny-hunters-online-asset-project.html

  • Anonymous
    September 19, 2013
    Thanks, Nico!

  • Anonymous
    September 25, 2013
    in regards to building an Asteriods clone; You dont have to stop because the men in black suites said so.  You cannot patent game play, you can patent the name Asteriods, Possibly the ship, maybe the shape of the Asteriods but not the idea of shooting rocks in space, so call it , shooting tiny rocks in space.  Don’t be bullied!!

  • Anonymous
    September 25, 2013
    Ah, but imagine that The Suits go after the app store that is publishing your game. The app store isn't going to get involved, and so they block your app until The Suits say it's ok. The Suits then say it isn't ok, no matter what changes I made. So the app store wouldn't publish. Perhaps it's different now, but a few years ago..