Inledning
Denna artikel är en direkt fortsättning på artikeln MonoAsteroids - Del 4 . Nu är det dags att färdigställa spelet och göra det "klart".
Artikeln är mer ett stöd till den videogenomgång som mer steg för steg går igenom alla moment. Videogenomgången hittar du i slutet av artikeln.
SpriteFont
I denna sista och avslutande del så behövs en ny textur som ska fungera som typsnitt. Du hittar den här nedan. Glöm ej bara att sätta Processor till "Font Texture" i Pipeline-verktyget.
Ändrade filer
Globals.cs
using Microsoft.Xna.Framework;
namespace MonoAsteroids
{
class Globals
{
public static int ScreenWidth = 1280;
public static int ScreenHeight = 720;
public static Rectangle GameArea
{
get
{
return new Rectangle(-80, -80, ScreenWidth + 160, ScreenHeight + 160);
}
}
public static Rectangle RespawnArea
{
get
{
return new Rectangle((int)CenterScreen.X - 200, (int)CenterScreen.Y - 200, 400, 400);
}
}
public static Vector2 CenterScreen
{
get { return new Vector2(ScreenWidth / 2, ScreenHeight / 2); }
}
}
}
GameObjectManager.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using MonoAsteroids.Messanges;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MonoAsteroids
{
class GameObjectManager : DrawableGameComponent
{
Random rnd = new Random();
List<Meteor> meteors = new List<Meteor>();
Texture2D meteorBigTexture;
Texture2D meteorMediumTexture;
Texture2D meteorSmallTexture;
List<Shot> shots = new List<Shot>();
Texture2D laserTexture;
SoundEffect laserSound;
SoundEffect explosionSound;
Texture2D explosionTexture;
List<Explosion> explosions = new List<Explosion>();
public GameObjectManager(Game game) : base(game)
{
}
public override void Initialize()
{
Messenger.Instance.Register<AddShotMessage>(this, AddShotMessageCallback);
base.Initialize();
}
public void Reset()
{
meteors.Clear();
shots.Clear();
explosions.Clear();
while (meteors.Count < 10)
{
var angle = rnd.Next() * MathHelper.TwoPi;
var m = new Meteor(MeteorType.Big)
{
Position = new Vector2(Globals.GameArea.Left + (float)rnd.NextDouble() * Globals.GameArea.Width,
Globals.GameArea.Top + (float)rnd.NextDouble() * Globals.GameArea.Height),
Rotation = angle,
Speed = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * rnd.Next(20, 60) / 30.0f
};
if (!Globals.RespawnArea.Contains(m.Position))
meteors.Add(m);
}
}
private void AddShotMessageCallback(AddShotMessage msg)
{
shots.Add(msg.Shot);
laserSound.Play();
}
protected override void LoadContent()
{
laserTexture = Game.Content.Load<Texture2D>("laser");
meteorBigTexture = Game.Content.Load<Texture2D>("meteorBrown_big4");
meteorMediumTexture = Game.Content.Load<Texture2D>("meteorBrown_med1");
meteorSmallTexture = Game.Content.Load<Texture2D>("meteorBrown_tiny1");
laserSound = Game.Content.Load<SoundEffect>("laserSound");
explosionSound = Game.Content.Load<SoundEffect>("explosionSound");
explosionTexture = Game.Content.Load<Texture2D>("explosion");
base.LoadContent();
}
public void CheckPlayerCollision(Player playerComponent)
{
var collidingMeteor = meteors.FirstOrDefault(m => m.CollidesWith(playerComponent));
if (collidingMeteor != null)
{
explosionSound.Play(collidingMeteor.ExplosionScale, 0.0f, 0.0f);
playerComponent.Lifes--;
meteors.Remove(collidingMeteor);
explosions.Add(new Explosion()
{
Position = collidingMeteor.Position,
Rotation = collidingMeteor.Rotation,
Scale = collidingMeteor.ExplosionScale
});
if (playerComponent.Lifes <= 0)
Messenger.Instance.Send(new GameStateChangedMessage() { NewState = GameState.Dead });
}
}
public override void Update(GameTime gameTime)
{
foreach (Shot shot in shots)
{
shot.Update(gameTime);
Meteor meteor = meteors.FirstOrDefault(m => m.CollidesWith(shot));
if (meteor != null)
{
meteors.Remove(meteor);
meteors.AddRange(Meteor.BreakMeteor(meteor));
explosions.Add(new Explosion()
{
Position = meteor.Position,
Scale = meteor.ExplosionScale
});
shot.IsDead = true;
explosionSound.Play(0.7f, 0f, 0f);
}
}
foreach (Explosion explosion in explosions)
explosion.Update(gameTime);
foreach (Meteor metor in meteors)
metor.Update(gameTime);
shots.RemoveAll(s => s.IsDead || !Globals.GameArea.Contains(s.Position));
explosions.RemoveAll(e => e.IsDead);
if (meteors.Count == 0)
Messenger.Instance.Send(new GameStateChangedMessage() { NewState = GameState.Won });
base.Update(gameTime);
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Shot s in shots)
{
spriteBatch.Draw(laserTexture, s.Position, null, Color.White, s.Rotation,
new Vector2(laserTexture.Width / 2, laserTexture.Height / 2), 1.0f, SpriteEffects.None, 0f);
}
foreach (Meteor meteor in meteors)
{
Texture2D meteorTexture = meteorSmallTexture;
switch (meteor.Type)
{
case MeteorType.Big: meteorTexture = meteorBigTexture; break;
case MeteorType.Medium: meteorTexture = meteorMediumTexture; break;
}
spriteBatch.Draw(meteorTexture, meteor.Position, null, Color.White, meteor.Rotation,
new Vector2(meteorTexture.Width / 2, meteorTexture.Height / 2), 1.0f, SpriteEffects.None, 0f);
}
foreach (Explosion explosion in explosions)
{
spriteBatch.Draw(explosionTexture, explosion.Position, null, explosion.Color, explosion.Rotation,
new Vector2(explosionTexture.Width / 2, explosionTexture.Height / 2), explosion.Scale, SpriteEffects.None, 0f);
}
}
}
}
MonoAsteroids.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoAsteroids.Messanges;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MonoAsteroids
{
public class MonoAsteroids : Game
{
GameState state;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D backgroundTexture;
SpriteFont fontTexture;
Player player;
GameObjectManager gameObjectManager;
KeyboardState previousKbState;
public MonoAsteroids()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferHeight = Globals.ScreenHeight;
graphics.PreferredBackBufferWidth = Globals.ScreenWidth;
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
player = new Player(this);
Components.Add(player);
gameObjectManager = new GameObjectManager(this);
Components.Add(gameObjectManager);
Messenger.Instance.Register<GameStateChangedMessage>(this, OnGameStateChangedCallback);
Messenger.Instance.Send(new GameStateChangedMessage() { NewState = GameState.GetReady });
base.Initialize();
}
private void OnGameStateChangedCallback(GameStateChangedMessage msg)
{
if (msg.NewState == state)
return;
switch(msg.NewState)
{
case GameState.GetReady:
gameObjectManager.Reset();
player.Reset();
player.Enabled = gameObjectManager.Enabled = false;
break;
case GameState.Playing:
player.Enabled = gameObjectManager.Enabled = true;
break;
case GameState.Dead:
case GameState.Won:
player.Enabled = gameObjectManager.Enabled = false;
break;
}
state = msg.NewState;
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
backgroundTexture = Content.Load<Texture2D>("background");
fontTexture = Content.Load<SpriteFont>("spriteFontBig");
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
KeyboardState kbState = Keyboard.GetState();
switch(state)
{
case GameState.Dead:
case GameState.Won:
if (kbState.IsKeyDown(Keys.Space) && previousKbState.IsKeyUp(Keys.Space))
Messenger.Instance.Send(new GameStateChangedMessage() { NewState = GameState.GetReady });
break;
case GameState.GetReady:
if (kbState.IsKeyDown(Keys.Space) && previousKbState.IsKeyUp(Keys.Space))
Messenger.Instance.Send(new GameStateChangedMessage() { NewState = GameState.Playing });
break;
case GameState.Playing:
gameObjectManager.CheckPlayerCollision(player);
break;
}
previousKbState = kbState;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
for (int y = 0; y < Globals.ScreenHeight; y += backgroundTexture.Width)
{
for (int x = 0; x < Globals.ScreenWidth; x += backgroundTexture.Width)
{
spriteBatch.Draw(backgroundTexture, new Vector2(x, y), Color.White);
}
}
gameObjectManager.Draw(spriteBatch);
player.Draw(spriteBatch);
string overlayText = null;
switch(state)
{
case GameState.GetReady:
overlayText = "Get ready! Press space!";
break;
case GameState.Dead:
overlayText = "GAME OVER!";
break;
case GameState.Won:
overlayText = "Congratulations!";
break;
}
if (!string.IsNullOrEmpty(overlayText))
{
var size = fontTexture.MeasureString(overlayText);
spriteBatch.DrawString(fontTexture, overlayText, Globals.CenterScreen - size/2.0f, Color.DarkViolet);
}
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Player.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoAsteroids.Messanges;
using System;
namespace MonoAsteroids
{
class Player : DrawableGameComponent, IGameObject
{
public bool IsDead { get; set; }
public int Lifes { get; set; }
public Vector2 Position { get; set; }
public float Radius { get; set; }
public Vector2 Speed { get; set; }
public float Rotation { get; set; }
public bool CanShoot { get { return reloadTimer == 0; } }
private Texture2D playerTexture;
private Texture2D lifeTexture;
private int reloadTimer = 0;
private Random rnd = new Random();
public Player(Game game) : base(game)
{
Radius = 30;
Reset();
}
protected override void LoadContent()
{
playerTexture = Game.Content.Load<Texture2D>("player");
lifeTexture = Game.Content.Load<Texture2D>("life");
base.LoadContent();
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(playerTexture, Position, null, Color.White, Rotation + MathHelper.PiOver2,
new Vector2(playerTexture.Width / 2, playerTexture.Height / 2), 1.0f, SpriteEffects.None, 0f);
for(int i=0; i<Lifes; i++)
{
spriteBatch.Draw(lifeTexture, new Vector2(40 + i * 50, 40), Color.White);
}
}
public override void Update(GameTime gameTime)
{
KeyboardState state = Keyboard.GetState();
if (state.IsKeyDown(Keys.Up))
Accelerate();
if (state.IsKeyDown(Keys.Left))
Rotation -= 0.05f;
else if (state.IsKeyDown(Keys.Right))
Rotation += 0.05f;
if (state.IsKeyDown(Keys.Space) && CanShoot)
Messenger.Instance.Send(new AddShotMessage() { Shot = Shoot() });
Position += Speed;
if (reloadTimer > 0)
reloadTimer--;
if (Position.X < Globals.GameArea.Left)
Position = new Vector2(Globals.GameArea.Right, Position.Y);
if(Position.X > Globals.GameArea.Right)
Position = new Vector2(Globals.GameArea.Left, Position.Y);
if (Position.Y < Globals.GameArea.Top)
Position = new Vector2(Position.X, Globals.GameArea.Bottom );
if (Position.Y > Globals.GameArea.Bottom)
Position = new Vector2(Position.X, Globals.GameArea.Top);
base.Update(gameTime);
}
public void Reset()
{
Speed = Vector2.Zero;
Lifes = 3;
Position = Globals.CenterScreen;
}
public void Accelerate()
{
Speed += new Vector2((float)Math.Cos(Rotation),
(float)Math.Sin(Rotation))* 0.10f;
if(Speed.LengthSquared() > 16)
Speed = Vector2.Normalize(Speed) * 4;
}
public Shot Shoot()
{
if (!CanShoot)
return null;
reloadTimer = 20;
return new Shot()
{
Position = Position,
Speed = Speed + 10f * new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation)),
Rotation = rnd.Next() * MathHelper.TwoPi
};
}
}
}
Videogenomgång
Övningar
Här följer lite tips/övningar på fortsatt arbete om du vill utveckla spelet vidare!
Meteor HP
Det vore logiskt om meteoriterna tålde olika mycket innan de sprängdes. T.ex. att en stor tålde 4 skott, en medel 2 och en liten 1.Power ups
Lägg till en chans att det dyker upp en "power up" när meteoriterna exploderar. Förslag på olika power ups:- Liv
- Ökad acceleration
- Nya/kraftigare vapen