Here is a game I made in school, I've been going back through it and am looking for ways to improve it. If anyone has any ideas or critical feedback that'd be great :^)
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
const int
INSERT_COIN = 0,
PLAYING = 1,
GAME_OVER = 2;
int state = 0;
Texture2D backgroundTexture;
Texture2D shipSpriteSheet;
Texture2D submarineSpriteSheet;
Texture2D torpedoSpriteSheet;
Texture2D depthChargeSpriteSheet;
SpriteFont messageFont;
SpriteFont scoreFont;
ShipSprite ship;
SubmarineSprite submarine;
TorpedoSprite torpedo;
DepthChargeSprite depthCharge;
int updateCount = 0;
int score = 0;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
backgroundTexture = this.Content.Load<Texture2D>("Background");
shipSpriteSheet = this.Content.Load<Texture2D>("Ship");
submarineSpriteSheet = this.Content.Load<Texture2D>("Submarine");
torpedoSpriteSheet = this.Content.Load<Texture2D>("Torpedo");
depthChargeSpriteSheet = this.Content.Load<Texture2D>("DepthCharge");
messageFont = this.Content.Load<SpriteFont>("MessageFont");
scoreFont = this.Content.Load<SpriteFont>("ScoreFont");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
KeyboardState kbState = Keyboard.GetState();
updateCount++;
switch (state)
{
case INSERT_COIN:
ship = null;
submarine = null;
torpedo = null;
depthCharge = null;
updateCount = 0;
score = 0;
if (kbState.IsKeyDown(Keys.Escape)) state = PLAYING;
break;
case PLAYING:
if (torpedo == null) torpedo = new TorpedoSprite(torpedoSpriteSheet, this, 100);
if (depthCharge == null) depthCharge = new DepthChargeSprite(depthChargeSpriteSheet, this, 100);
if (submarine == null) submarine = new SubmarineSprite(submarineSpriteSheet, this, 150);
if (ship == null) ship = new ShipSprite(shipSpriteSheet, this, 180);
if (kbState.IsKeyDown(Keys.Down)) depthCharge.Drop = true;
ship.Update(kbState);
depthCharge.Update(ship, submarine);
submarine.Update(ship, torpedo);
torpedo.Update(ship, submarine);
if (submarine.Destroyed && submarine.Finished) score += 20;
if (submarine.Finished) submarine = null;
if (torpedo.Finished) torpedo = null;
if (depthCharge.Finished) depthCharge = null;
if (ship.Finished && submarine == null) { updateCount = 0; state = GAME_OVER; }
break;
case GAME_OVER:
if (updateCount > 120) state = INSERT_COIN;
break;
default:
break;
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
switch (state)
{
case INSERT_COIN:
spriteBatch.DrawString(messageFont, "Press ESC key to start game",
new Vector2(220, 300), Color.White);
break;
case PLAYING:
spriteBatch.Draw(backgroundTexture, new Vector2(0, 0), Color.White);
//Draws the sprites if not equal to null
if (ship != null) ship.Draw(spriteBatch); //Draws ship sprite
if (submarine != null) submarine.Draw(spriteBatch); //Draws Submarine sprite
if (torpedo != null) torpedo.Draw(spriteBatch); //Draws Torpedo sprite
if (depthCharge != null) depthCharge.Draw(spriteBatch); //Draws Depth Charge sprite
spriteBatch.Draw(submarineSpriteSheet, new Vector2(630, 45), new Rectangle(0, 0, 150, 80),
Color.White, 0f, new Vector2(0, 0), 0.4f, SpriteEffects.None, 0.5f);
spriteBatch.DrawString(scoreFont, "" + score, new Vector2(700, 50), Color.White);
break;
case GAME_OVER:
spriteBatch.Draw(backgroundTexture, new Vector2(0, 0), Color.White);
spriteBatch.Draw(submarineSpriteSheet, new Vector2(630, 45), new Rectangle(0, 0, 150, 80),
Color.White, 0f, new Vector2(0, 0), 0.4f, SpriteEffects.None, 0.5f);
spriteBatch.DrawString(scoreFont, "" + score, new Vector2(700, 50), Color.White);
spriteBatch.DrawString(messageFont, "GAME OVER", new Vector2(330, 300), Color.Red);
break;
default:
break;
}
spriteBatch.End();
base.Draw(gameTime);
}
}
class ShipSprite : BasicSprite
{
public const int
START = 0, //Set start position
SAILING = 1, //Moving ship along X-axis
SINKING = 2, //Hit by torpedo, sink animation
DESTROYED = 3, //Starts timer
END = 4; //End state
// States for finite state machines
int _state;
int _timer;
const int LEFT_SCREEN = 0; //Position of left side of screen
const int RIGHT_SCREEN = 800; //Position of right side of screen
const int X_START_POS = 300; //X co-ord for ship start point
const int Y_START_POS = 150; //Y co-ord for ship start point
const int SHIP_DESTROY_DELAY = 120; //Time delay for ship destruction
// Message from Torpedo: ship has been hit and is sinking
bool _hitByTorpedo;
public bool HitByTorpedo { set { _hitByTorpedo = value; } }
// Message for the Game1 and Submarine: the ship is sailing normally
public bool Sailing { get { return _state == SAILING; } }
// Constructor
public ShipSprite(Texture2D image,
Game game,
int frameWidth)
: base(image, game, frameWidth,
0, 0, 1000000, // Turn off default animation
90, 75) // Image offset
{
_state = START;
}
// Update the ship's position using the keyboard
public void Update(KeyboardState kbState)
{
switch (_state)
{
case START:
//Sets ship starting position
X = X_START_POS;
Y = Y_START_POS;
Show = true;
_state = SAILING;
break;
case SAILING:
//If left key is pressed and ship is on the screen...
if (kbState.IsKeyDown(Keys.Left) && X > LEFT_SCREEN)
{
//Move ship left
X--;
//Flip sprite image
FlipHorizontally = true;
}
//If right key is pressed and ship is on the screen...
if (kbState.IsKeyDown(Keys.Right) && X < RIGHT_SCREEN)
{
//Move ship right
X++;
FlipHorizontally = false;
}
//If hit by torpedo...
if (_hitByTorpedo)
{
HitByTorpedo = true;
//Display animation (first frame, last frame, framerate)
SetCycle(0, 5, 10);
_state = SINKING;
}
break;
case SINKING:
//If last frame of animation
if(FrameNumber == 4)
{
//End animation
this.SetCycle(4, 4, 1000);
_state = DESTROYED;
}
break;
case DESTROYED:
//Increment timer
_timer++;
//If timer equals destroy delay...
if (_timer == SHIP_DESTROY_DELAY)
{
_state = END;
}
break;
case END:
// This sprite is now finished
Show = false;
Finished = true;
break;
default:
// Should never get here
_state = END;
break;
}
// Call the BasicSprite Update to allow animation
base.Update();
}
}
class DepthChargeSprite : VectoredSprite
{
public const int
START = 0, //Waits for drop command/confirms ship direction
IN_AIR = 1, //Deploy depth charge in air
SINKING = 2, //Depth charge in water
EXPLODING = 3, //Hit or miss submarine
END = 4; //End state
// State for finite state machine
int _state;
// Trigger from user to start depth charge drop/explode sequence
bool _drop;
public bool Drop { set { _drop = value; } }
const int SEA_LEVEL = 150; //Y value of sea level
const double VELOCITY_IN_AIR = 3; //Speed of depth charge in air
const double VELOCITY_IN_WATER = 2.5; //Speed of depth charge in water
const double DEPLOY_ROTATION = 0.1; //Rotation which depth charge is deployed
// Constructor
public DepthChargeSprite(Texture2D image,
Game game,
int frameWidth)
: base(image, game, frameWidth,
0, 0, 1000000, // No animation required for frame 0
50, 50) // Image origin
{
_state = START;
}
// Update to be called from Game1 class.
// ship required as parameter to establish starting position
// submarine required to establish explosion depth and whether submarine destroyed.
public void Update(ShipSprite ship,
SubmarineSprite submarine)
{
switch (_state)
{
case START:
//If depth charge dropped and ship is facing right...
if(_drop && ship.FlipHorizontally == false)
{
X = ship.X - 50;
Y = ship.Y;
Angle = 0;
Velocity = VELOCITY_IN_AIR;
Rotation = -DEPLOY_ROTATION;
Scale = 0.3;
Show = true;
_state = IN_AIR;
}
//If depth charge dropped and ship is facing left...
if (_drop && ship.FlipHorizontally == true)
{
X = ship.X + 50;
Y = ship.Y;
Angle = 0;
Velocity = VELOCITY_IN_AIR;
Rotation = DEPLOY_ROTATION;
Scale = 0.3;
Show = true;
_state = IN_AIR;
}
break;
case IN_AIR:
//If depth charge is in the sea...
if (Y >= SEA_LEVEL)
{
Angle = Math.PI;
Rotation = 0;
Velocity = VELOCITY_IN_WATER;
_state = SINKING;
}
break;
case SINKING:
//If depth charge Y reaches submarine Y...
if (Y >= submarine.Y)
{
//Display animation
SetCycle(0, 6, 10);
Velocity = 0;
Angle = 0;
Scale = 1;
_state = EXPLODING;
}
break;
case EXPLODING:
//Draw bounding box for depth charge
Rectangle depthChargeBox = new Rectangle((int)X, (int)Y, 100, 100);
//Draw bounding box for submarine
Rectangle submarineBox = new Rectangle((int)submarine.X, (int)submarine.Y, 150, 80);
//If depth charge box intersects with submarine box...
if(depthChargeBox.Intersects(submarineBox))
{
//Submarine is hit
submarine.HitByDepthCharge = true;
}
//If last frame of animation...
if (FrameNumber == 6)
{
_state = END;
}
break;
case END:
Finished = true;
Show = false;
break;
default:
// Should never reach here
_state = END;
break;
}
// Call the VectoredSprite Update to perform any movement and animation
base.Update();
}
}
class SubmarineSprite : MovingSprite
{
public const int
START = 0, // Newly created sprite
SAILING = 1, // Sailing across the screen
SINKING = 2, // Hit by depth charge and sinking
DESTROYED = 3, // Destroyed by depth charge
END = 4; // Finished
const int MIN_DISTANCE = 60; // Minimum distance to ship for launching torpedo
const int SUB_START_LEFT = 0; //Position of left side screen
const int SUB_START_RIGHT = 800; //Position of right side screen
const int TORPEDO_DELAY = 80; //Time between firing torpedos
const int DESTROY_DELAY = 60; //Destroy duration
const int SUB_VELOCITY = 2; //Speed of submarines
// State for finite state machine
int _state;
int _timer;
// Signal from depth charge sprite: submarine has been hit
bool _hitByDepthCharge;
public bool HitByDepthCharge { set { _hitByDepthCharge = value; } }
// Signal to Game1 class: submarine destroyed by depth charge
bool _destroyed;
public bool Destroyed { get { return _destroyed; } }
// Constructor
public SubmarineSprite(Texture2D image,
Game game,
int frameWidth)
: base(image, game, frameWidth,
0, 0, 1000000,
75, 50)
{
_state = START;
}
// Update to be called from Game1 class.
// ship required as parameter to establish whether in range for torpedo
// torpedo required as parameter to trigger torpedo launch
public void Update(ShipSprite ship,
TorpedoSprite torpedo)
{
switch (_state)
{
case START:
Random randomNumber = new Random();
//Stores random number as randomNum
int randomNum = randomNumber.Next(0, 2);
Random randomN = new Random();
//Stores random number as randomY
int randomY = randomN.Next(250, 550);
//If randomNum is below 0.5...
if(randomNum < 0.5)
{
X = SUB_START_LEFT;
Y = randomY;
VelocityX = SUB_VELOCITY;
FlipHorizontally = false;
Show = true;
}
//If randomNum is above or equal to 0.5...
if (randomNum >= 0.5)
{
X = SUB_START_RIGHT;
Y = randomY;
VelocityX = -SUB_VELOCITY;
FlipHorizontally = true;
Show = true;
}
_state = SAILING;
break;
case SAILING:
//Increments timer
_timer++;
//If timer is larger than torpedo delay,
//the ship is infront of the sub and torpedo is not in running state
if (_timer > TORPEDO_DELAY && this.IsShipAhead(ship) && torpedo.Running == false)
{
//Launch torpedo
torpedo.Launch = true;
_state = SAILING;
}
////If submarine goes off of screen...
if (X > SUB_START_RIGHT || X < SUB_START_LEFT)
{
Finished = true;
Show = false;
_state = END;
}
//If sub is hit by depth charge...
if (_hitByDepthCharge == true)
{
//Display animation
SetCycle(0, 4, 10);
VelocityX = 0;
_state = SINKING;
}
break;
case SINKING:
//If animation is on final frame...
if (FrameNumber == 4)
{
//End animation
SetCycle(4, 4, 10);
_destroyed = true;
_timer = 0;
_state = DESTROYED;
}
break;
case DESTROYED:
//Increment timer
_timer++;
//If timer is larger than or equal to destroy elay...
if (_timer >= DESTROY_DELAY)
{
_state = END;
}
break;
case END:
// This sprite is now finished
Show = false;
Finished = true;
break;
default:
// Should never reach here
_state = END;
break;
}
// Call the BasicSprite Update to allow animation
base.Update();
}
// Return true if the ship is ahead of the submarine, taking into account
// whether the sub is facing left or right
private bool IsShipAhead(ShipSprite ship)
{
// Function result
bool result = false;
// Which direction is the sub facing?
if (!FlipHorizontally)
result = (X < ship.X - MIN_DISTANCE);
else
result = (X > ship.X + MIN_DISTANCE);
// Confirm ship is in the SAILING state
result = result & ship.Sailing;
return result;
}
}
class TorpedoSprite : VectoredSprite
{
public const int
START = 0, // Waiting for launch command
RUNNING = 1, // Running towards target ship
HIT_TARGET = 2, // Hit the ship and exploding
END = 3; // End state
const int TORPEDO_OFFSET = 75; //Sets where the torpedo is fired from in relation to sub
const double TORPEDO_VELOCITY = 1.5; //Torpedo Speed
const double TORPEDO_SCALE = 0.5; //Scales torpedo
const double ANGLE_CHANGE = 0.5; //Angle difference for torpedo
// State for finite state machine
int _state;
// Target: position of ship when torpedo launched
double _targetX, _targetY;
// Signal from submarine to launch this torpedo
bool _launch;
public bool Launch { set { _launch = value; } }
// Status flag for submarine
public bool Running { get { return _state == RUNNING; } }
// Constructor for torpedo sprite
public TorpedoSprite(Texture2D image,
Game game,
int frameWidth)
: base(image, game, frameWidth,
0, 0, 1000000, // No animation required for torpedo
50, 50) // Image origin
{
_state = START;
}
// Update to be called from Game1 class.
// ship required as parameter to establish target position
// submarine required to establish torpedo's starting position
public void Update(ShipSprite ship,
SubmarineSprite submarine)
{
switch (_state)
{
case START:
//If torpedo launched and sub is facing right...
if (_launch && submarine.FlipHorizontally == false)
{
X = submarine.X + TORPEDO_OFFSET;
Y = submarine.Y;
Angle = Math.PI/2;
_targetX = ship.X;
_targetY = ship.Y;
Velocity = TORPEDO_VELOCITY;
Scale = TORPEDO_SCALE;
Show = true;
_state = RUNNING;
}
//If torpedo launched and sub is facing left...
if (_launch && submarine.FlipHorizontally == true)
{
X = submarine.X - TORPEDO_OFFSET;
Y = submarine.Y;
Angle = -Math.PI/2;
_targetX = ship.X;
_targetY = ship.Y;
Velocity = TORPEDO_VELOCITY;
Scale = TORPEDO_SCALE;
Show = true;
_state = RUNNING;
}
break;
case RUNNING:
//Creats a new angle
double newAngle = Math.Atan2(_targetX - X, Y - _targetY);
//If submarine is facing left...
if (submarine.FlipHorizontally == true)
{
//Sets angle
Angle = newAngle - ANGLE_CHANGE;
}
//If submarine is facing right...
if (submarine.FlipHorizontally == false)
{
//Sets angle
Angle = newAngle + ANGLE_CHANGE;
}
//Draws bounding box for torpedo
Rectangle torpedoBox = new Rectangle((int)X, (int)Y, 50, 50);
//Tells where to place the bounding box for torpedo
Vector2 torpedoPosition = new Vector2((float)X, (float)Y);
//Draws bounding box for ship
Rectangle shipBox = new Rectangle((int)ship.X, (int)ship.Y, 180, 100);
//Bounding box position for ship
Vector2 shipPosition = new Vector2((float)ship.X, (float)ship.Y);
//If torpedo box contains ships target position...
if (torpedoBox.Contains(new Point((int)_targetX,(int)_targetY)))
{
//If torpedo and ship interect...
if(torpedoBox.Intersects(shipBox))
{
ship.HitByTorpedo = true;
SetCycle(0, 5, 5);
Scale = 1;
Velocity = 0;
Angle = 0;
Show = false;
Finished = true;
_state = HIT_TARGET;
}
//If they don't intersect...
else
{
Show = false;
Finished = true;
_state = END;
}
}
//If torpedo position is equal to target position...
if (X == _targetX && Y == _targetY)
{
ship.HitByTorpedo = false;
_state = END;
}
break;
case HIT_TARGET:
//If animation is finished...
if(FrameNumber == 5)
{
Show = false;
Finished = true;
_state = END;
}
break;
case END:
// This sprite is finished
Show = false;
Finished = true;
break;
default:
_state = END;
break;
}
// Call the VectoredSprite Update to allow movement and animation
base.Update();
}
}