#region File Description //----------------------------------------------------------------------------- // FuelCellGame.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using System; using MediaPlayer = Microsoft.Xna.Framework.Media.MediaPlayer; namespace FuelCell { /// /// The available states of the game /// public enum GameState { Loading, Running, Won, Lost } /// /// This is the main type for your game /// public class FuelCellGame : Game { // Resources for drawing. private GraphicsDeviceManager graphics; private SpriteBatch spriteBatch; private SpriteFont statsFont; private GameObject ground; private Camera gameCamera; private Random random; private GameState currentGameState = GameState.Loading; // Game objects private FuelCarrier fuelCarrier; private FuelCell[] fuelCells; private Barrier[] barriers; private GameObject boundingSphere; private int retrievedFuelCells = 0; private TimeSpan startTime, roundTimer, roundTime; private float aspectRatio; private IInputState inputState; private Song backgroundMusic; public FuelCellGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; random = new Random(); roundTime = GameConstants.RoundTime; graphics.PreferredBackBufferWidth = 853; graphics.PreferredBackBufferHeight = 480; inputState = new InputState(this); } protected override void Initialize() { // Initialize the Game objects ground = new GameObject(); gameCamera = new Camera(); boundingSphere = new GameObject(); aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio; base.Initialize(); } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { this.Content.RootDirectory = "Content"; // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); statsFont = Content.Load("Fonts/StatsFont"); ground.Model = Content.Load("Models/ground"); boundingSphere.Model = Content.Load("Models/sphere1uR"); // Audio backgroundMusic = Content.Load("Audio/background-music"); //Initialize fuel cells fuelCells = new FuelCell[GameConstants.NumFuelCells]; for (int index = 0; index < fuelCells.Length; index++) { fuelCells[index] = new FuelCell(); fuelCells[index].LoadContent(Content, "Models/fuelcellmodel"); } //Initialize barriers barriers = new Barrier[GameConstants.NumBarriers]; int randomBarrier = random.Next(3); string barrierName = null; for (int index = 0; index < barriers.Length; index++) { switch (randomBarrier) { case 0: barrierName = "Models/cube10uR"; break; case 1: barrierName = "Models/cylinder10uR"; break; case 2: barrierName = "Models/pyramid10uR"; break; } barriers[index] = new Barrier(); barriers[index].LoadContent(Content, barrierName); randomBarrier = random.Next(3); } PlaceFuelCellsAndBarriers(); //Initialize fuel carrier fuelCarrier = new FuelCarrier(); fuelCarrier.LoadContent(Content, "Models/fuelcarrier"); } /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { // Update the InputState class. inputState.Update(); // Allows the game to exit if (inputState.PlayerExit(PlayerIndex.One)) { this.Exit(); } // If the player has only just pressed the Enter key or has pressed the Start button if (currentGameState == GameState.Loading) { if (inputState.StartGame(PlayerIndex.One)) { ResetGame(gameTime, aspectRatio); } } // Main gameplay running screen if ((currentGameState == GameState.Running)) { fuelCarrier.Update(inputState, barriers); float aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio; gameCamera.Update(fuelCarrier.ForwardDirection, fuelCarrier.Position, aspectRatio); retrievedFuelCells = 0; foreach (FuelCell fuelCell in fuelCells) { fuelCell.Update(fuelCarrier.BoundingSphere); if (fuelCell.Retrieved) { retrievedFuelCells++; } } if (retrievedFuelCells == GameConstants.NumFuelCells) { currentGameState = GameState.Won; } roundTimer -= gameTime.ElapsedGameTime; if ((roundTimer < TimeSpan.Zero) && (retrievedFuelCells != GameConstants.NumFuelCells)) { currentGameState = GameState.Lost; } } if ((currentGameState == GameState.Won) || (currentGameState == GameState.Lost)) { // Gameplay has stopped and if audio is still playing, stop it. if (MediaPlayer.State == MediaState.Playing) { MediaPlayer.Stop(); } // Reset the world for a new game if (inputState.StartGame(PlayerIndex.One)) { ResetGame(gameTime, aspectRatio); } } base.Update(gameTime); } /// /// Draws the game from background to foreground. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); switch (currentGameState) { case GameState.Loading: DrawSplashScreen(); break; case GameState.Running: DrawGameplayScreen(); break; case GameState.Won: DrawWinOrLossScreen(GameConstants.StrGameWon); break; case GameState.Lost: DrawWinOrLossScreen(GameConstants.StrGameLost); break; }; base.Draw(gameTime); } private void DrawGameplayScreen() { // Draw the ground terrain model DrawTerrain(ground.Model); // Draw the fuel cells on the map foreach (FuelCell fuelCell in fuelCells) { if (!fuelCell.Retrieved) { fuelCell.Draw(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix); // Draw the bounding sphere for the fuel cells // ChangeRasterizerState(FillMode.WireFrame); // fuelCell.DrawBoundingSphere(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix, boundingSphere); // ChangeRasterizerState(FillMode.Solid); } } // Draw the barriers on the map foreach (Barrier barrier in barriers) { barrier.Draw(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix); // Draw the bounding sphere for the barriers // ChangeRasterizerState(FillMode.WireFrame); // barrier.DrawBoundingSphere(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix, boundingSphere); // ChangeRasterizerState(FillMode.Solid); } // Draw the player fuelcarrier on the map fuelCarrier.Draw(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix); // Draw the bounding sphere for the fuel carrier // ChangeRasterizerState(FillMode.WireFrame); // fuelCarrier.DrawBoundingSphere(gameCamera.ViewMatrix, gameCamera.ProjectionMatrix, boundingSphere); // ChangeRasterizerState(FillMode.Solid); DrawStats(); } /// /// Helper function to change the rasterizer state for drawing the wireframe bounding spheres /// /// The render `FillMode` to draw with, e.g. WireFrame /// The culling mode to draw with, e.g. None /// Returns a new RasterizerState to apply to a graphics device private RasterizerState ChangeRasterizerState(FillMode fillmode, CullMode cullMode = CullMode.None) { RasterizerState rasterizerState = new RasterizerState() { FillMode = fillmode, CullMode = cullMode }; graphics.GraphicsDevice.RasterizerState = rasterizerState; return rasterizerState; } private void DrawTerrain(Model model) { foreach (ModelMesh mesh in model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); effect.PreferPerPixelLighting = true; effect.World = Matrix.Identity; // Use the matrices provided by the game camera effect.View = gameCamera.ViewMatrix; effect.Projection = gameCamera.ProjectionMatrix; } mesh.Draw(); } } private void DrawSplashScreen() { float xOffsetText, yOffsetText; Vector2 viewportSize = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); Vector2 strCenter; graphics.GraphicsDevice.Clear(Color.SteelBlue); xOffsetText = yOffsetText = 0; Vector2 strInstructionsSize = statsFont.MeasureString(GameConstants.StrInstructions1); Vector2 strPosition; strCenter = new Vector2(strInstructionsSize.X / 2, strInstructionsSize.Y / 2); yOffsetText = (viewportSize.Y / 2 - strCenter.Y); xOffsetText = (viewportSize.X / 2 - strCenter.X); strPosition = new Vector2(xOffsetText, yOffsetText); spriteBatch.Begin(); spriteBatch.DrawString(statsFont, GameConstants.StrInstructions1, strPosition, Color.White); strInstructionsSize = statsFont.MeasureString(GameConstants.StrInstructions2); strCenter = new Vector2(strInstructionsSize.X / 2, strInstructionsSize.Y / 2); yOffsetText = (viewportSize.Y / 2 - strCenter.Y) + statsFont.LineSpacing; xOffsetText = (viewportSize.X / 2 - strCenter.X); strPosition = new Vector2(xOffsetText, yOffsetText); spriteBatch.DrawString(statsFont, GameConstants.StrInstructions2, strPosition, Color.LightGray); spriteBatch.End(); ResetRenderStates(); } private void DrawWinOrLossScreen(string gameResult) { float xOffsetText, yOffsetText; Vector2 viewportSize = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); Vector2 strCenter; xOffsetText = yOffsetText = 0; Vector2 strResult = statsFont.MeasureString(gameResult); Vector2 strPlayAgainSize = statsFont.MeasureString(GameConstants.StrPlayAgain); Vector2 strPosition; strCenter = new Vector2(strResult.X / 2, strResult.Y / 2); yOffsetText = (viewportSize.Y / 2 - strCenter.Y); xOffsetText = (viewportSize.X / 2 - strCenter.X); strPosition = new Vector2(xOffsetText, yOffsetText); spriteBatch.Begin(); spriteBatch.DrawString(statsFont, gameResult, strPosition, Color.Red); strCenter = new Vector2(strPlayAgainSize.X / 2, strPlayAgainSize.Y / 2); yOffsetText = (viewportSize.Y / 2 - strCenter.Y) + (float)statsFont.LineSpacing; xOffsetText = (viewportSize.X / 2 - strCenter.X); strPosition = new Vector2(xOffsetText, yOffsetText); spriteBatch.DrawString(statsFont, GameConstants.StrPlayAgain, strPosition, Color.AntiqueWhite); spriteBatch.End(); ResetRenderStates(); } private void DrawStats() { float xOffsetText, yOffsetText; string str1 = GameConstants.StrTimeRemaining; string str2 = GameConstants.StrCellsFound + retrievedFuelCells.ToString() + " of " + GameConstants.NumFuelCells.ToString(); Rectangle rectSafeArea; str1 += (roundTimer.Seconds).ToString(); //Calculate str1 position rectSafeArea = GraphicsDevice.Viewport.TitleSafeArea; xOffsetText = rectSafeArea.X; yOffsetText = rectSafeArea.Y; Vector2 strSize = statsFont.MeasureString(str1); Vector2 strPosition = new Vector2(xOffsetText + 10, yOffsetText); spriteBatch.Begin(); spriteBatch.DrawString(statsFont, str1, strPosition, Color.White); strPosition.Y += strSize.Y; spriteBatch.DrawString(statsFont, str2, strPosition, Color.White); spriteBatch.End(); ResetRenderStates(); } private void ResetRenderStates() { //re-enable depth buffer after sprite batch disablement GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap; } private void PlaceFuelCellsAndBarriers() { int min = GameConstants.MinDistance; int max = GameConstants.MaxDistance; Vector3 tempCenter; //place fuel cells foreach (FuelCell cell in fuelCells) { cell.Position = GenerateRandomPosition(min, max); tempCenter = cell.BoundingSphere.Center; tempCenter.X = cell.Position.X; tempCenter.Z = cell.Position.Z; cell.BoundingSphere = new BoundingSphere(tempCenter, cell.BoundingSphere.Radius); cell.Retrieved = false; } //place barriers foreach (Barrier barrier in barriers) { barrier.Position = GenerateRandomPosition(min, max); tempCenter = barrier.BoundingSphere.Center; tempCenter.X = barrier.Position.X; tempCenter.Z = barrier.Position.Z; barrier.BoundingSphere = new BoundingSphere(tempCenter, barrier.BoundingSphere.Radius); } } private Vector3 GenerateRandomPosition(int min, int max) { int xValue, zValue; do { xValue = random.Next(min, max); zValue = random.Next(min, max); if (random.Next(100) % 2 == 0) xValue *= -1; if (random.Next(100) % 2 == 0) zValue *= -1; } while (IsOccupied(xValue, zValue)); return new Vector3(xValue, 0, zValue); } private bool IsOccupied(int xValue, int zValue) { foreach (GameObject currentObj in fuelCells) { if (((int)(MathHelper.Distance(xValue, currentObj.Position.X)) < 15) && ((int)(MathHelper.Distance(zValue, currentObj.Position.Z)) < 15)) { return true; } } foreach (GameObject currentObj in barriers) { if (((int)(MathHelper.Distance(xValue, currentObj.Position.X)) < 15) && ((int)(MathHelper.Distance(zValue, currentObj.Position.Z)) < 15)) { return true; } } return false; } private void ResetGame(GameTime gameTime, float aspectRatio) { fuelCarrier.Reset(); gameCamera.Update(fuelCarrier.ForwardDirection, fuelCarrier.Position, aspectRatio); InitializeGameField(); retrievedFuelCells = 0; startTime = gameTime.TotalGameTime; roundTimer = roundTime; currentGameState = GameState.Running; // We use a try catch around the Media player, else in debug mode it can cause an exception which we need to catch. try { MediaPlayer.Stop(); MediaPlayer.IsRepeating = true; MediaPlayer.Volume = 0.5f; MediaPlayer.Play(backgroundMusic); } catch { } } private void InitializeGameField() { //Initialize barriers barriers = new Barrier[GameConstants.NumBarriers]; int randomBarrier = random.Next(3); string barrierName = null; for (int index = 0; index < GameConstants.NumBarriers; index++) { switch (randomBarrier) { case 0: barrierName = "Models/cube10uR"; break; case 1: barrierName = "Models/cylinder10uR"; break; case 2: barrierName = "Models/pyramid10uR"; break; } barriers[index] = new Barrier(); barriers[index].LoadContent(Content, barrierName); randomBarrier = random.Next(3); } PlaceFuelCellsAndBarriers(); } } }