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 );
  2. 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;
  3. 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 );
    }
  4. 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;
    }
}
Show: