Click here to Skip to main content
16,016,882 members
Articles / Game Development

Journey of Windows 8 Game (SkyWar) for Intel App Up Using MonoGame Framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
8 Jan 2013CPOL15 min read 24.5K   226   12   6
Journey of Windows 8 game SkyWar for Intel App Up competition

Introduction

This article is all about my experience of developing SkyWar game for CodeProject’s Intel AppUp competition. You can download complete source code of game attached with the article. The game is in validation stage in Microsoft store and you will find the same game soon in store for Windows Phone 7.5 Mango, Windows Phone 8. Its freely download able.

Background

I come across Intel App Innovation contest and decided to take part in this competition. It was about creating a windows 8 app, which demonstrates best features of Intel ultrabook. I thought lots of ideas and finally decided to create a Windows 8 game.

Ultrabook is enabled with feature like touch; sensor, GPS and smart connect etc. So I thought of creating a game which will utilize most of touch and sensors and will have good user experience. I was little aware of XNA framework. I thought using XNA we can create windows 8 game apps. But window 8 does not support XNA anymore, but Windows Phone 8.0 SDK does. So I started looking for alternatives and found this great article by bob. I found that using Mono Game framework; we can easily create game apps for windows 8 and even port games free of cost to android and iphone. The best thing about it is, it’s exactly similar like XNA.

Game Story Line

Before we write game, we should be clear in our mind what we are going to do. What's the goal of game, how game will proceed, what will be challenge for player, what will be ending criteria, etc.

Finally, I came up with an idea of sky shooting game called Sky War which is inspired by the old arcade games.

This game allows the player to control a space fighter ship in a star field.

Player will face different enemies in galaxy. In each stage, player will face set of enemies, once player defeat them, will be facing a boss or dragon enemy of stage.

Player will be provided with 3 initial life lines and 100% health in start. But later to defeat such dangerous enemies, player will be provided with amazing life lines in game.

The more player play the more complex game will become with difficult dragons or boss enemies.

We should have even detailed story line, which texture will do what, how many objects will be there etc. For this article I’ll just brief story.

Setting Up Environment

Before we start constructing game for windows 8, it will be good to know little about Mono Game Framework and how to setup environment for creating games.

MonoGame provides a cross platform XNA Framework implementation for XNA developers who want to take their code to non-Microsoft platforms as well as the ability. You can read further about mono game Mono Game Overview. Let me quickly jump to setting up environment.

How to setup Mono Game Environment?

There are two ways of setting up environment and I will explain easy way.

  1. Install visual studio 2010. Install its service pack sp1 on it (which is required for installation of windows mobile sdk 7.1).
  2. Install windows mobile sdk 7.1 which will automatically install XNA Game Studio 4.0 on your machine. (You must be wondering why we have installed vs 2010 because XNA template is not available in vs2012 and further MonoGame framework does not have content pipeline project, a pre-compiler step in preparation of graphic and audio use at runtime in XNA)
  3. Install Visual studio 2012.
  4. Install Mono Game installer which you can download from here.

This will setup environment for you and create MonoGame template in Visual studio 2010 and 2012.

Second approach you can read from here. Now we are all set to create our first Mono Game for windows 8.

Let's Begin

Before creating any game we should have these following things ready for us -

  1. Game story line (detail description of game, stages planning etc)
  2. Textures (which we will use in game like any sort of images, sounds etc)
  3. Game state management

I have given the brief game story which you can find above, but for games we should have a detailed story line. Later I spend lot to time collecting right textures for game from different free websites. I modified them in photoshop as per my need.

If you are starter and don't have any idea at all what’s XNA is? Please find links and books references at bottom of this article.

Once we have all these ready, let’s start coding.

Let’s open visual studio 2012 and select mono game project template. Give your project a name Sky War. Here we should notice, we don’t have content project. Best approach to deal with this is, create a Content folder in your game. Now open visual studio 2010, add all textures there which you want in game, build project and add in your content folder in sky war game. Now set “copy if newer” to all textures in content folder. This is how we need to deal with not having content project in Mono Game framework.

Now we can start writing classes for each object of game. Below attached snap shots will give you brief idea of game, what we gone develop –

Image 1

Image 2

Image 3

Image 4

Image 5

After looking at images we can identify below objects of game –

Scrolling Background (for creating moving background object), SpaceShip (it’s the ship which player will be flying in galaxy), Bullet (which will be reused by player and aliens), Alien (enemy ships), Alien Component ( Number of aliens visible on screen and initiating them back), AlienBoss (dragon of stage), AnimatedSprite (for animation of texture), Audio Library (for playing audios), Alien Type (game have different types of aliens with different capabilities), Level Data (for level information), Power Up (which powers currently player have) and main game screen which will initiate everything. I will discuss now most difficult part of code one by one, rest of code is fairly simple to understand. You can download the attached source code.

Microsoft has provided lots of game state management tutorial and code on their websites which you can find here. So we need not to discuss it here. I quickly jumped on link and downloaded code. I modified it for supporting touch and keyboard both. You find classes attached with article. We will focus on objects.

Scrolling Background (Moving Background)

I started writing code by creating a moving background. When you will run the game and when you closely notice, the background have two images laying over each other where second image will keep on moving on top of first image. This gives feel to user that player is moving in galaxy. This happens as user touches Gas button or press up keyboard key. Below picture will give you fare bit of idea.

Image 6

We have two images here, background image and an image parallel to it, which will move on top of it. Below code I used for creating moving background.

C#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace SkyWar
{
    public class Background
    {
        Texture2D t2dBackground, t2dParallax;
        GameData _gameData = null;
        int iViewportWidth = 800;
        int iViewportHeight = 600;
 
        int iBackgroundWidth = 1600;
        int iBackgroundHeight = 1080;
 
        int iParallaxWidth = 1600;
        int iParallaxHeight = 1080;
 
        int iBackgroundOffset;
        int iParallaxOffset;
        public int BackgroundOffset
        {
            get { return iBackgroundOffset; }
            set
            {
                iBackgroundOffset = value;
                if (iBackgroundOffset < 0)
                {
                    iBackgroundOffset += iBackgroundHeight;
                }
                if (iBackgroundOffset > iBackgroundHeight)
                {
                    iBackgroundOffset -= iBackgroundHeight;
                }
            }
        }
 
        public int ParallaxOffset
        {
            get { return iParallaxOffset; }
            set
            {
                iParallaxOffset = value;
                if (iParallaxOffset < 0)
                {
                    iParallaxOffset += iParallaxHeight;
                }
                if (iParallaxOffset > iParallaxHeight)
                {
                    iParallaxOffset -= iParallaxHeight;
                }
            }
        }
 
        bool drawParallax = true;
 
        public bool DrawParallax
        {
            get { return drawParallax; }
            set { drawParallax = value; }
        }
 
        public Background(ContentManager content, string sBackground, string sParallax)
        {
            _gameData = GameData.GetGameDataInstance();
            iViewportHeight = _gameData.GameScreenHeight;
            iViewportWidth = _gameData.GameScreenWidth;
            t2dBackground = content.Load<Texture2D>(sBackground);
            _gameData.BackgroundWidth = iBackgroundWidth = t2dBackground.Width;
            _gameData.BackgroundHeight = iBackgroundHeight = t2dBackground.Height;
            t2dParallax = content.Load<Texture2D>(sParallax);
            iParallaxWidth = t2dParallax.Width;
            iParallaxHeight = t2dParallax.Height;
        }
        public Background(ContentManager content, string sBackground)
        {
            _gameData = GameData.GetGameDataInstance();
            iViewportHeight = _gameData.GameScreenHeight;
            iViewportWidth = _gameData.GameScreenWidth;
            t2dBackground = content.Load<Texture2D>(sBackground);
            _gameData.BackgroundWidth = iBackgroundWidth = t2dBackground.Width;
            _gameData.BackgroundHeight = iBackgroundHeight = t2dBackground.Height;
            t2dParallax = t2dBackground;
            iParallaxWidth = t2dParallax.Width;
            iParallaxHeight = t2dParallax.Height;
            drawParallax = false;
        }
 
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(t2dBackground, new Rectangle(0, -1 * iBackgroundOffset, iViewportWidth, iBackgroundHeight), Color.White);
 
            if (iBackgroundOffset > iBackgroundHeight - iViewportHeight)
            {
                spriteBatch.Draw(t2dBackground, new Rectangle(0, (-1 * iBackgroundOffset) + iBackgroundHeight, iViewportWidth, iBackgroundHeight), Color.White);
            }
 
            if (drawParallax)
            {                
                spriteBatch.Draw(t2dParallax, new Rectangle(0, -1 * iParallaxOffset, iViewportWidth, iParallaxHeight), Color.SlateGray);                
                if (iParallaxOffset > iParallaxHeight - iViewportHeight)
                {
                    spriteBatch.Draw(t2dParallax, new Rectangle(0, (-1 * iParallaxOffset) + iParallaxHeight, iViewportWidth, iParallaxHeight), Color.SlateGray);
                }
            }
        }
    }
}

Here, iViewportWidth and iViewportHeight is game width and height. iBackgroundWidth and iBackgroundHeight is main background width and height. iParallaxWidth and iParallaxHeight is the image height laying over it.

In addition to the variables, I have BackgroundOffset and ParallalxOffset properties to set them from outside the class. You will notice that the two properties above check to see if we have gone off of either end of each texture and wrap around if necessary. If we don't do this, we can scroll top off the end of the background image.

When we use this constructor to create an instance of our Background class, we will pass in a content manager and use it to load the two textures. Our constructor will set iBackgroundWidth, iBackgroundHeight, iParallaxWidth, and iParallaxHeight to the appropriate values from the textures we loaded.

Our Draw method is passed a SpriteBatch to use, and we will assume that we are within a SpriteBatch.Begin and

SpriteBatch.End
call set.

Our first statement draws the background image, offset by the background offset:

C#
spriteBatch.Draw(t2dBackground, new Rectangle(0, -1 * iBackgroundOffset, iViewportWidth,
iBackgroundHeight), Color.White);

When we create the destination rectangle, we set the left position to "-1 * iBackgroundOffset", which results in shifting our image to the left by a number of pixels equal to iBackgroundOffset.

This works great except when we get to the point where drawing the offset image doesn't fill our entire display. If we don't account for that, we will end up with a partially filled background and then the XNA Blue Window. This is where the next statement comes in:

C#
// If the right edge of the background panel will end within the bounds of
// the display, draw a second copy of the background at that location.
if (iBackgroundOffset > iBackgroundHeight - iViewportHeight)
{
spriteBatch.Draw(t2dBackground, new Rectangle(0, (-1 * iBackgroundOffset) + 
     iBackgroundHeight, iViewportWidth, iBackgroundHeight), Color.White);
}

First we check to see if we need to draw a second copy of the image. If so, we repeat the above draw call except that we modify the position of the second destination rectangle by adding the height of the background image to the call. This will line the second copy of the image up to start at exactly the point where the first copy ended. We will never need to draw more than two of these images to fill the screen, since the height of the background image is greater than the height of the screen. The rest of our draw function does exactly the same process with the parallax star overlay after checking to see if we should be drawing it. It uses the same offsetting and second copy drawing logic as the background. Next, we'll need to actually initialize the background by running its constructor. In the LoadContent method of our game, let’s add the following code:

C#
background = new Background(Content,@"Textures\PrimaryBackground",@"Textures\ParallaxStars");

Here, we call the Background object's constructor and pass it our Content Manager object, along with the asset names of the two textures we will be using.

That handles the setup, so all that is left is to draw our background. Scroll down to the Draw method and add the following line Before the draw code, but inside the spriteBatch.Begin and spriteBatch.End block:
C#
background.Draw(spriteBatch);

Player (SpaceShip)

I had one of the biggest challenge to provide game support for all kind of Windows 8 devices (touch and non touch based systems). Ultrabook have awesome touch drivers which works with Windows 8 in elegant manner and user does not need to change even single piece of code. Below is code for player class.

C#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace SkyWar
{
    class SpaceShip
    {
        GameData _gameData;
        AnimatedSprite asSprite;
        Texture2D t2dSpaceShip;
        SoundEffectInstance spaceShipDead;
        bool sActive;        
        public bool IsSpaceShipActive { get { return sActive; } private set { sActive = value; } }
        int iX = 604;
        int iY = 260;
        int iFacing = 1;
        bool bThrusting = false;
        int iScrollRate = 0;
        int iShipAccelerationRate = 1;
        int iShipVerticalMoveRate = 3;
        float fSpeedChangeCount = 0.0f;
        float fSpeedChangeDelay = 0.1f;
        float fVerticalChangeCount = 0.0f;
        float fVerticalChangeDelay = 0.01f;
        TimeSpan _deathDelay, _deathCountDelay;
 
        float[] fFireRateDelay = new float[3] { 0.15f, 0.1f, 0.05f };
        float fSuperBombDelayTimer = 2f;
 
        int iMaxSuperBombs = 5;
        int iMaxWeaponLevel = 1;
        int iShipMaxFireRate = 2;
        int iMaxAccelerationModifier = 5;
 
        int iSuperBombs = 0;
        int iWeaponLevel = 0;
        int iWeaponFireRate = 0;
        int iAccelerationModifier = 1;
 
        public int X
        {
            get { return iX; }
            set { iX = value; }
        }
 
        public int Y
        {
            get { return iY; }
            set { iY = value; }
        }
 
        public int Facing
        {
            get { return iFacing; }
            set { iFacing = value; }
        }
 
        public bool Thrusting
        {
            get { return bThrusting; }
            set { bThrusting = value; }
        }
 
        public int ScrollRate
        {
            get { return iScrollRate; }
            set { iScrollRate = value; }
        }
 
        public int AccelerationRate
        {
            get { return iShipAccelerationRate; }
            set { iShipAccelerationRate = value; }
        }
 
        public int VerticalMovementRate
        {
            get { return iShipVerticalMoveRate; }
            set { iShipVerticalMoveRate = value; }
        }
 
        public float SpeedChangeCount
        {
            get { return fSpeedChangeCount; }
            set { fSpeedChangeCount = value; }
        }
 
        public float SpeedChangeDelay
        {
            get { return fSpeedChangeDelay; }
            set { fSpeedChangeDelay = value; }
        }
 
        public float VerticalChangeCount
        {
            get { return fVerticalChangeCount; }
            set { fVerticalChangeCount = value; }
        }
 
        public float VerticalChangeDelay
        {
            get { return fVerticalChangeDelay; }
            set { fVerticalChangeDelay = value; }
        }
        public int ShipWidth { get; private set; }
        public int ShipHeight { get; private set; }
 
        public Rectangle BoundingBox
        {
            get { return new Rectangle(iX, iY, _gameData.SpaceShipWidth, _gameData.SpaceShipHeight); }
        }
 
        public int SuperBombs
        {
            get { return iSuperBombs; }
            set
            {
                iSuperBombs = (int)MathHelper.Clamp(value,
                  0, iMaxSuperBombs);
            }
        }
 
        public int FireRate
        {
            get { return iWeaponFireRate; }
            set
            {
                iWeaponFireRate = (int)MathHelper.Clamp(value,
                  0, iShipMaxFireRate);
            }
        }
 
        public float FireDelay
        {
            get { return fFireRateDelay[iWeaponFireRate]; }
        }
 
        public int WeaponLevel
        {
            get { return iWeaponLevel; }
            set
            {
                iWeaponLevel = (int)MathHelper.Clamp(value,
                  0, iMaxWeaponLevel);
            }
        }
 
        public float SuperBombDelay
        {
            get { return fSuperBombDelayTimer; }
        }
 
        public int AccelerationBonus
        {
            get { return iAccelerationModifier; }
            set
            {
                iAccelerationModifier = (int)MathHelper.Clamp(value,
                  1, iMaxAccelerationModifier);
            }
        }
 
        public SpaceShip(ContentManager content, string texture)
        {
            _gameData = GameData.GetGameDataInstance();
            t2dSpaceShip = content.Load<texture2d>(texture);
            asSprite = new AnimatedSprite(t2dSpaceShip, 0, 0, t2dSpaceShip.Width, t2dSpaceShip.Height, 1);
            ShipWidth = t2dSpaceShip.Width;
            ShipHeight = t2dSpaceShip.Height;
            iX = _gameData.GameScreenWidth / 2 - t2dSpaceShip.Width / 2;            
            iY = _gameData.GameScreenHeight / 2 + _gameData.GameScreenHeight / 4 + _gameData.GameScreenHeight / 6;
            asSprite.IsAnimating = false;
            sActive = true;             
            spaceShipDead = _gameData.AudioLibarary.BossDead;
        }
 
        public void ResetSpaceShip()
        {
            iX = _gameData.GameScreenWidth / 2 - t2dSpaceShip.Width / 2; 
            iY = _gameData.GameScreenHeight / 2 + _gameData.GameScreenHeight / 4 + _gameData.GameScreenHeight / 6; 
            iAccelerationModifier = 1;
            iWeaponFireRate = 0;
            iWeaponLevel = 0;
            iSuperBombs = 0;//(int)MathHelper.Max(1, iSuperBombs);
            iScrollRate = 0;
        }
 
        public void PlayerHit(int power = 1)
        {            
            _gameData.PlayerHealth -= 20;
            if (_gameData.PlayerHealth == 0)
            {
                spaceShipDead.Play();
                _gameData.PlayerHealth = 0;
                sActive = false;
                _gameData.PlayerLives--;
                if (_gameData.PlayerLives == 0)
                   _gameData.IsGameOver = true;
                else
                {
                    _deathDelay = TimeSpan.FromSeconds(3);
                    _deathCountDelay = TimeSpan.Zero;
                }
            }
            else if (_gameData.PlayerHealth < 0)
                _gameData.PlayerHealth = 0;
        }
 
        public void Update(GameTime gametime)
        {
            if ((!sActive) && _deathDelay > TimeSpan.Zero)
            {
                if ((_deathCountDelay += gametime.ElapsedGameTime) > _deathDelay)
                {
                    sActive = true;
                    _gameData.PlayerHealth = 100;
                    ResetSpaceShip();
                    _deathDelay = TimeSpan.Zero;
                }
            }
            
        }
 
        public void Draw(SpriteBatch sb)
        {
            if (sActive)
                asSprite.Draw(sb, iX, iY, false);
        }
    }
}

AnimatedSprite will, of course, be used to house the image above. We will be using the bAnimating feature of our animated sprite class to prevent it from animating automatically so that we can control which frame of the ship "animation" is displayed.

iX and iY determine the location of the ship on the screen. In the game we will be producing for this tutorial series, the ship will never leave the center of the screen (horizontally) but will move freely vertically.

iFacing determines which direction the player is currently facing. 0=Right, 1=Left.

bThrusting is set to true when the player is actively moving in a direction (as opposed to coasting in that direction).

The next few variables are all related to how our ship/screen moves. We'll get into more detail below when we add the ship to the screen and handle movement.

iScrollRate determines the speed and direction that the ship is actually moving (this is not related to the Facing, as it is possible to be moving one direction while facing the other). Positive values indicate rightward movement, negative values leftwards. The magnitude of the number determines the number of pixels per update frame that the screen will scroll.

iShipAccelerationRate sets how fast iScrollRate can change. A value of 1 means that every time the speed changes it changes by 1.

iShipVerticalMovementRate is the number of pixels the ship moves vertically when the player presses up or down on the gamepad/keyboard.

fSpeedChangeCount and fSpeedChangeDelay determine how rapidly iShipAccelerationRate can be applied. fSpeedChangeCount accumulates the time since the last speed change. When it is greater than fSpeedChangeDelay, the speed is allowed to change and will be reset to 0 if it does.

The BoundingBox property simply returns a new rectangle based on the position and size of our ship. We will be adding similar properties to other objects in our game, some of them more complex than this to account for objects position within the "world map".

fBoardUpdateDelay and fBoardUpdateInterval will be used to control how fast the screen can scroll overall (if we rely simply on calls to Update we can potentially get inconsistent speeds).

Constructor and Draw method of class is fairly simple; we are passing texture in constructor and SpriteBatch in Draw method.

We can now integrate it in our game. This is how we can create a player object by passing content manager and texture path.

C#
spaceShipObj = new SpaceShip(content, "images/plane");

Now we need to update player ship based on sensors (when user tilt the ultrabook or use keyboard player plan should move). So we will write update code in update method of game.

C#
spaceShipObj.Update(gameTime);
float gameElapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

if (spaceShipObj.IsSpaceShipActive)
{
    float movement = 0.0f;
    if (System.Math.Abs(acceloVelocity.X) > accelThreshold)
    {
        movement = acceloVelocity.X * acceleoSpeed;
    }
    //update spaceship postision
    spaceShipObj.X += (int)movement;
    spaceShipObj.Update(gameTime);

    if (spaceShipObj.X <= iPlayAreaLeft)
        spaceShipObj.X = iPlayAreaLeft;

    if (spaceShipObj.X + spaceShipObj.ShipWidth >= iPlayAreaRight)
        spaceShipObj.X = iPlayAreaRight - spaceShipObj.ShipWidth;

    spaceShipObj.SpeedChangeCount += gameElapsedTime;
    spaceShipObj.VerticalChangeCount += gameElapsedTime;
    //check if gas or fire is touched or not
    TouchCollection touchCollection = TouchPanel.GetState();
    foreach (TouchLocation tl in touchCollection)
    {
        if ((tl.State == TouchLocationState.Pressed) || (tl.State == TouchLocationState.Moved))
        {
            //check gas
            if (GasTouchBound.Intersects(new Rectangle((int)tl.Position.X, (int)tl.Position.Y, 80, 80)))
            {
                _touchGasFlag = true;
                if (spaceShipObj.SpeedChangeCount > spaceShipObj.SpeedChangeDelay)
                    CheckVerticalMovementKeys(Keyboard.GetState(), GamePad.GetState(PlayerIndex.One));
            }
            else
                UpdateShipSpeed();

            //check fire
            if (FireTouchBound.Intersects(new Rectangle((int)tl.Position.X, (int)tl.Position.Y, 80, 80)))
            {
                CheckOtherKeys();
            }
        }
    }
    if (touchCollection.Count == 0)
    {
        UpdateShipSpeed();
    }
    //give keyboard support
    KeyboardState keyboardState = Keyboard.GetState();
    if (spaceShipObj.SpeedChangeCount > spaceShipObj.SpeedChangeDelay)
    {
        if (keyboardState.IsKeyDown(Keys.Up))
            CheckVerticalMovementKeys(Keyboard.GetState(), GamePad.GetState(PlayerIndex.One));
        else
            UpdateShipSpeed();
    }
    if (spaceShipObj.VerticalChangeCount > spaceShipObj.VerticalChangeDelay)
    {
        CheckHorizontalMovementKeys(keyboardState, GamePad.GetState(PlayerIndex.One));
    }
    if (keyboardState.IsKeyDown(Keys.Space))
        CheckOtherKeys();
}

Supporting Methods of Update Method

protected void CheckVerticalMovementKeys(KeyboardState ksKeys, GamePadState gsPad)
        {
            bool bResetTimer = false;

            spaceShipObj.Thrusting = false;
            
            if (spaceShipObj.ScrollRate > -iMaxHorizontalSpeed)
            {
                spaceShipObj.ScrollRate -= spaceShipObj.AccelerationRate;
                if (spaceShipObj.ScrollRate < -iMaxHorizontalSpeed)
                    spaceShipObj.ScrollRate = -iMaxHorizontalSpeed;
                bResetTimer = true;
            }
            spaceShipObj.Thrusting = true;
            spaceShipObj.Facing = 1;
            if (bResetTimer)
                spaceShipObj.SpeedChangeCount = 0.0f;
        }
        protected void UpdateShipSpeed()
        {
            if (_touchGasFlag)
            {
                spaceShipObj.ScrollRate = spaceShipObj.ScrollRate + 1;
                if (spaceShipObj.ScrollRate >= iMinHorizontalSpeen)
                    spaceShipObj.ScrollRate = iMinHorizontalSpeen;
            }
        }

        protected void CheckHorizontalMovementKeys(KeyboardState ksKeys, GamePadState gsPad)
        {
            if (ksKeys.IsKeyDown(Keys.Left))
            {
                spaceShipObj.X -= 5;
                if (spaceShipObj.X < iPlayAreaLeft)
                    spaceShipObj.X = iPlayAreaLeft;
            }
            if (ksKeys.IsKeyDown(Keys.Right))
            {
                spaceShipObj.X += 5;
                if (spaceShipObj.X + spaceShipObj.ShipWidth > iPlayAreaRight)
                    spaceShipObj.X = iPlayAreaRight - spaceShipObj.ShipWidth;
            }            
        }

The above code is for updating player spaceship when player touch, tilt ultrabook or if press navigation arrow keys. Touch collection object will give you all touch location user have touched. We can loop over it and quickly identify as it interests the button on left and right or not and make appropriate action accordingly. If TouchCollection count is zero and if keyboard key is pressed then we again move player. In both the cases, we will update player speed move him forward in world map.

Sprite , Alien and AlienCompnenet

Aliens are bad guys who will try to protest player by proceed further in stage. Alien can fire bullets. We can create alien and boss(dragon) in same class or we can follow OOPS by creating a class which will hold all generic properties of alien and further we can inherit that and create two different classes – Alien and BossAlien. This is what I did in game. I created a generic class sprite, which can be reused for any object. It holds most generic properties. Below is code for sprite class.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SkyWar
{
    class Sprite
    {
        public Texture2D Texture { get; private set; }
        public Vector2 Position;
        public Vector2 Velocity;
        public Vector2 Origin;
        public bool Active = true;
        public float Scale = 1;
        public float Rotation;
        public float ZLayer;
        public Color Color = Color.White;

        public int TotalFrames { get; private set; }
        public TimeSpan AnimationInterval;
        public bool OneShotAnimation;
        public bool DeactivateOnAnimationOver = true;

        private Rectangle[] _rects;
        private int _currentFrame;
        private TimeSpan _animElapsed;

        public int FrameWidth { get { return _rects == null ? Texture.Width : _rects[0].Width; } }
        public int FrameHeight { get { return _rects == null ? Texture.Height : _rects[0].Height; } }

        public Sprite(Texture2D texture, Rectangle? firstRect = null, int frames = 1, bool horizontal = true, int space = 0)
        {
            Texture = texture;
            TotalFrames = frames;
            if (firstRect != null)
            {
                _rects = new Rectangle[frames];
                Rectangle first = (Rectangle)firstRect;
                for (int i = 0; i < frames; i++)
                    _rects[i] = new Rectangle(first.Left + (horizontal ? (first.Width + space) * i : 0),
                        first.Top + (horizontal ? 0 : (first.Height + space) * i), first.Width, first.Height);
            }
            else
                _rects = new Rectangle[] { new Rectangle(0, 0, texture.Width, texture.Height) };
        }

        public virtual void Update(GameTime gameTime)
        {
            if (Active)
            {
                if (TotalFrames > 1 && (_animElapsed += gameTime.ElapsedGameTime) > AnimationInterval)
                {
                    if (++_currentFrame == TotalFrames)
                    {
                        _currentFrame = 0;
                        if (OneShotAnimation)
                        {
                            _currentFrame = TotalFrames - 1;
                            if (DeactivateOnAnimationOver)
                                Active = false;
                        }
                    }
                    _animElapsed -= AnimationInterval;
                }
                Position += Velocity;
            }
        }

        public virtual void Draw(GameTime gameTime, SpriteBatch batch)
        {
            if (Active)
            {
                batch.Draw(Texture, Position, _rects == null ? null : (Rectangle?)_rects[_currentFrame],
                    Color, Rotation, Origin, Scale, SpriteEffects.None, ZLayer);
            }
        }

        public int ActualWidth
        {
            get { return (int)(FrameWidth * Scale); }
        }

        public int ActualHeight
        {
            get { return (int)(FrameHeight * Scale); }
        }

        public Rectangle BoundingRect
        {
            get
            {
                return new Rectangle(
                    (int)(Position.X - Origin.X * Scale), (int)(Position.Y - Origin.Y * Scale),
                    (int)(_rects[0].Width * Scale), (int)(_rects[0].Height * Scale));
            }
        }

        public virtual bool Collide(Sprite other)
        {
            return Active && other.Active && BoundingRect.Intersects(other.BoundingRect);
        }
        public virtual bool Collide(Rectangle other)
        {
            return Active && BoundingRect.Intersects(other);
        }
    }
}

This will check collision of sprite with respect of other sprite or a rectangle. Rectangle class will have a inbuilt method called interest which returns true of false if object interest. The first thing we’ll do is create a custom Alien class, that extends the Sprite class and adds some specific alien attributes. This will make our lives a little bit easier when we managed all those aliens. Add a new class named Alien and derive it from Sprite. Please find below code –

C#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SkyWar
{
    class Alien : Sprite

    {
        public readonly bool IsBoss;
        public readonly AlienType Type;
        GameData _gameData = null;
        public int HitPoints;
        SoundEffectInstance alienDead;

        public Alien(AlienType type, bool isBoss)
            : base(type.Texture, type.FirstFrame, type.Frames, type.IsHorizontal, type.Space)
        {
            IsBoss = isBoss;
            Type = type;
            Origin.X = type.FirstFrame.Value.Width / 2;
            AnimationInterval = type.AnimationRate;
            _gameData = GameData.GetGameDataInstance();
            HitPoints = type.Bullets;
            alienDead = _gameData.AudioLibarary.AlienDead;
        }

        public override void Update(GameTime gameTime)
        {
            if (Active && !_isDead)
            {
                if (Position.X < 0 || Position.X > _gameData.GameScreenWidth)
                    Velocity.X = -Velocity.X;
                if (Position.Y > _gameData.GameScreenHeight + 40)
                {
                    Active = false;
                    if (OutOfBounds != null)
                        OutOfBounds(this);
                }
            }
            else if (_isDead)
            {
                if (Color.R > 4)
                    Color.R -= 4;
                else
                {
                    Active = false;
                }
                return;
            }
            base.Update(gameTime);
        }

        public event Action<Alien> OutOfBounds;
        public event Action<Alien> Killed;

        private bool _isDead;

        public bool IsDead { get { return _isDead; } }
        public virtual void Hit(int power = 1)
        {
            if ((HitPoints -= power) <= 0)
            {
                alienDead.Play();
                // alien killed, start death sequence
                _isDead = true;
                Color = new Color(255, 0, 0);
                if (Killed != null)
                    Killed(this);
            }
        }
    }
}

The code is simple as we will just Draw and Update alien, if it’s in screen it will be active and else we will make active false. We will update it possible and velocity to update it. But still work in not done yet. Now we’ll create an array of Alien references in the AlienComponent class. Which will manage set of aliens on screen. In this case, however, as every alien may look different, we may have to create aliens dynamically as needed. Another possible approach would be to allow the Texture of a sprite to be changed after the sprite has been initialized. Currently, the Texture of a sprite is passed via the constructor, and is read only since.

C#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SkyWar
{
    class AliensComponent
    {
        GameplayScreen _gameScreen;
        GameData _gameData;
        BossAlien bossAlien = null;

        Alien[] _aliens = new Alien[40];        
        int _currentAlienIndex;
        Random _rnd = new Random();
        TimeSpan _elapsedTime;
        int _currentLiveAliens, _totalKilledAliens;
        private TimeSpan _delayCount, _delay;
        public TimeSpan Delay
        {
            get { return _delay; }
            set
            {
                _delay = value;
                _delayCount = TimeSpan.Zero;
            }
        }
        internal IEnumerable<Alien> Aliens { get { return _aliens; } }
        public BossAlien BossAlien { get { return bossAlien; } }
        // alien bullets
        Sprite[] _bullets = new Sprite[40];
        //SoundEffectInstance[] _bulletSoundInst = new SoundEffectInstance[24];
        //SoundEffect _bulletSound;
        Texture2D _texture;
        int _totalBullets, _currentBullet;

        public AliensComponent(GameplayScreen gpScreen, ContentManager content)
        {
            _gameScreen = gpScreen;
            _gameData = GameData.GetGameDataInstance();
            _texture = content.Load<Texture2D>("images/laser1");
        }

        public void Update(GameTime gameTime)
        {
            // get current level data
            LevelData data = _gameScreen.GetCurrentLevelData();

            if (data == null)
            {
                _gameData.IsGameCompleted = true;
                return;
            }
            foreach (var bullet in _bullets)
            {
                if (bullet != null && bullet.Active)
                {
                    bullet.Update(gameTime);
                    if (bullet.Position.Y > _gameData.GameScreenHeight + 30)
                    {
                        bullet.Active = false;
                        _totalBullets--;
                    }
                    else if (bullet.Collide(_gameScreen.SpaceShipComponent.BoundingBox))
                    {
                        _gameScreen.SpaceShipComponent.PlayerHit();
                        bullet.Active = false;
                        _totalBullets--;
                    }
                }
            }

            if (Delay > TimeSpan.Zero)
            {
                if ((_delayCount += gameTime.ElapsedGameTime) >= Delay)
                    Delay = TimeSpan.Zero;
            }
            
            if (_delay == TimeSpan.Zero && (_elapsedTime += gameTime.ElapsedGameTime) > data.AlienGenerationTime)
            {
                _elapsedTime = TimeSpan.Zero;
                var alien = CreateAlien(data);
                if (alien != null)
                {
                    _currentLiveAliens++;
                    while (_aliens[_currentAlienIndex] != null && _aliens[_currentAlienIndex].Active)
                        _currentAlienIndex = (_currentAlienIndex + 1) % _aliens.Length;
                    _aliens[_currentAlienIndex] = alien;
                }
            }

            foreach (var alien in _aliens)
                if (alien != null && alien.Active)
                {
                    alien.Update(gameTime);
                    if (!alien.IsDead && _gameScreen.SpaceShipComponent.IsSpaceShipActive && alien.Collide(_gameScreen.SpaceShipComponent.BoundingBox))
                    {
                        alien.Hit();
                        _gameScreen.SpaceShipComponent.PlayerHit();
                        if (_totalKilledAliens < data.TotalAliensToFinish)
                            _totalKilledAliens = 0;
                    }
                    if (_delay == TimeSpan.Zero && _totalBullets < data.MaxAlienBullets && _rnd.Next(100) < data.FireChance)
                        CreateBullet(alien);
                }

            if (bossAlien != null && bossAlien.Active && (!bossAlien.IsDead))
            {
                bossAlien.Update(gameTime);
                if (!bossAlien.IsDead && _gameScreen.SpaceShipComponent.IsSpaceShipActive && bossAlien.Collide(_gameScreen.SpaceShipComponent.BoundingBox))
                {
                    bossAlien.Hit();
                    _gameScreen.SpaceShipComponent.PlayerHit();
                }
                if (_delay == TimeSpan.Zero && _totalBullets < data.MaxAlienBullets && _rnd.Next(100) < data.FireChance)
                    CreateBullet(bossAlien);
            }
        }

        private Alien CreateAlien(LevelData data)
        {
            if (_currentLiveAliens > data.MaxActiveAliens) return null;

            if (_totalKilledAliens == data.TotalAliensToFinish)
            {
                _totalKilledAliens++;
                bossAlien = CreateBoss(data);
                bossAlien.Killed += delegate
                {
                    _currentLiveAliens--;
                    _gameScreen.InitLevel(_gameScreen.Level + 1);
                    _totalKilledAliens = 0;
                };
                return null;
            }

            int chance = 0;
            int value = _rnd.Next(100);
            foreach (var sd in data.SelectionData)
            {
                if (value < sd.Chance + chance)
                {
                    Alien alien = new Alien(sd.Alien, false);
                    alien.Position = new Vector2((float)(_rnd.NextDouble() * _gameData.GameScreenWidth), -50);
                    alien.Velocity = new Vector2((float)(_rnd.NextDouble() * alien.Type.MaxXSpeed * 2 - alien.Type.MaxXSpeed), (float)(_rnd.NextDouble() * alien.Type.MaxYSpeed + 2));
                    alien.Scale = 0.7f;
                    _aliens[_currentAlienIndex] = alien;
                    alien.OutOfBounds += a =>
                    {
                        _currentLiveAliens--;
                    };
                    alien.Killed += a =>
                    {
                        _currentLiveAliens--;
                        _totalKilledAliens++;
                    };
                    return alien;
                }
                chance += sd.Chance;
            }
            return null;
        }
        private BossAlien CreateBoss(LevelData data)
        {
            BossAlien alien = new BossAlien(data.Boss);
            alien.Position = new Vector2((float)(_rnd.NextDouble() * _gameData.GameScreenWidth), 10);
            alien.Velocity = new Vector2((float)(_rnd.NextDouble() * alien.Type.MaxXSpeed * 4 - alien.Type.MaxXSpeed), 0.0f);
            alien.Scale = 2.3f;
            return alien;
        }

        private void CreateBullet(Sprite alien)
        {
            while (_bullets[_currentBullet] != null && _bullets[_currentBullet].Active)
                _currentBullet = (_currentBullet + 1) % _bullets.Length;
            Sprite bullet = _bullets[_currentBullet];
            if (bullet == null)
            {
                bullet = new Sprite(_texture, new Rectangle(0, 0, 16, 46));
                bullet.Scale = 0.5f;
                bullet.ZLayer = .5f;
                //bullet.Color = Color.White;
                _bullets[_currentBullet] = bullet;
                //_bulletSoundInst[_currentBullet] = _bulletSound.CreateInstance();
                //_bulletSoundInst[_currentBullet].Volume = .7f;
            }
            bullet.Position = alien.Position + new Vector2(-2, alien.ActualHeight / 2);
            bullet.Velocity.Y = alien.Velocity.Y > 0 ? 1.8f * alien.Velocity.Y : _rnd.Next(10) + 3;
            bullet.Active = true;
            //_bulletSoundInst[_currentBullet].Play();
            _totalBullets++;
        }

        public void Draw(GameTime gameTime, SpriteBatch spbatch)
        {
            foreach (var alien in _aliens)
                if (alien != null && alien.Active)
                    alien.Draw(gameTime, spbatch);

            foreach (var bullet in _bullets)
                if (bullet != null)
                    bullet.Draw(gameTime, spbatch);

            if (bossAlien != null && bossAlien.Active && (!bossAlien.IsDead))
                bossAlien.Draw(gameTime, spbatch);
        }
    }
}

The class is very simple we have Alien[] _aliens = new Alien[40] here in class and we will dynamically create aliens and move it on screen. This class also take care of _currentLiveAliens, _totalKilledAliens to update score and level. There is also a property for looping over alien array.

C#
internal IEnumerable<Alien> Aliens { get { return _aliens; } }

Its Update method is most important one which is responsible for all action. It waits for alien generation period and generates new alien new aliens. If alien is killed it will update count and score. If total alien killed will reach to level count, it will load new level.

Let’s discuss now game stage planning, score, how many aliens need to be killed to proceed further, how many bullets alien can fire, alien generation time etc stuff.

Game Levels and Difficulty Level Control

I tried to make the game dynamic as much as possible. I tried controlling game difficulty, total number of aliens need to be killed, total bullets alien can fire, total aliens active on screen etc using xml file. So tomorrow if we feel game is too easy or too difficult, we just need to modify xml and rest of game will be untouched.

Below xml is used for configuration of different aliens of game. As you can it clearly specify what is the name of alien, how many points it will provide to player, what is the max x and y axis moving speed, then texture path, how many frames are present in texture and how many bullets required to kill this alien.

XML
<?xml version="1.0" encoding="utf-8" ?>

<AlienTypes>

<AlienType Name="alien1" Score="10" MaxXSpeed="2" MaxYSpeed="3" 
Texture="Images/alien1" Frames="1" Space="1" FirstFrame="0,0,50,50" Bullets="1" 
AnimationRate="500" />

<AlienType Name="alien2" Score="15" MaxXSpeed="3" MaxYSpeed="4" 
Texture="Images/alien2" Frames="1" Space="1" FirstFrame="0,0,42,67" Bullets="1" 
AnimationRate="400" />

<AlienType Name="alien3" Score="20" MaxXSpeed="4" MaxYSpeed="5" 
Texture="Images/alien3" Frames="1" Space="1" FirstFrame="0,0,41,64" Bullets="1" 
AnimationRate="400" />

<AlienType Name="alien4" Score="25" MaxXSpeed="4" MaxYSpeed="4" 
Texture="Images/alien4" Frames="1" Space="1" FirstFrame="0,0,40,52" Bullets="1" 
AnimationRate="400" />

</AlienTypes>

Same way to control level we have level xml. As you can see it clearly specify how many levels are there, in which level how many maximum active aliens should be present on screen, how many aliens player need to kill to proceed to next level, which kind of dragon or boss enemy will come in end of stage, what is new alien generation time, how many bullets player need to hit to boos in order to kill him.

XML
<?xml version="1.0" encoding="utf-8" ?>

<Levels>

<Level Number="1" MaxActiveAliens="12" TotalAliensToFinish="10" 
Boss="boss1" AlienGenerationTime="1200" ChangeDirChance="2" FireChance="7" 
MaxAlienBullets="7">

<AlienTypes>

<AlienType Name="alien1" Chance="25" />

<AlienType Name="alien2" Chance="20" />

<AlienType Name="alien3" Chance="20" />

<AlienType Name="alien4" Chance="15" />

<AlienType Name="alien5" Chance="25" />

</AlienTypes>

</Level>

<Level Number="2" MaxActiveAliens="16" TotalAliensToFinish="14" 
Boss="boss2" AlienGenerationTime="1000" ChangeDirChance="2" FireChance="14" 
MaxAlienBullets="10">

<AlienTypes>

<AlienType Name="alien1" Chance="20" />

<AlienType Name="alien2" Chance="20" />

<AlienType Name="alien3" Chance="20" />

<AlienType Name="alien4" Chance="15" />

<AlienType Name="alien5" Chance="25" />

<AlienType Name="alien6" Chance="25" />

</AlienTypes>

</Level>

</Levels>

I have uploaded complete source code but will not be able to upload textures as its size is way above then code project limit. When game studio convert textures into .xnb file, its size increase a lot.

You can find by old App Innovation article here.

Hope this will give you complete guideline of game. Enjoy Gaming.

Point of Interest

I won first round and after receiving ultrabook, I created application on time and uploaded in Microsoft app store. But it was stuck in validation stage only and still pending (as I fall sick so could not proceed further in completion).

I learned a lot from Intel AppUp competition. I tried something new first time (MonoGame).

References

Before I finish this article, I would like to add some references to article which helped me learning MonoGame and XNA.

Books : XNA 3.0 Game Programming from Novice to Professional, XNA Game Programming Recepies

Links :

License

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


Written By
Software Developer eMids Technologies Pvt Ltd
India India
2+ year of experience in Information Technology with the extensive exposure of Treasury & Capital, Health Care and ERP domain.

Area of interest - Multithreading, Files, WPF, WCF, Jquery, Mvc, Sql Server, Perceptual programming

Heavily Worked on desktop application ,Web application and WCF Services.

Also worked on small games application for learning purpose.

Latest interest - Windows 8 , Perceptual Programming, Windows Phone, WCF etc

Comments and Discussions

 
QuestionApp is live on windows 8 app store Pin
sandeepkumar.sgnr16-Jan-13 20:12
professionalsandeepkumar.sgnr16-Jan-13 20:12 
QuestionBig 5+ Pin
BigWCat8-Jan-13 5:01
professionalBigWCat8-Jan-13 5:01 
Awesome!!!! Smile | :)
AnswerRe: Big 5+ Pin
sandeepkumar.sgnr8-Jan-13 18:52
professionalsandeepkumar.sgnr8-Jan-13 18:52 
QuestionI have wrote the same article again with proper images Pin
sandeepkumar.sgnr7-Jan-13 22:18
professionalsandeepkumar.sgnr7-Jan-13 22:18 
QuestionRegarding Article Images Pin
Ranjan.D7-Jan-13 4:59
professionalRanjan.D7-Jan-13 4:59 
AnswerRe: Regarding Article Images Pin
sandeepkumar.sgnr8-Jan-13 18:53
professionalsandeepkumar.sgnr8-Jan-13 18:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.