How to: Draw a Sprite Over a Model

This article demonstrates how to draw a sprite so that it obscures a model.

In this example, we are drawing an animated sprite representing an explosion over the current screen position of a 3D model. This example presumes you already have a model, a camera, and an animated sprite loaded. See How to: Animate a Sprite for an example of the AnimatedTexture class.

To draw a sprite over a model

  1. In your Update method, handle input to move your camera, then call UpdateFrame on the AnimatedTexture.

    protected override void Update( GameTime gameTime )
    

{ // TODO: Add your update logic here GamePadState PlayerOne = GamePad.GetState( PlayerIndex.One );

// Move the camera using thumbsticks
MoveCamera( PlayerOne );

// Start or stop the animated sprite using buttons
if (PlayerOne.Buttons.A == ButtonState.Pressed)
    explosion.Play();
if (PlayerOne.Buttons.B == ButtonState.Pressed)
    explosion.Stop();

// Update the animated sprite
explosion.UpdateFrame( (float)gameTime.ElapsedGameTime.TotalSeconds );</pre>
  1. Use CreateMerged to create a BoundingSphere that contains all the BoundingSphere values for each ModelMesh in the Model. Then use Viewport.Project to find the centerpoint of that sphere - that is the center of the model in screen coordinates.

    // Create a total bounding sphere for the mesh
    

BoundingSphere totalbounds = new BoundingSphere(); foreach (ModelMesh mesh in Ring.Meshes) { totalbounds = BoundingSphere.CreateMerged( totalbounds, mesh.BoundingSphere ); }

// Project the center of the 3D object to the screen, and center the // sprite there Vector3 center = graphics.GraphicsDevice.Viewport.Project( totalbounds.Center, projectionMatrix, Camera1.ViewMatrix, Matrix.Identity ); explosionpos.X = center.X; explosionpos.Y = center.Y;

  1. Take the BoundingSphere for the model and use that to create a BoundingBox with CreateFromSphere. Then find the corner of the box that is farthest from the center using Project, and use that to scale the sprite appropriately.

        // Create a bounding box from the bounding sphere, and find the corner
    // that is farthest away from the center using Project
    BoundingBox extents = BoundingBox.CreateFromSphere( totalbounds );
    float maxdistance = 0;
    float distance;
    Vector3 screencorner;
    foreach (Vector3 corner in extents.GetCorners())
    {
        screencorner = graphics.GraphicsDevice.Viewport.Project( corner,
        projectionMatrix, Camera1.ViewMatrix, Matrix.Identity );
        distance = Vector3.Distance( screencorner, center );
        if (distance > maxdistance)
            maxdistance = distance;
    }
    
    // Scale the sprite using the two points (the sprite is 
    // 75 pixels square)
    explosion.Scale = maxdistance / 75;
    
    base.Update( gameTime );
    

}

  1. In your Draw method, draw the Model normally, then draw the animated sprite using the position calculated in Update.

    protected override void Draw( GameTime gameTime )
    

{ graphics.GraphicsDevice.Clear( Color.CornflowerBlue );

//Draw the model, a model can have multiple meshes, so loop
foreach (ModelMesh mesh in Ring.Meshes)
{
    //This is where the mesh orientation is set, as well as our camera and projection
    foreach (BasicEffect effect in mesh.Effects)
    {
        effect.EnableDefaultLighting();
        effect.World = Matrix.Identity * Matrix.CreateRotationY( RingRotation )
            * Matrix.CreateTranslation( RingPosition );
        effect.View = Camera1.ViewMatrix;
        effect.Projection = projectionMatrix;
    }
    //Draw the mesh, will use the effects set above.
    mesh.Draw();
}

// Draw the sprite over the 3D object
batch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState );
explosion.DrawFrame( batch, explosionpos );
batch.End();

base.Draw( gameTime );

}

The Complete Example

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Model Ring;
    float RingRotation;
    Vector3 RingPosition;

    SampleArcBallCamera Camera1;
    Matrix projectionMatrix;

    SpriteBatch batch;

    AnimatedTexture explosion;
    Vector2 explosionpos;

    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );
        Camera1 = new SampleArcBallCamera( SampleArcBallCameraMode.Free );

        Camera1.Target = new Vector3( 0, 0, 0 );
        Camera1.Distance = 100f;
        Camera1.OrbitRight( MathHelper.PiOver4 );
        Camera1.OrbitUp( MathHelper.PiOver4 );

        projectionMatrix = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1.0f, 10000f );
        RingPosition = Vector3.Zero;
        RingRotation = 0.0f;

        explosion = new AnimatedTexture( new Vector2( 75 ), 0, 1.0f, 1.0f );
    }


    protected override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        if (loadAllContent)
        {
            Ring = content.Load<Model>( "ring16b" );
            batch = new SpriteBatch( graphics.GraphicsDevice );
            explosion.Load( graphics.GraphicsDevice, content, "explosion", 10, 3 );
            explosion.Pause();
        }
        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
        graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
    }


    protected override void UnloadGraphicsContent( bool unloadAllContent )
    {
        if (unloadAllContent == true)
        {
            content.Unload();
        }
    }

    protected override void Update( GameTime gameTime )
    {
        // Allows the default game to exit on Xbox 360 and Windows
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // TODO: Add your update logic here
        GamePadState PlayerOne = GamePad.GetState( PlayerIndex.One );

        // Move the camera using thumbsticks
        MoveCamera( PlayerOne );

        // Start or stop the animated sprite using buttons
        if (PlayerOne.Buttons.A == ButtonState.Pressed)
            explosion.Play();
        if (PlayerOne.Buttons.B == ButtonState.Pressed)
            explosion.Stop();

        // Update the animated sprite
        explosion.UpdateFrame( (float)gameTime.ElapsedGameTime.TotalSeconds );

        // Create a total bounding sphere for the mesh
        BoundingSphere totalbounds = new BoundingSphere();
        foreach (ModelMesh mesh in Ring.Meshes)
        {
            totalbounds = BoundingSphere.CreateMerged( totalbounds, mesh.BoundingSphere );
        }

        // Project the center of the 3D object to the screen, and center the
        // sprite there
        Vector3 center = graphics.GraphicsDevice.Viewport.Project( totalbounds.Center,
            projectionMatrix, Camera1.ViewMatrix, Matrix.Identity );
        explosionpos.X = center.X;
        explosionpos.Y = center.Y;

        // Create a bounding box from the bounding sphere, and find the corner
        // that is farthest away from the center using Project
        BoundingBox extents = BoundingBox.CreateFromSphere( totalbounds );
        float maxdistance = 0;
        float distance;
        Vector3 screencorner;
        foreach (Vector3 corner in extents.GetCorners())
        {
            screencorner = graphics.GraphicsDevice.Viewport.Project( corner,
            projectionMatrix, Camera1.ViewMatrix, Matrix.Identity );
            distance = Vector3.Distance( screencorner, center );
            if (distance > maxdistance)
                maxdistance = distance;
        }

        // Scale the sprite using the two points (the sprite is 
        // 75 pixels square)
        explosion.Scale = maxdistance / 75;

        base.Update( gameTime );
    }

    private void MoveCamera( GamePadState Player )
    {
        Camera1.OrbitUp( Player.ThumbSticks.Right.Y / 4 );
        Camera1.OrbitRight( Player.ThumbSticks.Right.X / 4 );
        Camera1.Distance -= Player.ThumbSticks.Left.Y;
        Vector3 newTarget = Camera1.Target;
        newTarget.X += Player.ThumbSticks.Left.X;
        Camera1.Target = newTarget;
    }


    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );


        //Draw the model, a model can have multiple meshes, so loop
        foreach (ModelMesh mesh in Ring.Meshes)
        {
            //This is where the mesh orientation is set, as well as our camera and projection
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
                effect.World = Matrix.Identity * Matrix.CreateRotationY( RingRotation )
                    * Matrix.CreateTranslation( RingPosition );
                effect.View = Camera1.ViewMatrix;
                effect.Projection = projectionMatrix;
            }
            //Draw the mesh, will use the effects set above.
            mesh.Draw();
        }

        // Draw the sprite over the 3D object
        batch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState );
        explosion.DrawFrame( batch, explosionpos );
        batch.End();

        base.Draw( gameTime );
    }
}
public class AnimatedTexture
{
    private int framecount;
    private Texture2D myTexture;
    private float TimePerFrame;
    private int Frame;
    private float TotalElapsed;
    private bool Paused;

    public float Rotation, Scale, Depth;
    public Vector2 Origin;
    public AnimatedTexture( Vector2 Origin, float Rotation, float Scale, float Depth )
    {
        this.Origin = Origin;
        this.Rotation = Rotation;
        this.Scale = Scale;
        this.Depth = Depth;
    }

    public void Load( GraphicsDevice device, ContentManager content, string asset, int FrameCount, int FramesPerSec )
    {
        framecount = FrameCount;
        myTexture = content.Load<Texture2D>( asset );
        TimePerFrame = (float)1 / FramesPerSec;
        Frame = 0;
        TotalElapsed = 0;
        Paused = false;
    }

    // class AnimatedTexture
    public void UpdateFrame( float elapsed )
    {
        if (Paused)
            return;
        TotalElapsed += elapsed;
        if (TotalElapsed > TimePerFrame)
        {
            Frame++;
            // Keep the Frame between 0 and the total frames, minus one
            Frame = Frame % (framecount - 1);
            TotalElapsed -= TimePerFrame;
        }
    }

    // class AnimatedTexture
    public void DrawFrame( SpriteBatch Batch, Vector2 screenpos )
    {
        DrawFrame( Batch, Frame, screenpos );
    }
    public void DrawFrame( SpriteBatch Batch, int Frame, Vector2 screenpos )
    {
        int FrameWidth = myTexture.Width / framecount;
        Rectangle sourcerect = new Rectangle( FrameWidth * Frame, 0,
            FrameWidth, myTexture.Height );
        Batch.Draw( myTexture, screenpos, sourcerect, Color.White,
            Rotation, Origin, Scale, SpriteEffects.None, Depth );
    }

    public bool IsPaused
    {
        get { return Paused; }
    }
    public void Reset()
    {
        Frame = 0;
        TotalElapsed = 0f;
    }
    public void Stop()
    {
        Pause();
        Reset();
    }
    public void Play()
    {
        Paused = false;
    }
    public void Pause()
    {
        Paused = true;
    }
}

See Also

Tasks

How to: Animate a Sprite
How to: Render a Model

Concepts

2D Graphics Overview

Reference

Project
BoundingSphere
BoundingBox