#region File Description
//-----------------------------------------------------------------------------
// Game.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace XMLContentLoadingSample
{
///
/// Sample showing how to implement a particle system entirely
/// on the GPU, using the vertex shader to animate particles.
///
public class Particle3DSampleGame : Microsoft.Xna.Framework.Game
{
#region Fields
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont font;
Model grid;
// This sample uses five different particle systems.
ParticleSystem explosionParticles;
ParticleSystem explosionSmokeParticles;
ParticleSystem projectileTrailParticles;
ParticleSystem smokePlumeParticles;
ParticleSystem fireParticles;
// The sample can switch between three different visual effects.
enum ParticleState
{
Explosions,
SmokePlume,
RingOfFire,
};
ParticleState currentState = ParticleState.Explosions;
// The explosions effect works by firing projectiles up into the
// air, so we need to keep track of all the active projectiles.
List projectiles = new List();
TimeSpan timeToNextProjectile = TimeSpan.Zero;
// Random number generator for the fire effect.
Random random = new Random();
// Input state.
KeyboardState currentKeyboardState;
GamePadState currentGamePadState;
KeyboardState lastKeyboardState;
GamePadState lastGamePadState;
// Camera state.
float cameraArc = -5;
float cameraRotation = 0;
float cameraDistance = 200;
#endregion
#region Initialization
///
/// Constructor.
///
public Particle3DSampleGame()
{
graphics = new GraphicsDeviceManager(this);
graphics.GraphicsProfile = GraphicsProfile.HiDef;
Content.RootDirectory = "Content";
// Construct our particle system components.
explosionParticles = new ParticleSystem(this, Content, "ExplosionSettings");
explosionSmokeParticles = new ParticleSystem(this, Content, "ExplosionSmokeSettings");
projectileTrailParticles = new ParticleSystem(this, Content, "ProjectileTrailSettings");
smokePlumeParticles = new ParticleSystem(this, Content, "SmokePlumeSettings");
fireParticles = new ParticleSystem(this, Content, "FireSettings");
// Set the draw order so the explosions and fire
// will appear over the top of the smoke.
smokePlumeParticles.DrawOrder = 100;
explosionSmokeParticles.DrawOrder = 200;
projectileTrailParticles.DrawOrder = 300;
explosionParticles.DrawOrder = 400;
fireParticles.DrawOrder = 500;
// Register the particle system components.
Components.Add(explosionParticles);
Components.Add(explosionSmokeParticles);
Components.Add(projectileTrailParticles);
Components.Add(smokePlumeParticles);
Components.Add(fireParticles);
}
///
/// Load your graphics content.
///
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
font = Content.Load("font");
grid = Content.Load("grid");
}
#endregion
#region Update and Draw
///
/// Allows the game to run logic.
///
protected override void Update(GameTime gameTime)
{
HandleInput();
UpdateCamera(gameTime);
switch (currentState)
{
case ParticleState.Explosions:
UpdateExplosions(gameTime);
break;
case ParticleState.SmokePlume:
UpdateSmokePlume();
break;
case ParticleState.RingOfFire:
UpdateFire();
break;
}
UpdateProjectiles(gameTime);
base.Update(gameTime);
}
///
/// Helper for updating the explosions effect.
///
void UpdateExplosions(GameTime gameTime)
{
timeToNextProjectile -= gameTime.ElapsedGameTime;
if (timeToNextProjectile <= TimeSpan.Zero)
{
// Create a new projectile once per second. The real work of moving
// and creating particles is handled inside the Projectile class.
projectiles.Add(new Projectile(explosionParticles,
explosionSmokeParticles,
projectileTrailParticles));
timeToNextProjectile += TimeSpan.FromSeconds(1);
}
}
///
/// Helper for updating the list of active projectiles.
///
void UpdateProjectiles(GameTime gameTime)
{
int i = 0;
while (i < projectiles.Count)
{
if (!projectiles[i].Update(gameTime))
{
// Remove projectiles at the end of their life.
projectiles.RemoveAt(i);
}
else
{
// Advance to the next projectile.
i++;
}
}
}
///
/// Helper for updating the smoke plume effect.
///
void UpdateSmokePlume()
{
// This is trivial: we just create one new smoke particle per frame.
smokePlumeParticles.AddParticle(Vector3.Zero, Vector3.Zero);
}
///
/// Helper for updating the fire effect.
///
void UpdateFire()
{
const int fireParticlesPerFrame = 20;
// Create a number of fire particles, randomly positioned around a circle.
for (int i = 0; i < fireParticlesPerFrame; i++)
{
fireParticles.AddParticle(RandomPointOnCircle(), Vector3.Zero);
}
// Create one smoke particle per frmae, too.
smokePlumeParticles.AddParticle(RandomPointOnCircle(), Vector3.Zero);
}
///
/// Helper used by the UpdateFire method. Chooses a random location
/// around a circle, at which a fire particle will be created.
///
Vector3 RandomPointOnCircle()
{
const float radius = 30;
const float height = 40;
double angle = random.NextDouble() * Math.PI * 2;
float x = (float)Math.Cos(angle);
float y = (float)Math.Sin(angle);
return new Vector3(x * radius, y * radius + height, 0);
}
///
/// This is called when the game should draw itself.
///
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
device.Clear(Color.CornflowerBlue);
// Compute camera matrices.
float aspectRatio = (float)device.Viewport.Width /
(float)device.Viewport.Height;
Matrix view = Matrix.CreateTranslation(0, -25, 0) *
Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
new Vector3(0, 0, 0), Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
aspectRatio,
1, 10000);
// Pass camera matrices through to the particle system components.
explosionParticles.SetCamera(view, projection);
explosionSmokeParticles.SetCamera(view, projection);
projectileTrailParticles.SetCamera(view, projection);
smokePlumeParticles.SetCamera(view, projection);
fireParticles.SetCamera(view, projection);
// Draw our background grid and message text.
DrawGrid(view, projection);
DrawMessage();
// This will draw the particle system components.
base.Draw(gameTime);
}
///
/// Helper for drawing the background grid model.
///
void DrawGrid(Matrix view, Matrix projection)
{
GraphicsDevice device = graphics.GraphicsDevice;
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
device.SamplerStates[0] = SamplerState.LinearWrap;
grid.Draw(Matrix.Identity, view, projection);
}
///
/// Helper for drawing our message text.
///
void DrawMessage()
{
string message = string.Format("Current effect: {0}!!!\n" +
"Hit the A button or space bar to switch.",
currentState);
spriteBatch.Begin();
spriteBatch.DrawString(font, message, new Vector2(50, 50), Color.White);
spriteBatch.End();
}
#endregion
#region Handle Input
///
/// Handles input for quitting the game and cycling
/// through the different particle effects.
///
void HandleInput()
{
lastKeyboardState = currentKeyboardState;
lastGamePadState = currentGamePadState;
currentKeyboardState = Keyboard.GetState();
currentGamePadState = GamePad.GetState(PlayerIndex.One);
// Check for exit.
if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
currentGamePadState.Buttons.Back == ButtonState.Pressed)
{
Exit();
}
// Check for changing the active particle effect.
if (((currentKeyboardState.IsKeyDown(Keys.Space) &&
(lastKeyboardState.IsKeyUp(Keys.Space))) ||
((currentGamePadState.Buttons.A == ButtonState.Pressed)) &&
(lastGamePadState.Buttons.A == ButtonState.Released)))
{
currentState++;
if (currentState > ParticleState.RingOfFire)
currentState = 0;
}
}
///
/// Handles input for moving the camera.
///
void UpdateCamera(GameTime gameTime)
{
float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Check for input to rotate the camera up and down around the model.
if (currentKeyboardState.IsKeyDown(Keys.Up) ||
currentKeyboardState.IsKeyDown(Keys.W))
{
cameraArc += time * 0.025f;
}
if (currentKeyboardState.IsKeyDown(Keys.Down) ||
currentKeyboardState.IsKeyDown(Keys.S))
{
cameraArc -= time * 0.025f;
}
cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.05f;
// Limit the arc movement.
if (cameraArc > 90.0f)
cameraArc = 90.0f;
else if (cameraArc < -90.0f)
cameraArc = -90.0f;
// Check for input to rotate the camera around the model.
if (currentKeyboardState.IsKeyDown(Keys.Right) ||
currentKeyboardState.IsKeyDown(Keys.D))
{
cameraRotation += time * 0.05f;
}
if (currentKeyboardState.IsKeyDown(Keys.Left) ||
currentKeyboardState.IsKeyDown(Keys.A))
{
cameraRotation -= time * 0.05f;
}
cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.1f;
// Check for input to zoom camera in and out.
if (currentKeyboardState.IsKeyDown(Keys.Z))
cameraDistance += time * 0.25f;
if (currentKeyboardState.IsKeyDown(Keys.X))
cameraDistance -= time * 0.25f;
cameraDistance += currentGamePadState.Triggers.Left * time * 0.5f;
cameraDistance -= currentGamePadState.Triggers.Right * time * 0.5f;
// Limit the camera distance.
if (cameraDistance > 500)
cameraDistance = 500;
else if (cameraDistance < 10)
cameraDistance = 10;
if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
currentKeyboardState.IsKeyDown(Keys.R))
{
cameraArc = -5;
cameraRotation = 0;
cameraDistance = 200;
}
}
#endregion
}
#region Entry Point
///
/// The main entry point for the application.
///
static class Program
{
static void Main()
{
using (Particle3DSampleGame game = new Particle3DSampleGame())
{
game.Run();
}
}
}
#endregion
}