Introduction
Having used XNA Game Studio 3.1 for a while then taking a break, I had trouble figuring out to to apply alpha to textures and do transformations. I could not seem to find any decent tutorials out there for this purpose, so I sat down while writing a screensaver in XNA 4.0 and clumped these two classes together.
They are simple and easy to use, well commented and documented so everyone should be able to follow along without any trouble.
Please be gentle as this is my first (and probably only) article/tutorial.
Assumptions about you:
I am assuming that you have at least some experience with XNA Game Studio 4.0 and C# and that you are able to:
- Create a Windows Game Project
- Add a texture to your project
- Add classes to your project (with supplied code)
- Debug any errors that occur
- Understand fairly simple code (no matrices or quaternions or trigonometry here...)
Using the code
There are two classes that you need to create in your project:
- InterpolationTimer.cs - This is a custom timer class that can be used as a simple timer or a timer that takes in a minimum and maximum value and automatically interpolates between minimum and maximum over the duration
- FadingTexture.cs - This class utilises 4 InterpolationTimers to automate the transition of a texture after a delay if required, from fully transparent, to opaque, then back to transparent.
I will give you the code for the two classes first, then explain how to use them. The code is straight forward so I don't feel that I need to go through it with you. If you have any questions though, feel free to ask and I'll try to answer as best as I can.
If you have any trouble copying the code from this page, I have attached a demo project that is working here: Download Demo Project and Source Code
The InterpolationTimer Class
#region Timer Finished Event Delegate
internal delegate void TimerFinished();
#endregion
internal class InterpolationTimer
{
#region Private Members
private TimeSpan _timeLimit;
private TimeSpan _timeElapsed;
private bool _isRunning = false;
private float _deltaPerMillisecond;
private float _currentValue;
private float _minValue, _maxValue;
private bool _interpolate;
#endregion
#region Internally-accessible Properties
internal TimeSpan TimeLimit
{
get { return _timeLimit; }
}
internal TimeSpan TimeElapsed
{
get { return _timeElapsed; }
}
internal bool IsRunning
{
get { return _isRunning; }
}
internal event TimerFinished OnTimerFinished;
internal float DeltaPerMillisecond
{
get {
if (_interpolate)
return _deltaPerMillisecond;
else
throw new NullReferenceException("You cannot retrieve DeltaPerMillisecond from a non-interpolated timer!");
}
}
internal float CurrentValue
{
get {
if (_interpolate)
return _currentValue;
else
throw new NullReferenceException("You cannot retrieve CurrentValue from a non-interpolated timer!");
}
}
#endregion
#region Constructors
internal InterpolationTimer(TimeSpan timeLimit, float minValue, float maxValue)
{
_timeLimit = timeLimit;
_timeElapsed = TimeSpan.Zero;
_deltaPerMillisecond = (maxValue - minValue) / (float)TimeLimit.TotalMilliseconds;
_minValue = minValue;
_maxValue = maxValue;
_interpolate = true;
}
internal InterpolationTimer(TimeSpan timeLimit)
{
_timeLimit = timeLimit;
Stop(); _timeElapsed = TimeSpan.Zero;
_interpolate = false;
}
#endregion
#region Timer Control Methods
internal void Start()
{
_timeElapsed = TimeSpan.Zero;
_isRunning = true;
if (_interpolate)
{
_currentValue = _minValue;
}
}
internal void Stop()
{
_timeElapsed = TimeSpan.Zero;
_isRunning = false;
}
internal void SetTimeLimit(TimeSpan newTimeLimit, float minValue, float maxValue)
{
if (!_interpolate)
{
SetTimeLimit(newTimeLimit);
return;
}
if (IsRunning)
throw new ApplicationException("You must stop the timer before changing the time limit!");
_deltaPerMillisecond = (maxValue - minValue) / (float)newTimeLimit.TotalMilliseconds;
_timeLimit = newTimeLimit;
}
internal void SetTimeLimit(TimeSpan newTimeLimit)
{
if (_interpolate)
throw new NullReferenceException("You must re-specify the minimum and maximum values for an interpolation timer! See - SetTimeLimit(time, minValue, maxValue)");
if (IsRunning)
throw new ApplicationException("You must stop the timer before changing the time limit!");
_timeLimit = newTimeLimit;
}
#endregion
#region Update Method
internal void Update(TimeSpan elapsedTime)
{
if (IsRunning)
{
_timeElapsed += elapsedTime;
if (_interpolate)
_currentValue += DeltaPerMillisecond * (float)elapsedTime.TotalMilliseconds;
if (_timeElapsed >= _timeLimit)
{
if (_currentValue != _maxValue)
_currentValue = _maxValue;
Stop();
if (OnTimerFinished != null)
OnTimerFinished();
}
}
}
#endregion
}
The FadingTexture Class
This class is a DrawableGameComponent
, which means it will integrate with your game and your game will actually call its update and draw methods automatically.
#region Sequence Complete Event Delegate
internal delegate void FadeSequenceComplete();
#endregion
internal class FadingTexture : DrawableGameComponent
{
#region Private Members
enum FadeState { InitialDelay, FadingIn, Opaque, FadingOut, Complete };
private Texture2D _splashTexture;
private Game1 _game;
private Vector2 _position;
private TimeSpan _fadeInTime, _fadeOutTime, _opaqueTime, _initialDelayTime;
private FadeState _currentState;
private float _opacity;
#endregion
#region Internally-accessible Properties & Members
internal InterpolationTimer FadeInTimer, OpaqueTimer, FadeOutTimer, InitialDelayTimer;
internal event FadeSequenceComplete OnFadeSequenceCompleted;
internal TimeSpan TotalDuration
{
get {
return _fadeInTime + _fadeOutTime + _opaqueTime + _initialDelayTime;
}
}
#endregion
#region Constructors
internal FadingTexture(Game1 game, Vector2 position, Texture2D texture, TimeSpan fadeInTime, TimeSpan opaqueTime, TimeSpan fadeOutTime)
: base(game)
{
_game = game;
if (!_game.Components.Contains(this))
_game.Components.Add(this);
DrawOrder = 30000;
_splashTexture = texture;
_fadeInTime = fadeInTime;
_opaqueTime = opaqueTime;
_fadeOutTime = fadeOutTime;
_initialDelayTime = TimeSpan.Zero;
_currentState = FadeState.FadingIn;
_position = position;
Initialize();
}
internal FadingTexture(Game1 game, Vector2 position, Texture2D texture, TimeSpan initialDelayTime, TimeSpan fadeInTime, TimeSpan opaqueTime, TimeSpan fadeOutTime)
: base(game)
{
_game = game;
if (!_game.Components.Contains(this))
_game.Components.Add(this);
DrawOrder = 30000;
_splashTexture = texture;
_fadeInTime = fadeInTime;
_opaqueTime = opaqueTime;
_fadeOutTime = fadeOutTime;
_initialDelayTime = initialDelayTime;
_currentState = FadeState.InitialDelay;
_position = position;
Initialize();
}
#endregion
#region Overridden XNA Methods (Initialize, Update & Draw)
public override void Initialize()
{
base.Initialize();
if (_currentState == FadeState.InitialDelay)
{
InitialDelayTimer = new InterpolationTimer(_initialDelayTime);
InitialDelayTimer.OnTimerFinished += new TimerFinished(InitialDelayTimer_OnTimerFinished);
}
FadeInTimer = new InterpolationTimer(_fadeInTime, 0.0f, 1.0f);
FadeInTimer.OnTimerFinished += new TimerFinished(FadeInTimer_OnTimerFinished);
OpaqueTimer = new InterpolationTimer(_opaqueTime);
OpaqueTimer.OnTimerFinished += new TimerFinished(OpaqueTimer_OnTimerFinished);
FadeOutTimer = new InterpolationTimer(_fadeOutTime, 1.0f, 0.0f);
FadeOutTimer.OnTimerFinished += new TimerFinished(FadeOutTimer_OnTimerFinished);
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
switch (_currentState)
{
case FadeState.InitialDelay:
if (!InitialDelayTimer.IsRunning)
InitialDelayTimer.Start();
InitialDelayTimer.Update(gameTime.ElapsedGameTime);
_opacity = 0.0f; break;
case FadeState.FadingIn:
if (!FadeInTimer.IsRunning)
FadeInTimer.Start();
FadeInTimer.Update(gameTime.ElapsedGameTime);
_opacity = FadeInTimer.CurrentValue;
break;
case FadeState.Opaque:
if (!OpaqueTimer.IsRunning)
OpaqueTimer.Start();
OpaqueTimer.Update(gameTime.ElapsedGameTime);
_opacity = 1.0f;
break;
case FadeState.FadingOut:
if (!FadeOutTimer.IsRunning)
FadeOutTimer.Start();
FadeOutTimer.Update(gameTime.ElapsedGameTime);
_opacity = FadeOutTimer.CurrentValue;
break;
}
if (_opacity > 0.0f)
Visible = true;
else
Visible = false;
}
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
_game.spriteBatch.Begin();
_game.spriteBatch.Draw(_splashTexture, _position, Color.White * _opacity);
_game.spriteBatch.End();
}
#endregion
#region Custom Methods
internal void Reset(TimeSpan delay)
{
if (_currentState != FadeState.Complete)
throw new Exception("Please wait until the sequence is complete before calling Reset!");
_initialDelayTime = delay;
if (InitialDelayTimer == null)
{
InitialDelayTimer = new InterpolationTimer(delay);
InitialDelayTimer.OnTimerFinished += new TimerFinished(InitialDelayTimer_OnTimerFinished);
}
else
InitialDelayTimer.SetTimeLimit(delay);
_currentState = FadeState.InitialDelay;
}
#endregion
#region InterpolationTimer Events
void FadeOutTimer_OnTimerFinished()
{
_currentState = FadeState.Complete;
if (OnFadeSequenceCompleted != null)
OnFadeSequenceCompleted();
}
void OpaqueTimer_OnTimerFinished()
{
_currentState = FadeState.FadingOut;
}
void FadeInTimer_OnTimerFinished()
{
_currentState = FadeState.Opaque;
}
void InitialDelayTimer_OnTimerFinished()
{
_currentState = FadeState.FadingIn;
}
#endregion
}
Using the FadingTexture Class
You'll need to load a Texture2D
in your Game.LoadContent
method, and then pass that into the constructor of this class.
If you don't like doing it that way, I'm sure you're smart enough to figure out how to make this class load a texture.
There are 2 constructors for this class, one that sets up an initial delay before fading in your texture, and one that fades the texture in immediately.
Once you have instantiated the class, it will do everything for you. No other code in your Game.Update or Game.Draw is necessary.
1). Make the spriteBatch
variable in your Game class INTERNAL or PUBLIC so this class can access it:
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
internal SpriteBatch spriteBatch; }
2). In order for this class to access your spritebatch, it needs to know the type of your game.
Look at the code in the FadingTexture
class and change the following to suit your game. If your game class is just Game1, you can skip this step.
In FadingTexture.cs
:
private Game1 _game;
internal FadingTexture(Game1 game, Vector2 position, Texture2D texture, TimeSpan fadeInTime, TimeSpan opaqueTime, TimeSpan fadeOutTime)
internal FadingTexture(Game1 game, Vector2 position, Texture2D texture, TimeSpan initialDelayTime, TimeSpan fadeInTime, TimeSpan opaqueTime, TimeSpan fadeOutTime)
3). Define the class variable in your game, and if you haven't already, a Texture2D
containing the texture, and TimeSpan
s for fadeIn, Opaque, fadeOut and InitialDelay if applicable.
In Game1.cs
: (or whatever your Game class file name is)
FadingTexture _myFadingTexture;
FadingTexture _myFadingTextureDelayed;
Texture2D _myTexture;
Vector2 _texturePosition;
TimeSpan _fadeInTime = TimeSpan.FromSeconds(1.5),
_fadeOutTime = TimeSpan.FromSeconds(1.5),
_opaqueTime = TimeSpan.FromSeconds(5);
TimeSpan _delayTime = TimeSpan.FromSeconds(3);
4). Instantiate the class AFTER YOU HAVE LOADED YOUR CONTENT
In Game1.cs
:
protected override void LoadContent()
{
_myTexture = Content.Load<Texture2D>(@"Textures\fadeTexture");
Viewport vp = graphics.GraphicsDevice.Viewport;
Vector2 textureSize = new Vector2(_myTexture.Width, _myTexture.Height);
_texturePosition = new Vector2(vp.Width / 2, vp.Height / 2) - textureSize / 2;
_myFadingTexture = new FadingTexture(this, _texturePosition, _myTexture, _fadeInTime, _opaqueTime, _fadeOutTime);
delayTime += _myFadingTexture.TotalDuration; _myFadingTextureDelayed = new FadingTexture(this, _texturePosition, _myTexture, _delayTime, _fadeInTime, _opaqueTime, _fadeOutTime);
}
Step 5). Run your game and watch the prettiness!!
If you would like your game to be notified when the transition is completed, simply subscribe to the OnFadeSequenceCompleted
event like so:
In Game1.cs
or another class that needs to be notified when the transition is complete:
_myFadingTextureDelayed.OnFadeSequenceCompleted += new FadeSequenceComplete(_myFadingTextureDelayed_OnFadeSequenceCompleted);
void _myFadingTextureDelayed_OnFadeSequenceCompleted()
{
_myFadingTextureDelayed.Dispose();
_myFadingTextureDelayed.Reset(TimeSpan.FromMinutes(1)); }
If you're able to understand the code of the FadingTexture
class then you should realise that you do not need to interact with any InterpolationTimer classes. FadingTexture
does all of that for you.
If you just want the Timer aspect of the classes, here is how to use the InterpolationTimer
class without the FadingTexture
class.
Using the InterpolationTimer Class without the FadingTexture Class
A Simple Timer
1). Create your instance of the class and subscribe to the OnTimerFinished
event
InterpolationTimer _myTimer;
_myTimer = new InterpolationTimer(TimeSpan.FromSeconds(10)); _myTimer.OnTimerFinished += new TimerFinished(_myTimer_OnTimerFinished);
2). Make sure the timer is updated when it is running, or nothing will happen!
protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (_myTimer.IsRunning)
_myTimer.Update(gameTime.ElapsedGameTime);
}
3). Start your timer when you see fit
_myTimer.Start();
4). Your event should be triggered when the timer is complete. If nothing happens, check you are updating the timer in your Update
method correctly.
void _myTimer_OnTimerFinished()
{
throw new NotImplementedException();
}
An Interpolating Timer
The only difference from the above code is the overloaded constructor and how you fetch the interpolation value.
1). Create your instance of the class and subscribe to the OnTimerFinished
event
InterpolationTimer _myInterpolationTimer;
_myInterpolationTimer = new InterpolationTimer(TimeSpan.FromSeconds(10), 0.0f, 1.0f); _myInterpolationTimer = new InterpolationTimer(TimeSpan.FromSeconds(10), 1.0f, 0.0f); _myInterpolationTimer.OnTimerFinished += new TimerFinished(_myInterpolationTimer_OnTimerFinished);
2). Use the code for simple timer above to make sure your timer is updated (rename the variable from _myTimer
to _myInterpolationTimer
though)
You'll probably want to use the interpolation value here in your update method, to do that, simply reference _myInterpolationTimer.CurrentValue
as follows:
protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (_myInterpolationTimer.IsRunning)
_myInterpolationTimer.Update(gameTime.ElapsedGameTime);
_textureAlphaLevel = _myInterpolationTimer.CurrentValue;
}
3). Start the timer the same way as above
4). Your event will be triggered the same way as above
To draw an object using the alpha value generated by the timer, simply draw it as normal, with a Color of White, multiplied by the alpha value, like follows:
spriteBatch.Begin();
spriteBatch.Draw(texture, position, Color.White * _textureAlphaLevel);
spriteBatch.End();
I hope at least a few people find this useful!