using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using Nuclex.Graphics.SpecialEffects.Particles; using Nuclex.Graphics.SpecialEffects.Particles.HighLevel; namespace Nuclex.Graphics.SpecialEffects.Demo { /// /// Demonstrates the capabilities of the Nuclex.SpecialEffects library /// public class SpecialEffectsDemoGame : Microsoft.Xna.Framework.Game { /// Initialiezs a new game public SpecialEffectsDemoGame() { this.graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } /// /// 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-graphics related content. Calling base.Initialize will enumerate through /// any components and initialize them as well. /// protected override void Initialize() { Components.Add(new FpsComponent(this.graphics)); // Initialize any registered game components base.Initialize(); // Set up a camera through which the scene can be viewed this.camera = new Camera( Matrix.CreateLookAt( new Vector3(0.0f, 1.5f, 10.0f), // camera location new Vector3(0.0f, 0.0f, 0.0f), // camera focal point Vector3.Up // up vector for the camera's orientation ), Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, // field of view (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height, // aspect ratio 0.01f, 1000.0f // near and far clipping plane ) ); this.lucidaFont = Content.Load("Lucida"); } /// Will be called when your game shuts down /// /// Whether the shutdown was initiated from user code (as opposed to the GC) /// /// /// If the dispose was initiated by user, any referenced objects we own are /// guaranteed to be alive and can be disposed as well. If the dispose comes /// from the garbage collection, we mustn't access any other objects. We should /// release unmanaged resources in both cases, however. /// protected override void Dispose(bool calledByUser) { if(calledByUser) { if(this.particleManager != null) { this.particleManager.Dispose(); this.particleManager = null; } } base.Dispose(calledByUser); } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. this.spriteBatch = new SpriteBatch(GraphicsDevice); // Load the effect we'll be using to draw the flare particles this.flareTexture = Content.Load("FlareTexture"); this.flareEffect = Content.Load("FlareEffect"); this.flareEffect.Parameters["FlareTexture"].SetValue(this.flareTexture); // Create a particle system manager. This is optional and you can manage your // particle system yourself if you wish. This manager just makes it easier for // us to manage everything. It will automatically manage batched rendering, take // care of the vertex declaration and clean everything up when it's destroyed. this.particleManager = new ParticleSystemManager(this.graphics); // Next, we create a particle system. A particle system is a lightweight // container that holds up to a fixed number of particles (in the interest of // garbage avoidal) and remembers which affectors (eg. wind, gravity, decay) // should be applied. Having several particle systems is quite okay. this.flareParticleSystem = new ParticleSystem(64000); // Add some affectors to the particle system. Affectors modify particles over // time. For example, the gravity affector accellerates particles downwards // to simulate gravity. The movement affector updates a particle's position // by its current velocity. And the decay affector dims the particle slowly. this.flareParticleSystem.Affectors.Add( new GravityAffector(FlareParticleModifier.Default) ); this.flareParticleSystem.Affectors.Add(new DragAffector()); this.flareParticleSystem.Affectors.Add( new MovementAffector(FlareParticleModifier.Default) ); this.flareParticleSystem.Affectors.Add(FlareDecayAffector.Default); #if !XNA_4 // TODO: Write a triangle billboard renderer // Add the particle system to the manager. This will auto-create a vertex // declaration for it, set up a primitive batch for batched rendering and // dispose it upon shutdown. We also specify a pruning delegate that will be // used to detect dead particles that can be cleaned up. this.particleManager.AddParticleSystem( this.flareParticleSystem, // particle system we're adding isParticleAlive, // used to detect dead particles this.flareEffect // effect used to render particles ); #endif } /// /// UnloadContent will be called once per game and is the place to unload /// all content. /// protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// /// 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) { // Allows the game to exit if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // Add some new particles to the system for(int i = 0; i < 60; ++i) { this.flareParticleSystem.AddParticle( new FlareParticle( Vector3.Zero, makeRandomVector() * 10.0f, makeRandomColor() ) ); } // Let the particle system manager update all particle systems. This means // their registered affectors are applied to the particles. This is done // asynchronously and distributed over multiple threads - all you need to // know is that your game can do other things while this is happening as // long as it's not touching the particle systems in any way! IAsyncResult asyncResult = this.particleManager.BeginUpdate( 1, // Number of update cycled to run 2, // Number of threads to use simultaneously null, null // Callback and state (see .NET asynchronous pattern) ); // TODO: Do other stuff here while the particle system is updating! this.camera.HandleControls(gameTime); // Complete the asynchronous update. If the update was still running, this // line will wait until the update is complete. If one of your evil, // badly-written affectors coughed up an exception, it will surface here :) this.particleManager.EndUpdate(asyncResult); // Perform dead particle elimination. If you're getting nowhere close to // the particle limit, you can do also do this asynchronously in the Draw() // method while drawing of everything but the particles takes place. Dead // particles have to be eliminated in a second step to allow the affectors // to be run in multiple threads. this.particleManager.Prune(); // Update other game components base.Update(gameTime); } /// Called when the game should draw itself. /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { //GraphicsDevice.Clear(Color.CornflowerBlue); GraphicsDevice.Clear(Color.Black); // Draws all particle systems registered to the particle manager this.flareEffect.Parameters["WorldViewProjection"].SetValue( this.camera.View * this.camera.Projection ); this.particleManager.Draw(gameTime); base.Draw(gameTime); #if XNA_4 this.spriteBatch.Begin(); #else this.spriteBatch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState ); #endif try { string particlesText = string.Format( "Particle Count: {0}", this.flareParticleSystem.Particles.Count ); this.spriteBatch.DrawString( this.lucidaFont, particlesText, new Vector2(10.0f, 30.0f), Color.Red ); } finally { this.spriteBatch.End(); } } /// Determines whether a flare particle is still alive /// Particle that will be checked /// True if the particle is still alive private static bool isParticleAlive(ref FlareParticle particle) { return (particle.Power > 0.1f); } /// Creates a vector with random values /// A vector with random values private Vector3 makeRandomVector() { return new Vector3( (float)randomNumberGenerator.NextDouble() * 2.0f - 1.0f, (float)randomNumberGenerator.NextDouble() * 2.0f - 1.0f, (float)randomNumberGenerator.NextDouble() * 2.0f - 1.0f ); } /// Creates a color with random values /// A color with random values private Color makeRandomColor() { return new Color( (float)randomNumberGenerator.NextDouble(), (float)randomNumberGenerator.NextDouble(), (float)randomNumberGenerator.NextDouble(), 1.0f ); } /// The random number generator we use private Random randomNumberGenerator = new Random(); /// Texture for the flare private Texture flareTexture; /// Camera through which the scene is being viewed private Camera camera; /// Manages our graphics device private GraphicsDeviceManager graphics; /// Batches simple 2D rendering commands private SpriteBatch spriteBatch; /// Manages the particle systems used in the demonstration private ParticleSystemManager particleManager; /// Particle system for a fireworks display private ParticleSystem flareParticleSystem; /// Effect used to render flare particles private Effect flareEffect; /// Font used to render overlays private SpriteFont lucidaFont; } } // namespace Nuclex.Graphics.SpecialEffects.Demo