Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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