官术网_书友最值得收藏!

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:

Creating explosions within the Reach profile

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. As with other particle-based effects, we begin with a new particle class:
    class ReachExplosionParticle
    {
    
  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. Next, define the class that will render each particle:
    class ReachExplosionParticleDisplay
    {
    
  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. 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. 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. Create a new class to orchestrate all the particles and their displays:
    class ReachExplosion
    {
    
  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. 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. 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. 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. 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. 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. 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.

主站蜘蛛池模板: 温泉县| 海伦市| 临沧市| 文化| 襄汾县| 讷河市| 台东县| 北碚区| 邢台县| 卢氏县| 龙川县| 盐亭县| 德格县| 福州市| 迁安市| 龙山县| 尖扎县| 阳信县| 广州市| 澜沧| 孟津县| 阿拉善盟| 古蔺县| 武陟县| 香格里拉县| 大关县| 永昌县| 乌什县| 聂荣县| 正阳县| 永善县| 布拖县| 郯城县| 胶南市| 邹平县| 汤阴县| 囊谦县| 海原县| 达孜县| 永寿县| 镇巴县|