Book Image

Microsoft XNA 4.0 Game Development Cookbook

By : Luke Drumm
Book Image

Microsoft XNA 4.0 Game Development Cookbook

By: Luke Drumm

Overview of this book

Microsoft XNA attempts to free game developers from writing "repetitive boilerplate code", allowing them to focus on producing enjoyable gameplay rather than tedious and complicated setup. The Framework has reduced the once steep learning curve for game development, transforming it into something more attainable, and this cookbook will help you to take full advantage of XNA to bring reality into your virtual worlds. "Microsoft XNA 4.0 Game Development Cookbook" is the perfect resource for propelling your game development capabilities from the simple 2D demo towards engaging and exciting, professional looking games. With a diverse selection of game-related topics covered, discover how to create rich 2D and 3D worlds filled with interesting characters, detailed scenery and dynamic special effects applicable to PC, Xbox 360, and Windows Phone 7 game play. There is no shortage of introductory texts available for XNA, a number of which are fantastic for getting started with simple 2D games, but "Microsoft XNA 4.0 Game Development Cookbook"ù will help you take the steps to start producing games that have deeper gameplay, compelling graphics and that little bit of extra polish! The book's recipes will get you up and going quickly with the next level of game features such as 3D graphics, AI, and network play. With this practical cookbook to hand, even the more experienced developer will be better equipped to achieve high level tasks with XNA in a quick and efficient manner.
Table of Contents (15 chapters)
Microsoft XNA 4.0 Game Development Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface

Creating explosions within the Reach profile


If there's one thing I really appreciate, it's explosions that have a visceral and almost palpable presence in a game.

Much of the presence of an explosion tends to come from the more subtle in-game artifacts, such as a deep, complex, sound design and shaking of the camera, and for a lot of games this is more than enough. But sometimes, something that is a little more satisfying is required visually, such as the rolling fireball covered in this recipe and pictured in the following illustration:

Getting ready

In this special effect, you'll need a textured sphere with a flame or lava appearance.

In the following code, there are references to a GeometricBuffer class that can be substituted with any other sort of mesh container. You can find the details of how to construct this kind of mesh programmatically in Chapter 3, Procedural Modeling.

A textured sphere mesh from a modeling package would work equally well.

How to do it...

To create an explosion within the Reach profile:

  1. 1. As with other particle-based effects, we begin with a new particle class:

    class ReachExplosionParticle
    {
    
  2. 2. Start the new class by adding some instance variables to hold its position, age, and other details:

    public Microsoft.Xna.Framework.Matrix World;
    public float Size;
    public float HorizontalAngle;
    public float HorizontalOffset;
    public float VerticleOffset;
    public float Roll;
    public float Age;
    public float AgeDelta;
    
  3. 3. Next, define the class that will render each particle:

    class ReachExplosionParticleDisplay
    {
    
  4. 4. Add some instance-level variables to hold the mesh, effect, and lifetime details of the particle displayed:

    GeometricBuffer<VertexPositionNormalTexture> sphereBuffer;
    BasicEffect sphereEffect;
    Curve alphaCurve;
    Curve lightCurve;
    
  5. 5. Create a constructor to initialize everything:

    public ReachExplosionParticleDisplay(
    GeometricBuffer<VertexPositionNormalTexture> sphereBuffer,
    BasicEffect sphereEffect)
    {
    this.sphereBuffer = sphereBuffer;
    this.sphereEffect = sphereEffect;
    alphaCurve = new Curve();
    alphaCurve.Keys.Add(new CurveKey(0, 0.75f));
    alphaCurve.Keys.Add(new CurveKey(0.125f, 0.5f));
    alphaCurve.Keys.Add(new CurveKey(0.35f, 0.125f));
    alphaCurve.Keys.Add(new CurveKey(1f, 0f));
    lightCurve = new Curve();
    lightCurve.Keys.Add(new CurveKey(0, 1f));
    lightCurve.Keys.Add(new CurveKey(0.2f, 1f));
    lightCurve.Keys.Add(new CurveKey(1f, 0.25f));
    }
    
  6. 6. Add the Draw() method to render the particles and finish the class:

    internal void Draw(
    Matrix view,
    Matrix projection,
    Matrix world,
    float age)
    {
    sphereEffect.View = view;
    sphereEffect.Projection = projection;
    sphereEffect.World = world;
    sphereEffect.Alpha = alphaCurve.Evaluate(age);
    sphereEffect.DiffuseColor =
    Vector3.One * lightCurve.Evaluate(age);
    sphereBuffer.Draw(sphereEffect);
    }
    
  7. 7. Create a new class to orchestrate all the particles and their displays:

    class ReachExplosion
    {
    
  8. 8. Add the instance-level variables to hold the display renderer, the particles, and some animation settings:

    ReachExplosionParticleDisplay particleDisplay;
    List<ReachExplosionParticle> particles;
    Random random = new Random();
    Curve horizontalRateCurve;
    Curve verticleRateCurve;
    private bool exploding;
    
  9. 9. Initialize everything in a constructor:

    public ReachExplosion(
    GraphicsDevice graphicsDevice,
    ContentManager content)
    {
    particleDisplay = ReachExplosionParticleDisplayFactory.Create(
    graphicsDevice, content);
    particles = new List<ReachExplosionParticle>();
    for (var index = 0; index < 100; index++)
    {
    particles.Add(new ReachExplosionParticle());
    }
    horizontalRateCurve = new Curve();
    horizontalRateCurve.Keys.Add(new CurveKey(0, 0f));
    horizontalRateCurve.Keys.Add(new CurveKey(0.025f, 0.8f));
    horizontalRateCurve.Keys.Add(new CurveKey(0.25f, 1f));
    verticleRateCurve = new Curve();
    verticleRateCurve.Keys.Add(new CurveKey(0.2f, 0.1f));
    verticleRateCurve.Keys.Add(new CurveKey(0.3f, 0.25f));
    }
    
  10. 10. Create a variable and a method to indicate when things should start exploding:

    public void Explode()
    {
    foreach (var particle in particles)
    {
    Reset(particle);
    }
    exploding = true;
    }
    
  11. 11. Add the ability to reset particles back to an initial position ready to explode:

    private void Reset(ReachExplosionParticle particle)
    {
    particle.Size = (float)random.NextDouble() * 0.2f;
    particle.HorizontalAngle = (float)random.NextDouble() *
    MathHelper.TwoPi;
    particle.HorizontalOffset = (float)random.NextDouble() *
    0.5f;
    particle.Roll = ((0.4f *
    (float)random.NextDouble()) +
    0.6f) * 2f * MathHelper.TwoPi;
    particle.VerticleOffset = (0.2f *
    (float)random.NextDouble());
    particle.Age = 0f;
    particle.AgeDelta = ((0.6f *
    (float)random.NextDouble()) + 0.4f);
    }
    
  12. 12. Update the state of all the particles through the addition of an Update() method:

    public void Update(GameTime gameTime)
    {
    if (!exploding)
    {
    return;
    }
    var liveParticleCount = 0;
    foreach (var particle in particles)
    {
    if (particle.Age > 1)
    {
    continue;
    }
    particle.Age += particle.AgeDelta *
    (float)gameTime.ElapsedGameTime.TotalSeconds;
    particle.VerticleOffset +=
    verticleRateCurve.Evaluate(particle.Age) *
    (float)gameTime.ElapsedGameTime.TotalSeconds *
    5f;
    particle.Roll +=
    (float)gameTime.ElapsedGameTime.TotalSeconds;
    var horizontalOffset =
    horizontalRateCurve.Evaluate(particle.Age) *
    particle.HorizontalOffset *
    Vector3.Backward;
    var verticleOffset = Vector3.Up *
    particle.VerticleOffset;
    particle.World = Matrix.CreateScale(particle.Size) *
    Matrix.CreateRotationX(particle.Roll) *
    Matrix.CreateTranslation(horizontalOffset) *
    Matrix.CreateRotationY(particle.HorizontalAngle) *
    Matrix.CreateTranslation(verticleOffset);
    liveParticleCount++;
    }
    exploding = liveParticleCount > 0;
    }
    
  13. 13. And completing the class, we come to rendering the particles onscreen:

    public void Draw(
    Matrix view,
    Matrix projection,
    GameTime gameTime)
    {
    if (!exploding)
    {
    return;
    }
    foreach (var particle in particles)
    {
    if (particle.Age > 1)
    {
    continue;
    }
    particleDisplay.Draw(
    view,
    projection,
    particle.World,
    particle.Age);
    }
    }
    
  14. 14. To complete the example, add a factory class to create a new instance of the particle mesh. Here's an example using the GeometricBuffer classes from Chapter 3, Procedural Modeling:

    class ReachExplosionParticleDisplayFactory
    {
    public static ReachExplosionParticleDisplay Create(GraphicsDevice graphicsDevice, ContentManager content)
    {
    var sphereEffect = new BasicEffect(graphicsDevice)
    {
    SpecularColor = Color.Black.ToVector3(),
    DiffuseColor = Color.White.ToVector3(),
    Texture = content.Load<Texture2D>("lava"),
    TextureEnabled = true
    };
    var factory = new VertexPositionNormalTextureGeometricBufferFactory();
    var radius = 1f;
    var vStep = -MathHelper.Pi / 8f;
    var uStep = -MathHelper.TwoPi / 8f;
    for (var v = MathHelper.PiOver2;
    v > -MathHelper.PiOver2;
    v += vStep)
    {
    var nextV = v + vStep;
    var vY = radius * (float)Math.Sin(v);
    var nextVY = (float)Math.Sin(nextV);
    var bandRadius = radius * (float)Math.Cos(v);
    var nextBandRadius = radius *
    (float)Math.Cos(nextV);
    var top = new Vector3(
    bandRadius,
    vY,
    bandRadius);
    var bottom = new Vector3(
    nextBandRadius,
    nextVY,
    nextBandRadius);
    for (var u = MathHelper.Pi;
    u > -MathHelper.Pi;
    u += uStep)
    {
    var nextU = u + uStep;
    var uX = (float)Math.Sin(u);
    var nextUX = (float)Math.Sin(nextU);
    var uZ = (float)Math.Cos(u);
    var nextUZ = (float)Math.Cos(nextU);
    var right = new Vector3(uX, 1f, uZ);
    var left = new Vector3(nextUX, 1f, nextUZ);
    var topLeft = top * left;
    var topRight = top * right;
    var bottomRight = bottom * right;
    var bottomLeft = bottom * left;
    var textureLeft = (float)(
    (nextU + MathHelper.Pi) /
    MathHelper.TwoPi);
    var textureRight = (float)(
    (u + MathHelper.Pi) /
    MathHelper.TwoPi);
    var textureTop = 1f - (float)(
    (v + MathHelper.PiOver2) /
    Math.PI);
    var textureBottom = 1f - (float)(
    (nextV + MathHelper.PiOver2) /
    Math.PI);
    var topLeftNormal =
    Vector3.Normalize(topLeft);
    var topRightNormal =
    Vector3.Normalize(topRight);
    var bottomRightNormal =
    Vector3.Normalize(bottomRight);
    var bottomLeftNormal =
    Vector3.Normalize(bottomLeft);
    factory.AddPane(
    topLeft,
    new Vector2(textureLeft, textureTop),
    topLeftNormal,
    topRight,
    new Vector2(textureRight, textureTop),
    topRightNormal,
    bottomRight,
    new Vector2(textureRight, textureBottom),
    bottomRightNormal,
    bottomLeft,
    new Vector2(textureLeft, textureBottom),
    bottomLeftNormal);
    }
    }
    var sphereBuffer = factory.Create(graphicsDevice);
    sphereBuffer.IsTextureTransparent = true;
    return new ReachExplosionParticleDisplay(
    sphereBuffer, sphereEffect);
    }
    }
    

How it works...

The heart of the ReachExplosion class lies with the use of the XNA Curve class.

Via the Curve class, we can take the relatively random nature of the particle stream and mold it into the visually satisfying shape of a gaseous explosion.

Two instances of the Curve class are created within the constructor of the ReachExplosion class.

The first curve determines the shape of the explosion by expanding or contracting the distance between each particle and the center of the explosion. The second curve determines how quickly the particles rise.

Another two instances of the Curve class can be observed in the ReachExplosionParticleDisplay class controlling the luminescence and transparency of each particle, thereby simulating the particle's transition from flame, to smoke, to thin air.

There's more...

Just like in film, the difference between a convincing explosion and one less so can usually be found not in the explosion itself, but in the impact it has on the world around it.

A deep bass explosion sound along with a small shake of the virtual camera, possibly also achieved via the Curve class, would go a long way towards enhancing the realism of this effect.