Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Microsoft XNA 4.0 Game Development Cookbook
Microsoft XNA 4.0 Game Development Cookbook

Microsoft XNA 4.0 Game Development Cookbook: This book goes further than the basic manuals to help you exploit Microsoft XNA to create fantastic virtual worlds and effects in your 2D or 3D games. Includes 35 essential recipes for game developers.

Arrow left icon
Profile Icon Luke Drumm
Arrow right icon
Free Trial
Full star icon Full star icon Full star icon Full star icon Full star icon 5 (1 Ratings)
Paperback Jun 2012 356 pages 1st Edition
eBook
zł39.99 zł177.99
Paperback
zł221.99
Subscription
Free Trial
Arrow left icon
Profile Icon Luke Drumm
Arrow right icon
Free Trial
Full star icon Full star icon Full star icon Full star icon Full star icon 5 (1 Ratings)
Paperback Jun 2012 356 pages 1st Edition
eBook
zł39.99 zł177.99
Paperback
zł221.99
Subscription
Free Trial
eBook
zł39.99 zł177.99
Paperback
zł221.99
Subscription
Free Trial

What do you get with a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing
Table of content icon View table of contents Preview book icon Preview Book

Microsoft XNA 4.0 Game Development Cookbook

Chapter 1. Applying Special Effects

In this chapter we will cover:

  • Creating shadows within the Reach profile

  • Creating shadows within the HiDef profile

  • Implementing lens flare within the Reach profile

  • Implementing lens flare within the HiDef profile

  • Implementing smoke within the Reach profile

  • Creating explosions within the Reach profile

  • Creating explosions within the HiDef profile

Introduction

In this chapter, we will be creating some of the common special effects used in 3D games to help increase the level of realism, and thereby help the player to immerse themselves in the virtual world.

The examples in this chapter assume that at least the bare bones of a 3D scene are already present, with one or more 3D objects being rendered successfully to the screen. If inspiration for how to achieve this is in short supply, Chapter 2, Building 2D and 3D Terrain and Chapter 3, Procedural Modeling, provide some examples on possible ways to get started.

Introduction


In this chapter, we will be creating some of the common special effects used in 3D games to help increase the level of realism, and thereby help the player to immerse themselves in the virtual world.

The examples in this chapter assume that at least the bare bones of a 3D scene are already present, with one or more 3D objects being rendered successfully to the screen. If inspiration for how to achieve this is in short supply, Chapter 2, Building 2D and 3D Terrain and Chapter 3, Procedural Modeling, provide some examples on possible ways to get started.

Creating shadows within the Reach profile


Shadows are one of the most common ways to make something appear like it is part of the surrounding environment, and for a lot of games written within the Reach profile, a simple static image of a dark patch beneath the character's feet is sufficient, as seen in the following illustration:

There are, however, times where a non-descript blur isn't going to cut it, and a more realistic looking shadow is required.

This recipe will teach you how to create a detailed shadow of an in-game element, as seen in the following illustration, using one of the matrix transformation helper methods supplied in the XNA framework.

Getting ready

We will be using the BasicEffect class in this example, but any effect that implements the IEffectMatrices interface and has some way to darken the rendered model should be adequate.

Prior to adding the code presented in this example, ensure that a model is being loaded and rendered onto the screen.

How to do it...

To create a disc programmatically:

  1. 1. Define an instance-level variable to hold the direction of the virtual light source, another to hold the effect that's going to render the shadow, and a third to hold the transformation to give the shadow its shape:

    Vector3 lightDirection;
    BasicEffect reachShadowEffect;
    Matrix flattenShadow;
    
  2. 2. In the LoadContent() method, define the direction of the light source:

    lightDirection = Vector3.Normalize((Vector3.Backward * 2) +
    (Vector3.Up * 2) +
    (Vector3.Left * 2));
    
  3. 3. Next, define the matrix that will be used to transform objects into their flattened shadow form:

    var flattenShadow = Matrix.CreateShadow(
    lightDirection,
    new Plane(Vector3.Up, 0.95f));
    
  4. 4. Now use a calculation that takes the world matrix used to transform the regular object, and alters it into a transformation to project a shadow onto a flat surface:

    var shadowWorld = world * flattenShadow;
    
  5. 5. Next, implement the effect that will be used to render the shadow:

    reachShadowEffect = new BasicEffect(GraphicsDevice)
    {
    View = view,
    Projection = projection,
    World = shadowWorld,
    AmbientLightColor = Vector3.Zero,
    DiffuseColor = Vector3.Zero,
    SpecularColor = Vector3.Zero,
    Alpha = 0.5f
    };
    
  6. 6. After drawing the game scene's floor, but prior to drawing the object to be shadowed, insert the following code to give the shadow transparency:

    graphicsDevice.BlendState = BlendState.AlphaBlend;
    graphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
    
  7. 7. Drawing the object with the shadow effect will then render a shadow:

    gameObject.Draw(reachShadowEffect);
    
  8. 8. Setting the BlendState and DepthStencilState back to their defaults will allow you to draw the object normally. For example:

    graphicsDevice.BlendState = BlendState.Opaque;
    graphicsDevice.DepthStencilState = DepthStencilState.Default;
    gameObject.Draw(regularEffect);
    
    

Note

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

How it works...

Utilizing one of the built-in transformations of the XNA framework, we are squashing a blackened copy of the mesh onto a given plane.

It's a simple technique, but it does come at the cost of having to render your shadow casting meshes at least one more time than normal. These particular shadows are as stiff as a board too and won't bend if they happen to fall upon a wall or other vertical surface.

If you're after a shadow with a softer edge within the Reach profile, you may want to render the shadow to a separate render target and blur in a similar fashion to the technique demonstrated in the Implementing lens flare within the Reach profile section of this chapter.

There's more...

In the example, a simplified call to a game object's Draw() method was made, passing in the effect to be used in rendering.

If you're interested in how such a method might be constructed, sneak a peek at the Draw() method of the GeometricBuffer class covered in the Modeling triangles recipe in Chapter 3, Procedural Modeling .

See also

  • Implementing lens flare within the Reach profile recipe of this chapter.

Creating shadows within the HiDef profile


Creating realistic-looking shadows without sacrificing a huge amount of memory or processing power remains one of the great challenges in computer graphics. While I may not be able to offer a perfect solution for every shadow-related problem you have in your games, I can at least get you started in the world of shadow creation through the demonstration of one of the more well-known techniques, shadow mapping, seen in the following illustration.

Getting ready

For this recipe, it's best to start with a simple existing scene containing a floor and at least one mesh floating or standing above it, as shown in the previous illustration.

How to do it...

To create a disc programmatically:

  1. 1. Add a new effect file to your game content project named Shadows.fx.

  2. 2. Define the input parameters of the new shader:

    float4x4 World;
    float4x4 View;
    float4x4 Projection;
    float4x4 LightViewProj;
    float3 LightDirection;
    float4 AmbientColor = float4(0.15, 0.15, 0.15, 0);
    float DepthBias = 0.001f;
    texture Texture;
    sampler TextureSampler = sampler_state
    {
    Texture = (Texture);
    };
    texture ShadowMap;
    sampler ShadowMapSampler = sampler_state
    {
    Texture = <ShadowMap>;
    };
    
  3. 3. Define the structures used to pass data between the pixel and vertex shaders:

    struct DrawWithShadowMap_VSIn
    {
    float4 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
    };
    struct DrawWithShadowMap_VSOut
    {
    float4 Position : POSITION0;
    float3 Normal : TEXCOORD0;
    float2 TexCoord : TEXCOORD1;
    float4 WorldPos : TEXCOORD2;
    };
    struct CreateShadowMap_VSOut
    {
    float4 Position : POSITION;
    float Depth : TEXCOORD0;
    };
    
  4. 4. Next, create a vertex shader for rendering a depth map:

    CreateShadowMap_VSOut CreateShadowMap_VertexShader(
    float4 Position: POSITION)
    {
    CreateShadowMap_VSOut Out;
    Out.Position = mul(Position, mul(World, LightViewProj));
    Out.Depth = Out.Position.z / Out.Position.w;
    return Out;
    }
    
  5. 5. Create the vertex shader's partner-in-crime, the pixel shader, to render a depth map:

    float4 CreateShadowMap_PixelShader(
    CreateShadowMap_VSOut input) : COLOR
    {
    return float4(input.Depth, 0, 0, 0);
    }
    
  6. 6. Next, make the vertex shader render the shadows:

    DrawWithShadowMap_VSOut DrawWithShadowMap_VertexShader(DrawWithShadowMap_VSIn input)
    {
    float4x4 WorldViewProj = mul(mul(World, View), Projection);
    DrawWithShadowMap_VSOut Output;
    Output.Position = mul(input.Position, WorldViewProj);
    Output.Normal = normalize(mul(input.Normal, World));
    Output.TexCoord = input.TexCoord;
    Output.WorldPos = mul(input.Position, World);
    return Output;
    }
    
  7. 7. Create the matching pixel shader, which will, for every pixel, compare the depth of the scene from the player's perspective to that of the previously captured shadow depth map:

    float4 DrawWithShadowMap_PixelShader(
    DrawWithShadowMap_VSOut input) : COLOR
    {
    float4 diffuseColor = tex2D(
    TextureSampler, input.TexCoord);
    float diffuseIntensity = saturate(
    dot(LightDirection, input.Normal));
    float4 diffuse = diffuseIntensity *
    diffuseColor + AmbientColor;
    float4 lightingPosition = mul(
    input.WorldPos, LightViewProj);
    float2 ShadowTexCoord = 0.5 * lightingPosition.xy /
    lightingPosition.w +
    float2( 0.5, 0.5 );
    ShadowTexCoord.y = 1.0f - ShadowTexCoord.y;
    float shadowdepth = tex2D(ShadowMapSampler,
    ShadowTexCoord).r;
    float ourdepth = (lightingPosition.z / lightingPosition.w) - DepthBias;
    if (shadowdepth < ourdepth)
    {
    diffuse *= float4(0.5,0.5,0.5,0);
    };
    return diffuse;
    }
    
  8. 8. Add some technique definitions to specify which shader to use in which circumstance:

    technique CreateShadowMap
    {
    pass Pass1
    {
    VertexShader = compile vs_2_0
    CreateShadowMap_VertexShader();
    PixelShader = compile ps_2_0
    CreateShadowMap_PixelShader();
    }
    }
    technique DrawWithShadowMap
    {
    pass Pass1
    {
    VertexShader = compile vs_2_0
    DrawWithShadowMap_VertexShader();
    PixelShader = compile ps_2_0
    DrawWithShadowMap_PixelShader();
    }
    }
    
  9. 9. In your game class, add some instance variables to hold details about the virtual camera:

    Matrix view;
    Matrix projection;
    Matrix world;
    BoundingFrustum cameraFrustum = new BoundingFrustum(Matrix.Identity);
    
  10. 10. Then, add some details about the lighting:

    Vector3 lightDirection;
    Matrix lightViewProjection = Matrix.Identity;
    
  11. 11. Now, add some variables to hold the various effects that are going to be used to render the scene:

    BasicEffect basicEffect;
    Effect hiDefShadowEffect;
    RenderTarget2D shadowRenderTarget;
    
  12. 12. In the LoadContent() method, start by setting up the lighting and camera positions:

    lightDirection = Vector3.Normalize(
    (Vector3.Backward * 2) +
    (Vector3.Up * 2) +
    (Vector3.Left * 2));
    view = Matrix.CreateLookAt(
    (Vector3.Backward * 4) +
    (Vector3.Up * 3) +
    (Vector3.Right),
    Vector3.Zero,
    Vector3.Up);
    projection = Matrix.CreatePerspectiveFieldOfView(
    MathHelper.ToRadians(60f),
    GraphicsDevice.Viewport.AspectRatio,
    0.002f,
    100f);
    world = Matrix.CreateTranslation(Vector3.Zero);
    cameraFrustum.Matrix = view * projection;
    
  13. 13. Continue by creating a render target to hold the shadow map:

    var shadowMapWidthHeight = 2048;
    var pp = GraphicsDevice.PresentationParameters;
    shadowRenderTarget = new
    RenderTarget2D(graphics.GraphicsDevice,
    shadowMapWidthHeight,
    shadowMapWidthHeight,
    false,
    pp.BackBufferFormat,
    DepthFormat.Depth24);
    
  14. 14. Then, set up the effects used to render the objects within the scene, and the shadows cast by them:

    basicEffect = new BasicEffect(GraphicsDevice)
    {
    View = view,
    Projection = projection,
    World = world,
    };
    basicEffect.EnableDefaultLighting();
    hiDefShadowEffect = Content.Load<Effect>("Shadows");
    
  15. 15. Add a new method to calculate the position and size of the virtual camera used to record a depth map from the point of view of the light source:

    Matrix CreateLightViewProjectionMatrix()
    {
    
  16. 16. Insert a matrix into the new method to rotate things towards the direction of the light:

    Matrix lightRotation = Matrix.CreateLookAt(
    Vector3.Zero,
    -lightDirection,
    Vector3.Up);
    
  17. 17. Calculate the corners of the visible area for the "light" camera:

    Vector3[] frustumCorners = cameraFrustum.GetCorners();
    for (int i = 0; i < frustumCorners.Length; i++)
    {
    frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightRotation);
    }
    
  18. 18. Work out the smallest box that could fit the corners of the visible area:

    BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners);
    Vector3 boxSize = lightBox.Max - lightBox.Min;
    Vector3 halfBoxSize = boxSize * 0.5f;
    
  19. 19. From the previously calculated box dimensions, derive the position of the light camera:

    Vector3 lightPosition = lightBox.Min + halfBoxSize;
    lightPosition.Z = lightBox.Min.Z;
    lightPosition = Vector3.Transform(
    lightPosition, Matrix.Invert(lightRotation));
    
  20. 20. Calculate the associated view projection matrix:

    Matrix lightView = Matrix.CreateLookAt(
    lightPosition,
    lightPosition - lightDirection,
    Vector3.Up);
    Matrix lightProjection = Matrix.CreateOrthographic(
    boxSize.X, boxSize.Y,
    -boxSize.Z, boxSize.Z);
    return lightView * lightProjection;
    
  21. 21. Create a new method to set up the shadow effect:

    private void PopulateShadowEffect(bool createShadowMap)
    {
    string techniqueName = createShadowMap ?
    "CreateShadowMap" : "DrawWithShadowMap";
    hiDefShadowEffect.CurrentTechnique = hiDefShadowEffect.Techniques[techniqueName];
    hiDefShadowEffect.Parameters["World"].SetValue(world);
    hiDefShadowEffect.Parameters["View"].SetValue(view);
    hiDefShadowEffect.Parameters["Projection"].SetValue(
    projection);
    hiDefShadowEffect.Parameters["LightDirection"].SetValue(
    lightDirection);
    hiDefShadowEffect.Parameters["LightViewProj"].SetValue(
    CreateLightViewProjectionMatrix());
    if (!createShadowMap)
    hiDefShadowEffect.Parameters["ShadowMap"].SetValue(
    shadowRenderTarget);
    }
    
  22. 22. In your game's Draw() method, start by setting the GraphicsDevice to render to the shadowRenderTarget:

    GraphicsDevice.SetRenderTarget(shadowRenderTarget);
    GraphicsDevice.Clear(Color.White);
    PopulateShadowEffect(true);
    
  23. 23. Next, render any shadow casting objects using the hiDefShadowEffect:

    sphere.Draw(hiDefShadowEffect);
    
  24. 24. Switch the rendering from the shadowRenderTarget, back to the screen:

    GraphicsDevice.SetRenderTarget(null);
    GraphicsDevice.BlendState = BlendState.Opaque;
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    PopulateShadowEffect(false);
    
  25. 25. Set the texture of the hiDefShadowEffect to the corresponding scene object, and render it in a similar fashion to this (where floorTexture has already been loaded with the texture for the floor):

    hiDefShadowEffect.Parameters["Texture"].SetValue(floorTexture);
    floor.Draw(hiDefShadowEffect);
    
  26. 26. For any scene objects you don't want shadows to be cast upon, use a shader such as the BasicEffect shader you created earlier which will do the job nicely:

    basicEffect.Texture = texture;
    basicEffect.TextureEnabled = true;
    sphere.Draw(basicEffect);
    

How it works...

Shadow mapping, for all the code and math involved, really comes down to the relatively simple idea of identifying all the spots in a scene where a player can see something but a light cannot, due to an obstruction blocking the light's view.

A depth map is generated from the light's view of the scene, and another from the player's perspective. The shader darkens any pixels that correspond to the player's view being "deeper" than the light's.

There's more...

In the example given in this recipe, we've set up a 2048 x 2048 24-bit texture to hold our shadow map. Depending on the scene, you may find this is either a waste, or not nearly enough.

In the cases where it's an overkill, don't be afraid to drop the resolution to reclaim some memory for better use elsewhere.

On the other hand, if you find yourself unable to create a large enough shadow map to produce a sufficiently detailed shadow, the addition of blur to a shadow can be a useful tool to diminish or completely eliminate such issues.

See also

  • Creating shadows within the Reach profile recipe of this chapter.

Implementing lens flare within the Reach profile


A realistic lens flare can be achieved within the Reach profile by being "physically correct" and performing a check of each frame to confirm that the source of light is, at least partially, visible from the camera's point of view. (Tests involving whether something is in line of sight from something else are commonly referred to as occlusion tests.)

The bad news is, that without the benefit of hardware accelerated occlusion testing like the HiDef profile's OcclusionQuery class can offer, this test can be beyond the processing resources available to a game (especially one running on either mobile or older hardware).

The good news is that we do have a relatively cheap alternative that may just provide enough approximation for a game, and no one need be the wiser that the "proper" technique wasn't used, as the following illustration of the approximation in use demonstrates:

Getting ready

For this special effect, we're going to need an image that will form the basis of our glow. The image can be a simple white circle that fades out to complete transparency, as shown in the following illustration, but don't be afraid to make things even more visually interesting and swap it out for a more complex image later.

How to do it...

To create a lens flare within the Reach profile:

  1. 1. Start by creating a new class to hold the lens flare behavior:

    class ReachLensFlare
    {
    
  2. 2. Inside the new class, define some instance variables to hold details concerning the appearance of the glow:

    Texture2D glow;
    Vector2 glowOrigin;
    float glowScale = 0.25f;
    Vector2 lightPosition;
    public Vector3 LightDirection = Vector3.Normalize(new Vector3(0.5f, -0.1f, 0.5f));
    
  3. 3. Next, define some instance variables to hold details concerning the rendering of the glow:

    SpriteBatch spriteBatch;
    GraphicsDevice graphicsDevice;
    RenderTarget2D flareTarget;
    List<RenderTarget2D> blurTargets;
    Viewport flareViewport;
    public BasicEffect ShadowCaptureEffect;
    
  4. 4. Then, add a constructor:

    public ReachLensFlare(GraphicsDevice graphicsDevice, ContentManager content)
    {
    
  5. 5. Next, load the glow image details:

    glow = content.Load<Texture2D>(@"lensflare/glow");
    glowOrigin = new Vector2(glow.Width, glow.Height) / 2;
    
  6. 6. We now need to pre-calculate the size of the screen onto which the glow is going to be displayed:

    flareViewport = new Viewport(0,0,
    graphicsDevice.Viewport.Bounds.Width / 8,
    graphicsDevice.Viewport.Height /8);
    
  7. 7. Define the render targets, which will aid in the final composition of the overall effect:

    this.graphicsDevice = graphicsDevice;
    spriteBatch = new SpriteBatch(graphicsDevice);
    var pp = graphicsDevice.PresentationParameters;
    flareTarget = new RenderTarget2D(graphicsDevice,
    flareViewport.Width,
    flareViewport.Height,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat);
    blurTargets = new List<RenderTarget2D>()
    {
    new RenderTarget2D(graphicsDevice,
    3, 5,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat),
    new RenderTarget2D(graphicsDevice,
    7 ,4,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat),
    new RenderTarget2D(graphicsDevice,
    5, 9,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat),
    new RenderTarget2D(graphicsDevice,
    15, 10,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat),
    new RenderTarget2D(graphicsDevice,
    33, 43,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat),
    new RenderTarget2D(graphicsDevice,
    90, 90,
    false,
    pp.BackBufferFormat,
    pp.DepthStencilFormat)
    };
    
  8. 8. Complete the constructor with the effect that will be used to mask out any portions of the lens flare that are blocked by scenery:

    ShadowCaptureEffect = new BasicEffect(graphicsDevice)
    {
    DiffuseColor = Vector3.Zero,
    };
    
  9. 9. Next, add an Update() method to the class:

    public void Update(GameTime gameTime, Matrix view, Matrix projection)
    {
    
  10. 10. Add in a check to determine if the lens flare is visible at all where the player is currently looking:

    view.Translation = Vector3.Zero;
    var projectedPosition = flareViewport.Project(
    -LightDirection, projection,
    view, Matrix.Identity);
    if ((projectedPosition.Z < 0) || (projectedPosition.Z > 1))
    {
    return;
    }
    
  11. 11. If the lens flare is visible from the player's point of view, complete the Update() method by storing the light's screen space position:

    lightPosition = new Vector2(projectedPosition.X, projectedPosition.Y);
    
  12. 12. Create a method to begin the process of capturing the silhouette of the scene:

    public void BeginShadowCapture()
    {
    
  13. 13. Set the render target, clear the silhouette to black, and place the glow image in the background; ready to be possibly covered by scene elements:

    graphicsDevice.SetRenderTarget(flareTarget);
    graphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    spriteBatch.Draw(
    glow, lightPosition, null, Color.White, 0,
    glowOrigin, glowScale, SpriteEffects.None, 0);
    spriteBatch.End();
    
  14. 14. Create the method to complete the silhouette capture and apply some blur:

    public void EndShadowCapture()
    {
    
  15. 15. Paint the captured silhouette scene onto each of the blur targets:

    foreach (var blurTarget in blurTargets)
    {
    graphicsDevice.SetRenderTarget(blurTarget);
    spriteBatch.Begin(
    SpriteSortMode.Deferred,
    BlendState.Opaque,
    SamplerState.AnisotropicClamp, null, null);
    spriteBatch.Draw(
    flareTarget,
    blurTarget.Bounds,
    Color.LightBlue);
    spriteBatch.End();
    }
    
  16. 16. Paint the blur targets onto the final target:

    graphicsDevice.SetRenderTarget(flareTarget);
    graphicsDevice.Clear(Color.Black);
    spriteBatch.Begin(
    SpriteSortMode.Deferred,
    BlendState.Additive,
    SamplerState.AnisotropicClamp,
    null, null);
    foreach (var blurTarget in blurTargets)
    {
    spriteBatch.Draw(
    blurTarget,
    flareTarget.Bounds,
    Color.White);
    }
    spriteBatch.End();
    graphicsDevice.SetRenderTarget(null);
    
  17. 17. Add a Draw() method to render the final target on screen:

    public void Draw()
    {
    spriteBatch.Begin(
    SpriteSortMode.Deferred,
    BlendState.Additive,
    SamplerState.AnisotropicClamp,
    null, null);
    spriteBatch.Draw(
    flareTarget,
    graphicsDevice.Viewport.Bounds,
    Color.White);
    spriteBatch.End();
    }
    
  18. 18. Next in our game code, we add an instance variable for the new lens flare:

    ReachLensFlare reachLensFlare;
    
  19. 19. Initialize an instance of the lens flare in the LoadContent() method:

    reachLensFlare = new ReachLensFlare(GraphicsDevice, Content);
    
  20. 20. Inside the Draw() method, there are two parts. The first part is where we draw the elements of the game scene which are likely to block out the lens flare as the player moves through the scene. Here's an example of the required call to the lens flare, followed by a rendering of the scene with the silhouette effect:

    reachLensFlare.BeginShadowCapture();
    // draw scene here.
    // e.g. here's some building and ground objects
    // being rendered via their own custom Draw methods
    foreach (var building in buildings)
    {
    building.Draw(camera, reachLensFlare.ShadowCaptureEffect);
    }
    ground.Draw(camera, reachLensFlare.ShadowCaptureEffect);
    reachLensFlare.EndShadowCapture();
    
  21. 21. The second part of the rendering process is drawing the scene normally, followed by a call to render the now fully formed lens flare:

    GraphicsDevice.Clear(Color.CornflowerBlue);
    foreach (var building in buildings)
    {
    building.Draw(camera);
    }
    ground.Draw(camera);
    reachLensFlare.Draw();
    

How it works...

The lens flare class works by drawing a silhouette of the scene in front of the light source, our glow image, and then applying various levels of blurring until we achieve a "flare" appearance.

In the constructor of the lens flare class, we can see the elements of this strategy being set up with the loading of the glow image and the creation of some render targets, which will be used later to facilitate blurring.

Also note the creation of the shadow effect, which when used in the drawing of game objects, will make them appear as solid black shapes, perfect for our silhouette needs.

Moving down to the Update() method, the calculation of the glow position in screen space can be spotted. In this case, we're taking advantage of the Project() method on the ViewPort class, which is conveniently designed to do that very calculation for us.

Note

To simulate the sun's distance and size, the view has been altered so that the glow is always just in front of the camera. This step is not required if the lens flare being displayed is in place of something a lot closer, such as a lamp or headlight.

Finally, we have the three methods that form the working heart of the lens flare.

  • The BeginShadowCapture() method sets the render target, so that any rendering performed from this point onwards will be diverted into the flareTarget. It clears the contents of the render target to black, and draws the glow image.

  • Next is the EndShadowCapture() method, which is called by the game after the scene has finished being drawn onto the render target. This is where the blurring process takes place.

    The captured image in the render target is drawn onto each of the blur render targets, and then all of the blur render targets are combined back into one image.

    A combination of the low resolution of each one of the blur render targets, along with the smoothed sampling method used in their combination, gives us our desired blurred effect.

  • The Draw() method performs the important act of actually drawing the newly created blurred glow on the screen.

Note

SpriteBatch alters a few GraphicsDevice settings when it renders to the screen, and this can cause strange side effects in the appearance of any 3D graphics rendered afterwards. If your 3D world stops rendering correctly, be sure to reset the GraphicsDevice.BlendState and GraphicsDevice.DepthStencilState properties upon completing any SpriteBatch operations.

There's more...

This technique for producing lens flares will probably need to be fine-tuned in your game on a case-by-case basis, to achieve the best result and avoid any distracting artifacts produced during its construction.

Altering the resolutions and tints of the blur render targets can make a significant difference to how convincing the effect is.

Implementing lens flare within the HiDef profile


Modern GPUs are very good at determining whether one set of polygons is obscured by another set. We can use this to our advantage when creating lens flares within a HiDef profile.

Getting ready

This recipe assumes that you've already got a scene, rendering correctly, albeit without a lens flare.

How to do it...

To create a lens flare within the HiDef profile:

  1. 1. Create a new class to hold the lens flare behavior:

    class HiDefLensFlare
    {
    
  2. 2. Add some instance-level variables to hold the details of the occlusion test, the lighting, and the glow image:

    SpriteBatch spriteBatch;
    GraphicsDevice graphicsDevice;
    public BasicEffect ShadowCaptureEffect;
    OcclusionQuery occlusionQuery;
    bool occlusionQueryActive;
    float occlusionAlpha;
    const float querySize = 50;
    VertexPositionColor[] queryVertices;
    public Vector3 LightDirection = Vector3.Normalize(new Vector3(0.5f, -0.1f, 0.5f));
    Vector2 lightPosition;
    bool lightBehindCamera;
    Texture2D glow;
    Vector2 glowOrigin;
    float glowScale = 400f;
    
  3. 3. Next, add a constructor to set up the occlusion test prerequisites and load the glow texture:

    public HiDefLensFlare(GraphicsDevice graphicsDevice, ContentManager content)
    {
    this.graphicsDevice = graphicsDevice;
    spriteBatch = new SpriteBatch(graphicsDevice);
    ShadowCaptureEffect = new BasicEffect(graphicsDevice)
    {
    View = Matrix.Identity,
    VertexColorEnabled = true
    };
    occlusionQuery = new OcclusionQuery(graphicsDevice);
    queryVertices = new VertexPositionColor[4];
    queryVertices[0].Position = new Vector3(-querySize / 2, -querySize / 2, -1);
    queryVertices[1].Position = new Vector3(querySize / 2, -querySize / 2, -1);
    queryVertices[2].Position = new Vector3(-querySize / 2, querySize / 2, -1);
    queryVertices[3].Position = new Vector3(querySize / 2, querySize / 2, -1);
    glow = content.Load<Texture2D>(@"lensflare/glow");
    glowOrigin = new Vector2(glow.Width, glow.Height) / 2;
    }
    
  4. 4. Create a new BlendState instance-level variable so the ocular test can proceed without changing the visible image:

    static readonly BlendState ColorWriteDisable = new BlendState
    {
    ColorWriteChannels = ColorWriteChannels.None
    };
    
  5. 5. Add a new method to perform the ocular test:

    public void Measure(Matrix view, Matrix projection)
    {
    
  6. 6. Calculate the position of the lens flare on screen, and exit early if it's behind the player's viewpoint:

    var infiniteView = view;
    infiniteView.Translation = Vector3.Zero;
    var viewport = graphicsDevice.Viewport;
    var projectedPosition = viewport.Project(
    -LightDirection, projection,
    infiniteView, Matrix.Identity);
    if ((projectedPosition.Z < 0) ||
    (projectedPosition.Z > 1))
    {
    lightBehindCamera = true;
    return;
    }
    lightPosition = new Vector2(projectedPosition.X, projectedPosition.Y);
    lightBehindCamera = false;
    
  7. 7. Add the calculation for how much of the lens flare test area is occluded by the scene once the previous occlusion test has completed:

    if (occlusionQueryActive)
    {
    if (!occlusionQuery.IsComplete)
    {
    return;
    }
    const float queryArea = querySize * querySize;
    occlusionAlpha = Math.Min(
    occlusionQuery.PixelCount / queryArea, 1);
    }
    
  8. 8. Set up for the next occlusion query:

    graphicsDevice.BlendState = ColorWriteDisable;
    graphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
    ShadowCaptureEffect.World = Matrix.CreateTranslation(
    lightPosition.X,
    lightPosition.Y, 0);
    ShadowCaptureEffect.Projection = Matrix.CreateOrthographicOffCenter(0,
    viewport.Width,
    viewport.Height,
    0, 0, 1);
    ShadowCaptureEffect.CurrentTechnique.Passes[0].Apply();
    
  9. 9. Render the lens flare test vertices inside the occlusion test to determine how many pixels were rendered:

    occlusionQuery.Begin();
    graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, queryVertices, 0, 2);
    occlusionQuery.End();
    occlusionQueryActive = true;
    
  10. 10. Complete the class by adding a Draw() method to render the glow:

    public void Draw()
    {
    if (lightBehindCamera || occlusionAlpha <= 0)
    return;
    Color color = Color.White * occlusionAlpha;
    Vector2 origin = new Vector2(glow.Width, glow.Height) / 2;
    float scale = glowScale * 2 / glow.Width;
    spriteBatch.Begin();
    spriteBatch.Draw(glow, lightPosition, null, color, 0,
    origin, scale, SpriteEffects.None, 0);
    spriteBatch.End();
    }
    

How it works...

XNA and the underlying DirectX infrastructure contain a rather handy little diagnostic tool in the form of the occlusion test. With this test, you can count how many pixels were filled when trying to render a particular portion of a scene.

We utilize this in the lens flare example by attempting to render a small rectangle across the opposite side of the scene from the player's viewpoint, and measuring how much of it is obscured by the scene's meshes. With this number, we adjust the opacity of the lens flare's glow texture up or down to simulate the sun disappearing either partially or completely behind an object.

Implementing smoke within the Reach profile


If implemented in a naive fashion, rendering smoke could place a significant burden on the hardware of a device running a game under the Reach profile.

In this recipe, you'll learn a method for improving the distribution of work and data between the CPU and GPU, to hopefully get as close as possible to the unachievable goal of hardware instancing in an environment that doesn't allow for custom shaders.

In the following illustration, you can see this recipe in use within a stylized city scene:

Getting ready

An image of a smoke "particle" is required for this special effect, but don't feel pressured to spend too much time or effort creating anything too elaborate. Although it forms the heart of the display, the technique's repetition and distortion of even the most basic smoke pattern can result in some quite intricate and realistic results.

How to do it...

To create smoke within the Reach Profile:

  1. 1. Start by creating a new smoke particle class:

    class ReachSmokeParticle
    {
    
  2. 2. Insert instance variables for the position and age of the particle:

    public Vector3 Position;
    public float PositionDelta;
    public float Scale;
    public float Rotation;
    public float RotationDelta;
    public float Age;
    public float AgeDelta;
    public bool Visible;
    public Texture2D Texture;
    public Vector2 TextureOrigin;
    
  3. 3. Add an Update() method to calculate a particle's details:

    public void Update(
    GameTime gameTime,
    Vector3 wind,
    Vector3 spawnPoint,
    float spawnRadius,
    Random random)
    {
    var timeScale = (float)gameTime.ElapsedGameTime.TotalSeconds;
    Position += ((Vector3.Up * PositionDelta) + wind) *
    timeScale;
    Rotation += RotationDelta * timeScale;
    Age += AgeDelta * timeScale;
    if (Age > 1)
    {
    var offset = ((.5f - (float)random.NextDouble()) *
    (Vector3.Right * spawnRadius)) +
    ((.5f - (float)random.NextDouble()) *
    (Vector3.Forward * spawnRadius));
    Position = spawnPoint + offset;
    Age = 0;
    Visible = true;
    }
    }
    
  4. 4. Continue by adding a Draw() method:

    public void Draw(
    SpriteBatch spriteBatch,
    Viewport viewport,
    Matrix view,
    Matrix projection,
    float projectedScale)
    {
    if (!Visible)
    {
    return;
    }
    var projectedPosition = viewport.Project(
    Position,
    projection,
    view,
    Matrix.Identity);
    var screenPosition = new Vector2(
    projectedPosition.X, projectedPosition.Y);
    var tint = Color.FromNonPremultiplied(
    255, 255, 255, 255 - (int)(255f * Age));
    var displayScale = Scale * projectedScale;
    spriteBatch.Draw(
    Texture, screenPosition,
    null, tint, Rotation, TextureOrigin,
    displayScale,
    SpriteEffects.None, projectedPosition.Z);
    }
    
  5. 5. Now, add the class that will be emitting the freshly defined particles:

    class ReachSmoke
    {
    
  6. 6. Insert some instance variables to the new class to hold the details of the particles:

    SpriteBatch spriteBatch;
    Texture2D smoke;
    Vector2 halfSmokeSize;
    List<ReachSmokeParticle> particles;
    Vector3 spawnPoint = Vector3.Zero;
    float spawnRadius = 0.2f;
    Random random = new Random();
    Vector3 wind = Vector3.Right * 0.3f;
    
  7. 7. Add a constructor to create instances of all the particles:

    public ReachSmoke(
    GraphicsDevice graphicsDevice,
    ContentManager content)
    {
    spriteBatch = new SpriteBatch(graphicsDevice);
    smoke = content.Load<Texture2D>("smoke/smoke");
    halfSmokeSize = new Vector2(
    smoke.Width / 2, smoke.Height / 2);
    var particleCount = 300;
    particles = new List<ReachSmokeParticle>();
    for (var index = 0; index < particleCount; index++)
    {
    var particle = new ReachSmokeParticle()
    {
    Texture = smoke,
    TextureOrigin = halfSmokeSize,
    Position = spawnPoint,
    PositionDelta =
    (0.8f * (float)random.NextDouble()) + .2f,
    Scale =
    (0.8f * (float)random.NextDouble()) + .2f,
    Rotation = (float)random.NextDouble(),
    RotationDelta = 0.5f - (float)random.NextDouble(),
    Age = (float)random.NextDouble(),
    AgeDelta =
    (0.8f * (float)random.NextDouble()) + .2f,
    Visible = false
    };
    particles.Add(particle);
    }
    }
    
  8. 8. Add an Update() method to update all the particles to their latest positions:

    public void Update(GameTime gameTime)
    {
    foreach (var particle in particles)
    {
    particle.Update(
    gameTime,
    wind,
    spawnPoint, spawnRadius,
    random);
    }
    }
    
  9. 9. Finish the class with the addition of the Draw() method to render the particles onto the screen:

    public void Draw(
    Matrix view, Matrix projection, Matrix world,
    Viewport viewport)
    {
    var scaleTestPositionOne = viewport.Project(
    Vector3.Zero,
    projection, view, Matrix.Identity);
    if (scaleTestPositionOne.Z < 0)
    {
    return;
    }
    var scaleTestPositionTwo = viewport.Project(
    Vector3.Up + Vector3.Right,
    projection, view, Matrix.Identity);
    var projectedScale = Vector3.Distance(
    scaleTestPositionOne, scaleTestPositionTwo) /
    (smoke.Height * 2);
    if (projectedScale > 5f)
    {
    return;
    }
    spriteBatch.Begin(
    SpriteSortMode.Deferred,
    BlendState.AlphaBlend, null,
    DepthStencilState.DepthRead, null);
    foreach (var particle in particles)
    {
    particle.Draw(spriteBatch,
    viewport, view, projection, projectedScale);
    }
    spriteBatch.End();
    }
    

How it works...

Inspecting the constructor of the ReachSmoke class, we can see the creation of the smoke particles.

A diverse selection of random sizes, speeds, and states throughout the particles lessens the chance of players being able to spot any obvious signs of repetition, despite the use of only one texture across all of the particles.

In order to lessen the chance of unwanted pressure on the garbage collector, we create a set number of particles at the beginning and recycle them as required.

Within the Update() method of the ReachSmokeParticle class, the code to move and rotate each particle can be seen along the recycling process that re-spawns a particle once it has reached the end of its life.

Eagle-eyed readers may notice that a given particle's visibility isn't enabled until it has re-spawned at least once. This delay in visibility is done to give the particles the best chance of appearing in a reasonably even manner, and avoid one large, unrealistic clump at the beginning.

To enjoy the best chance of maximum performance, the Draw() methods of both the ReachSmoke and ReachSmokeParticle classes are designed with the idea of harnessing the optimizations present in the SpriteBatch class.

With this in mind, the transformation from 3D world space into 2D screen space is performed on the CPU via .NET code. This allows the GPU to receive and display the particles in one unified update, without the need to recalculate or reload.

Handling the 3D calculations ourselves presents two issues:

  • The first is that of perspective and the need to scale, which is dependent on how far away the viewer is from the smoke.

    A measurement of the distance between screen positions of two known points in 3D space is made at the start of the Draw() method of ReachSmoke, to help approximate the scaling of perspective.

  • The second problem is of depth, where the SpriteBatch class is more commonly called upon to draw images over the top of a 3D scene rather than within it.

    Thankfully, the Draw() method of the SpriteBatch class has an override that allows us to specify the depth of a given texture, and use this to ensure that particles appear correctly in front of, and behind other elements in a 3D scene.

There's more...

With some tweaking of the numbers and the texture, a variety of effects from flame, to steam, and even bubbles, can be achieved with a similar approach.

One addition that can really improve the realism of particle-based effects such as smoke, is animating the texture of each particle as it moves through the scene.

Examples of this can be found in games such as Naughty Dog's Uncharted 3, where they utilized an additional texture as a "movement map", where the color of each pixel in the movement map dictated the direction and intensity of the translation/distortion applied to the corresponding pixel in the texture map.

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.

Creating explosions within the HiDef profile


One of the more visceral elements of a good explosion is the shockwave that races ahead of the flames. Extracting and modifying one of the standard Microsoft examples of distortion mapping can let us enjoy the rather gratifying joy of shockwave-laden explosions in our own games.

Getting ready

This recipe assumes you have access to a spherical mesh and some sort of flame texture. It was originally written with the sphere generator presented in the Modeling spheres recipe of Chapter 3, Procedural Modeling, but should work equally well with any other method of creating spheres. Where you find the call to a CreateSphere() method in the following example code, feel free to replace that with your own.

Likewise, any references to GeometricBuffer classes of Chapter 3, Procedural Modeling can be substituted for any other mesh container style classes.

How to do it...

To create an explosion within the HiDef profile:

  1. 1. Add a new effect file to your game's content project, named DistortionGenerator.fx.

  2. 2. Inside the new file, clear any existing content and add the input parameters:

    float4x4 WorldViewProjection;
    float4x4 WorldView;
    float DistortionScale;
    
  3. 3. Define the structure of the data that passes between the vertex and pixel shaders:

    struct PositionNormal
    {
    float4 Position : POSITION;
    float3 Normal : NORMAL;
    };
    struct PositionDisplacement
    {
    float4 Position : POSITION;
    float2 Displacement : TEXCOORD;
    };
    
  4. 4. Add a vertex shader that calculates the distortion on behalf of the pixel shader based upon a vertex's normal:

    PositionDisplacement PullIn_VertexShader(PositionNormal input)
    {
    PositionDisplacement output;
    output.Position = mul(input.Position, WorldViewProjection);
    float3 normalWV = mul(input.Normal, WorldView);
    normalWV.y = -normalWV.y;
    float amount = dot(normalWV, float3(0,0,1)) * DistortionScale;
    output.Displacement = float2(.5,.5) + float2(amount * normalWV.xy);
    return output;
    }
    
  5. 5. Next, add the corresponding pixel shader that emits the distorted normal as a color:

    float4 DisplacementPassthrough_PixelShader(float2 displacement : TEXCOORD) : COLOR
    {
    return float4(displacement, 0, 1);
    }
    
  6. 6. Complete the file by tying the vertex and pixel shader together into a technique:

    technique PullIn
    {
    pass
    {
    VertexShader = compile vs_2_0 PullIn_VertexShader();
    PixelShader = compile ps_2_0 DisplacementPassthrough_PixelShader();
    }
    }
    
  7. 7. Create a new effect file named DistortionApplicator.fx and add it to your content project.

  8. 8. Specify the effect's inputs to receive two textures and the weighting arrays for a Gaussian blur:

    sampler SceneTexture : register(s0);
    sampler DistortionMap : register(s1);
    #define SAMPLE_COUNT 15
    float2 SampleOffsets[SAMPLE_COUNT];
    float SampleWeights[SAMPLE_COUNT];
    
  9. 9. Add a constant to help in the translation of a zero amount when moving between 0-1 and 0-255 ranges:

    const float ZeroOffset = 0.5f / 255.0f;
    
  10. 10. Include a pixel shader that renders a distorted and possibly blurred version of the scene texture if the distortion map texture is any color other than black:

    float4 Distort_PixelShader(float2 TexCoord : TEXCOORD0,
    uniform bool distortionBlur) : COLOR0
    {
    float2 displacement = tex2D(DistortionMap, TexCoord).rg;
    float4 finalColor = 0;
    if ((displacement.x == 0) && (displacement.y == 0))
    {
    finalColor = tex2D(SceneTexture, TexCoord);
    }
    else
    {
    displacement -= .5 + ZeroOffset;
    if (distortionBlur)
    {
    for (int i = 0; i < SAMPLE_COUNT; i++)
    {
    finalColor += tex2D(
    SceneTexture,
    TexCoord.xy +
    displacement +
    SampleOffsets[i]) *
    SampleWeights[i];
    }
    }
    else
    {
    finalColor = tex2D(SceneTexture, TexCoord.xy + displacement);
    }
    }
    return finalColor;
    }
    
  11. 11. Add two techniques to allow the pixel shader to be used with or without blurring:

    technique Distort
    {
    pass
    {
    PixelShader = compile ps_2_0 Distort_PixelShader(false);
    }
    }
    technique DistortBlur
    {
    pass
    {
    PixelShader = compile ps_2_0 Distort_PixelShader(true);
    }
    }
    
  12. 12. In your game project, create a new factory class to produce explosions:

    class HiDefExplosionFactory
    {
    
  13. 13. Add a static method to create new explosions:

    public static HiDefExplosion Create(
    GraphicsDevice graphicsDevice,
    ContentManager content)
    {
    var buffer = CreateSphere(graphicsDevice);
    var occlusionEffect = new BasicEffect(graphicsDevice)
    {
    DiffuseColor = Color.Black.ToVector3()
    };
    var flameEffect = new BasicEffect(graphicsDevice)
    {
    DiffuseColor = Color.White.ToVector3(),
    Texture = content.Load<Texture2D>("explosion/lava"),
    TextureEnabled = true,
    Alpha = 0.5f
    };
    var distortionGeneratorEffect = content.Load<Effect>("explosion/DistortionGenerator");
    var distortionApplicatorEffect = content.Load<Effect>("explosion/DistortionApplicator");
    return new HiDefExplosion(
    graphicsDevice,
    buffer,
    occlusionEffect ,
    flameEffect,
    distortionGeneratorEffect,
    distortionApplicatorEffect);
    }
    
  14. 14. Create a new Explosion class:

    class HiDefExplosion
    {
    
  15. 15. Add the instance-level variables that will be used to render the flames:

    GeometricBuffer<VertexPositionNormalTexture> buffer;
    BasicEffect flameEffect;
    RenderTarget2D sceneRenderTarget;
    
  16. 16. Declare the instance-level variables that will be used to render the shockwave:

    RenderTarget2D distortionRenderTarget;
    public BasicEffect OcclusionEffect;
    Effect distortionGeneratorEffect;
    Effect distortApplicatorEffect;
    
  17. 17. Append the instance-level variables used to display the completed animation:

    SpriteBatch spriteBatch;
    float explosionLifeSpanSeconds;
    Curve sizeCurve;
    Curve flameAlphaCurve;
    public Matrix World;
    private bool exploding;
    private double explosionStartTime;
    private double explosionEndTime;
    private float flameAlpha;
    private const float blurAmount = 1.25f;
    
    
  18. 18. Add a constructor:

    public HiDefExplosion(
    GraphicsDevice graphicsDevice,
    GeometricBuffer<VertexPositionNormalTexture> buffer,
    BasicEffect occlusionEffect,
    BasicEffect flameEffect,
    Effect distortersEffect,
    Effect distortEffect)
    {
    
  19. 19. Inside the constructor, populate the instance- level variables:

    this.buffer = buffer;
    OcclusionEffect = occlusionEffect;
    this.flameEffect = flameEffect;
    this.distortionGeneratorEffect = distortersEffect;
    this.distortApplicatorEffect = distortEffect;
    
  20. 20. Set up the pieces required to capture and display the various elements of the explosion:

    spriteBatch = new SpriteBatch(graphicsDevice);
    var pp = graphicsDevice.PresentationParameters;
    sceneRenderTarget = new RenderTarget2D(graphicsDevice,
    pp.BackBufferWidth, pp.BackBufferHeight,
    false, pp.BackBufferFormat, pp.DepthStencilFormat);
    distortionRenderTarget = new RenderTarget2D(graphicsDevice,
    pp.BackBufferWidth, pp.BackBufferHeight,
    false, pp.BackBufferFormat, pp.DepthStencilFormat);
    
  21. 21. Populate the Gaussian blur weight arrays of the distortion effect:

    SetBlurEffectParameters(
    1f / (float)pp.BackBufferWidth,
    1f / (float)pp.BackBufferHeight);
    
  22. 22. Create the timings for the explosion animation:

    explosionLifeSpanSeconds = 5f;
    sizeCurve = new Curve();
    sizeCurve.Keys.Add(new CurveKey(0, 0.1f));
    sizeCurve.Keys.Add(new CurveKey(0.75f, 5f));
    flameAlphaCurve = new Curve();
    flameAlphaCurve.Keys.Add(new CurveKey(0, 0f));
    flameAlphaCurve.Keys.Add(new CurveKey(0.05f, 1f));
    flameAlphaCurve.Keys.Add(new CurveKey(0.15f, 0f));
    
  23. 23. Add a method to begin capturing a scene into the sceneRenderTarget:

    public void BeginSceneCapture(GraphicsDevice graphicsDevice)
    {
    graphicsDevice.SetRenderTarget(sceneRenderTarget);
    }
    
  24. 24. Create a method to render the flame as a final element onto the scene, before switching the rendering from the sceneRenderTarget back to the screen:

    public void EndSceneCapture(
    GraphicsDevice graphicsDevice,
    Matrix view,
    Matrix projection)
    {
    if (exploding)
    {
    flameEffect.View = view;
    flameEffect.Projection = projection;
    flameEffect.World = World;
    flameEffect.Alpha = flameAlpha;
    // draw explosion particle.
    // e.g. here's how it would be done using
    // Chapter 3's GeometricBuffer classes
    buffer.IsTextureTransparent = true;
    buffer.Draw(flameEffect);
    }
    graphicsDevice.SetRenderTarget(null);
    }
    
  25. 25. Next up is the method to begin capturing any objects within a scene that may be occluding the explosion from the player:

    public void BeginOcclusionCapture(
    GraphicsDevice graphicsDevice)
    {
    if (!exploding)
    {
    return;
    }
    graphicsDevice.SetRenderTarget(distortionRenderTarget);
    graphicsDevice.Clear(Color.Black);
    }
    
  26. 26. Add the method to render the distortion effect into a render target, and shift the rendering to the screen once more:

    public void EndOcclusionCapture(
    GraphicsDevice graphicsDevice,
    Matrix view,
    Matrix projection)
    {
    if (!exploding)
    {
    return;
    }
    Matrix meshWorldView = Matrix.CreateScale(1.5f) * World * view;
    distortionGeneratorEffect.CurrentTechnique =
    distortionGeneratorEffect.Techniques["PullIn"];
    distortionGeneratorEffect.Parameters["WorldView"].SetValue(meshWorldView);
    distortionGeneratorEffect.Parameters["WorldViewProjection"].SetValue(
    meshWorldView * projection);
    distortionGeneratorEffect.Parameters["DistortionScale"].SetValue(
    0.0125f);
    buffer.Draw(distortionGeneratorEffect);
    graphicsDevice.SetRenderTarget(null);
    }
    
  27. 27. Specify the method and the associated variables to start the animation:

    public void Explode(GameTime gameTime)
    {
    exploding = true;
    explosionStartTime = gameTime.TotalGameTime.TotalSeconds;
    explosionEndTime = explosionStartTime +
    explosionLifeSpanSeconds;
    }
    
  28. 28. Insert an Update() method to play the animation once it has begun:

    public void Update(GameTime gameTime)
    {
    if (!exploding)
    {
    return;
    }
    if (gameTime.TotalGameTime.TotalSeconds >= explosionEndTime)
    {
    exploding = false;
    return;
    }
    var explosionTimeOffset = gameTime.TotalGameTime.TotalSeconds - explosionStartTime;
    World = Matrix.CreateScale(sizeCurve.Evaluate((float)explosionTimeOffset)) *
    Matrix.CreateTranslation(Vector3.Zero);
    flameAlpha = flameAlphaCurve.Evaluate((float)explosionTimeOffset);
    }
    
  29. 29. Add the Draw() method to render the scene along with any explosion that may be in progress:

    public void Draw(GraphicsDevice graphicsDevice)
    {
    if (exploding)
    {
    spriteBatch.Begin(0, BlendState.Opaque, null, null, null, distortApplicatorEffect);
    distortApplicatorEffect.CurrentTechnique = distortApplicatorEffect.Techniques["DistortBlur"];
    graphicsDevice.Textures[1] = distortionRenderTarget;
    graphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
    }
    else
    {
    spriteBatch.Begin();
    }
    var viewport = graphicsDevice.Viewport;
    spriteBatch.Draw(
    sceneRenderTarget,
    new Rectangle(0, 0, viewport.Width, viewport.Height),
    Color.White);
    spriteBatch.End();
    }
    
  30. 30. Finish the class with methods to calculate Gaussian blur weightings:

    void SetBlurEffectParameters(float dx, float dy)
    {
    EffectParameter weightsParameter, offsetsParameter;
    weightsParameter = distortApplicatorEffect.
    Parameters["SampleWeights"];
    offsetsParameter = distortApplicatorEffect.
    Parameters["SampleOffsets"];
    int sampleCount = weightsParameter.Elements.Count;
    float[] sampleWeights = new float[sampleCount];
    Vector2[] sampleOffsets = new
    Vector2[sampleCount];
    sampleWeights[0] = ComputeGaussian(0);
    sampleOffsets[0] = new Vector2(0);
    float totalWeights = sampleWeights[0];
    for (int i = 0; i < sampleCount / 2; i++)
    {
    float weight = ComputeGaussian(i + 1);
    sampleWeights[i * 2 + 1] = weight;
    sampleWeights[i * 2 + 2] = weight;
    totalWeights += weight * 2;
    float sampleOffset = i * 2 + 1.5f;
    var delta = new Vector2(dx, dy) *
    sampleOffset;
    sampleOffsets[i * 2 + 1] = delta;
    sampleOffsets[i * 2 + 2] = -delta;
    }
    HiDef profileexplosions, creatingfor (int i = 0; i < sampleWeights.Length; i++)
    {
    sampleWeights[i] /= totalWeights;
    }
    weightsParameter.SetValue(sampleWeights);
    offsetsParameter.SetValue(sampleOffsets);
    }
    static float ComputeGaussian(float n)
    {
    return (float)((1.0 /
    Math.Sqrt(2 * Math.PI * blurAmount)) *
    Math.Exp(-(n * n) /
    (2 * blurAmount * blurAmount)));
    }
    

How it works...

I find it easiest to imagine how this recipe works in terms of how I might try to achieve a similar effect using more traditional real-world artistic techniques.

For example, if this was an airbrushed artwork, I might paint the surrounding scene first, apply masking tape to any area where I didn't want the explosion to appear, spray a fiery design over the top, remove the tape, and voila! A scene with an explosion is produced.

Between the BeginSceneCapture() and EndSceneCapture() methods is where we draw the surrounding scene.

Next, we create a mask by painting the shape of the explosion amongst a completely blackened version of our scene, through calling the BeginOcclusionCapture() and EndOcclusionCapture() methods, and the DistortionGenerator effect.

Rendering this "heat map" amongst a blackened version of the scene means that any elements of the scene that would normally obscure the explosion are rendered as black, over the top of the explosion shape, thereby masking those portions out.

The color we fill the explosion shape in with is not the final color of the rendered explosion though. Instead, it is a sort of a "heat map", indicating the direction and amount of distorted explosive effect that will be applied to each pixel in the final image.

Inside the Draw() method is where we bring all the elements together with the help of the DistortionApplicator effect.

Taking note of which pixels are black and which are not within the masked image, the DistortionApplicator effect renders the original scene image with the appropriate amount of distortion in each area, thereby achieving the final result: a scene with an explosive bubble of distortion flooding across the landscape.

There's more...

The draw, mask, and combine technique demonstrated in this recipe is the foundation of a vast array of effects found in games.

Apply the distortions to a flattened pane and you're half way towards producing a patch of water. Change the intensity of the distortion based upon the depth of the original scene, and you're very close to a passable focal blur effect.

See also...

  • Rendering water within the HiDef profile recipe in Chapter 4, Creating Water and Sky.

Left arrow icon Right arrow icon

Key benefits

  • Accelerate your XNA learning with a myriad of tips and tricks to solve your everyday problems
  • Get to grips with adding special effects, virtual atmospheres and computer controlled characters with this book and e-book
  • A fast-paced cookbook packed with screenshots to illustrate each advanced step by step task
  • Apply the techniques learned for wiring games for PC, Xbox 360 and Windows Phone 7

Description

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.

Who is this book for?

If you are an XNA developer who has already successfully mastered simple 2D and 3D XNA tasks, dive into "Microsoft XNA 4.0 Game Development Cookbook"ù for something more challenging. You should be comfortable with the basics of the XNA framework, and have experience with C#.

What you will learn

  • Dive straight in to creating Special Effects like shadows, smoke and explosions
  • Get up to speed with creating both 2D and 3D terrain, including height maps and block worlds
  • Create more natural character animation with shortcuts like motion capture through Kinect and Rag Doll physics
  • Create 3D objects like spheres, tori and trees with code instead of modeling software
  • Add atmosphere to your world with virtual water, sky and clouds
  • Understand how to set the foundation for multiplayer gaming with networking

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jun 22, 2012
Length: 356 pages
Edition : 1st
Language : English
ISBN-13 : 9781849691987
Vendor :
Microsoft
Languages :

What do you get with a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing

Product Details

Publication date : Jun 22, 2012
Length: 356 pages
Edition : 1st
Language : English
ISBN-13 : 9781849691987
Vendor :
Microsoft
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just zł20 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just zł20 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 641.97
XNA 4.0 Game Development by Example: Beginner's Guide
zł197.99
Microsoft XNA 4.0 Game Development Cookbook
zł221.99
XNA 4 3D Game Development by Example: Beginner's Guide
zł221.99
Total 641.97 Stars icon
Banner background image

Table of Contents

9 Chapters
Applying Special Effects Chevron down icon Chevron up icon
Building 2D and 3D Terrain Chevron down icon Chevron up icon
Procedural Modeling Chevron down icon Chevron up icon
Creating Water and Sky Chevron down icon Chevron up icon
Non-Player Characters Chevron down icon Chevron up icon
Playing with Animation Chevron down icon Chevron up icon
Creating Vehicles Chevron down icon Chevron up icon
Receiving Player Input Chevron down icon Chevron up icon
Networking Chevron down icon Chevron up icon

Customer reviews

Rating distribution
Full star icon Full star icon Full star icon Full star icon Full star icon 5
(1 Ratings)
5 star 100%
4 star 0%
3 star 0%
2 star 0%
1 star 0%
Caleb Sep 27, 2012
Full star icon Full star icon Full star icon Full star icon Full star icon 5
If you are considering purchasing Microsoft XNA 4.0 Game Development Cookbook by Luke Drumm, I would highly recommend it with one caveat: this is not for the absolute beginner. You should have a decent basis in 2D programming and some experience in 3D. I feel Mr. Drumm has propelled me from being an advanced beginner to an intermediate game programmer as I now understand much more of the logic that goes into game creation. Buy the book, work the solutions and study the logic that went into creating these recipes, then experiment! Mr. Drumm will help you take your games to another level both visually and interactively as you work through fascinating recipes and design stunning effects, create interactive AI, and implement physics. To understand my review, you need to understand my background a little. I am a fairly new to the world of programming (accountant by trade). I started off with very basic stuff 3 years ago in Microsoft Excel and have really advanced into making small business applications, but I was bored with that (accounting is boring work, especially programming for accountants). I began learning C# and the XNA framework 1 year ago (I took 8 months off to take the CPA exam), and have been grasping at straws trying to advance. I have a basic understanding from tons of research and a couple of books that I have read, but was looking to take my skills further and begin developing in 3D. That is when I found Luke Drumm's book. Upon obtaining this book, I felt overwhelmed. After trying for a few hours and failing, I figured out that the book, while very written well, should be reorganized just a bit to eliminate the same confusion I had. To all new readers of this book, start with Chapter 3! After my initial confusion and not understanding this stuff, I was able plug through it and find out just how amazing these recipes are. I particularly like the sections where Mr. Drumm explains how it works. For myself personally, I found it easier (to understand why I was doing something and results it makes) to read the "how it works" part of each section before generating the code. I definitely see the benefit of incorporating many of these recipes into my games as you do not have to deal with the frustration of model imports and it can be extremely easy to randomly generate customized and/or random objects on your screen with the code designed elements developed here. These recipes create some very aesthetically pleasing elements. The special effects are ones that I will use in conjunction with one another on a regular basis. They are realistic looking and add a whole new dimension to my games. Between solar flare, explosions, and smoke, I can create many mainstream effects now with minimal effort. Up to this point, I was only ever able to create extremely simple 3D operations, but with the addition of some of these cookbook items, it really doesn't look like a novice created them. Some of the AI pieces involved here seem pretty complex, but they form the basis for enemies and interactive pieces within the game structure. This book has proven immensely helpful to me in this area. The section on decentralized behavior was quite interesting and I found the concept to be quite challenging. I look forward to developing my first full 3D game and implementing networking and multiplayer capabilities. I am now jumping head first into the exciting (and frustrating) world of game development. Now, I just wish I could do this full time! I can tell, after just one read through, that I will be using this book and referencing it for quite some time to come. It has definitely propelled me from beginner 2D programming into the fascinating world of 3D game design! This book will be used as my XNA bible until I master all of the techniques that are relevant to my designs. I still can't believe how professional my games now look!Things I would like to see in the second version:This book could actually be way longer or broken down into two books. I would like to see more on model manipulation, working with physics engines, and animation. Also, since I do not come from a programming background, it would be nice to have more robust explanations on why we are doing what we are doing, and possibly mix that in with the coding. Another thing that I would like to see is the book organized in a way that develops a complete (albeit very simple) game that incorporates the techniques used so I could see it action.Thank you, Luke Drumm, for helping me take my game designs to new heights.Sincerely, Caleb Hunyadi, CPA, MBA
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is included in a Packt subscription? Chevron down icon Chevron up icon

A subscription provides you with full access to view all Packt and licnesed content online, this includes exclusive access to Early Access titles. Depending on the tier chosen you can also earn credits and discounts to use for owning content

How can I cancel my subscription? Chevron down icon Chevron up icon

To cancel your subscription with us simply go to the account page - found in the top right of the page or at https://subscription.packtpub.com/my-account/subscription - From here you will see the ‘cancel subscription’ button in the grey box with your subscription information in.

What are credits? Chevron down icon Chevron up icon

Credits can be earned from reading 40 section of any title within the payment cycle - a month starting from the day of subscription payment. You also earn a Credit every month if you subscribe to our annual or 18 month plans. Credits can be used to buy books DRM free, the same way that you would pay for a book. Your credits can be found in the subscription homepage - subscription.packtpub.com - clicking on ‘the my’ library dropdown and selecting ‘credits’.

What happens if an Early Access Course is cancelled? Chevron down icon Chevron up icon

Projects are rarely cancelled, but sometimes it's unavoidable. If an Early Access course is cancelled or excessively delayed, you can exchange your purchase for another course. For further details, please contact us here.

Where can I send feedback about an Early Access title? Chevron down icon Chevron up icon

If you have any feedback about the product you're reading, or Early Access in general, then please fill out a contact form here and we'll make sure the feedback gets to the right team. 

Can I download the code files for Early Access titles? Chevron down icon Chevron up icon

We try to ensure that all books in Early Access have code available to use, download, and fork on GitHub. This helps us be more agile in the development of the book, and helps keep the often changing code base of new versions and new technologies as up to date as possible. Unfortunately, however, there will be rare cases when it is not possible for us to have downloadable code samples available until publication.

When we publish the book, the code files will also be available to download from the Packt website.

How accurate is the publication date? Chevron down icon Chevron up icon

The publication date is as accurate as we can be at any point in the project. Unfortunately, delays can happen. Often those delays are out of our control, such as changes to the technology code base or delays in the tech release. We do our best to give you an accurate estimate of the publication date at any given time, and as more chapters are delivered, the more accurate the delivery date will become.

How will I know when new chapters are ready? Chevron down icon Chevron up icon

We'll let you know every time there has been an update to a course that you've bought in Early Access. You'll get an email to let you know there has been a new chapter, or a change to a previous chapter. The new chapters are automatically added to your account, so you can also check back there any time you're ready and download or read them online.

I am a Packt subscriber, do I get Early Access? Chevron down icon Chevron up icon

Yes, all Early Access content is fully available through your subscription. You will need to have a paid for or active trial subscription in order to access all titles.

How is Early Access delivered? Chevron down icon Chevron up icon

Early Access is currently only available as a PDF or through our online reader. As we make changes or add new chapters, the files in your Packt account will be updated so you can download them again or view them online immediately.

How do I buy Early Access content? Chevron down icon Chevron up icon

Early Access is a way of us getting our content to you quicker, but the method of buying the Early Access course is still the same. Just find the course you want to buy, go through the check-out steps, and you’ll get a confirmation email from us with information and a link to the relevant Early Access courses.

What is Early Access? Chevron down icon Chevron up icon

Keeping up to date with the latest technology is difficult; new versions, new frameworks, new techniques. This feature gives you a head-start to our content, as it's being created. With Early Access you'll receive each chapter as it's written, and get regular updates throughout the product's development, as well as the final course as soon as it's ready.We created Early Access as a means of giving you the information you need, as soon as it's available. As we go through the process of developing a course, 99% of it can be ready but we can't publish until that last 1% falls in to place. Early Access helps to unlock the potential of our content early, to help you start your learning when you need it most. You not only get access to every chapter as it's delivered, edited, and updated, but you'll also get the finalized, DRM-free product to download in any format you want when it's published. As a member of Packt, you'll also be eligible for our exclusive offers, including a free course every day, and discounts on new and popular titles.