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
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>
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;
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 );
}
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