Standardized Full-Screen Fade In and Fade Out Solution

hi,

i've implemented an enhanced Screen class that does full-screen fade-ins and fade-outs automatically -- all a derived class must do is call a function and accomodate for the wait-time of any fades it invokes. i *strongly* feel that this kind of thing should be included in the XNA samples or the architecture itself, but since it's not, i did one myself.

(hindsight) this post turned into more than i intended. it's more of a tutorial now, hehe... but get through it, and you'll have easy fade-ins/outs in no time. anyway, here's what i did -- hope it helps someone...

NOTE: i'm a prototype developer, so comments are not my thing and the code may be incomplete and/or have bugs that i don't see at this early stage. i know i reformatted the tabs to two-spaces, and i may have even removed comments or anything i felt was extraneous (i like to do bottom-up coding), so you may have some work to get it integrated into your game, but probably just some cutting&pasting. as i said, i urge anyone to post updates to compensate for my way of doing things.

despite its shortcomings, i figure someone out there might still use (or expand upon) what i did. if someone wanted to comment the code and re-post it, you'd be a better coder than i.

here's how the updated Screen class works:

1. you initiate a fade-in with Screen.DoFadeIn() from your Screen-derived class' constructor. you do the same for a fade-out but you use Screen.DoFadeOut() and you invoke it after your screen is already visible and doing stuff.

*there is no protection against misuse of these functions (i.e. calling DoFadeIn() after the screen is visible).

2. you can detect the fade-in/out status by checking the Screen.doFadeIn or Screen.doFadeOut (bool) variables.

3. the Screen class also now contains two virtual methods: Screen.raise_FadedIn() and Screen.raise_FadedOut(). you can override them to know *exactly* when the fade is complete, rather than using the boolean state variables to poll the fade status.

*i'm new to XNA and C# (been doing C++ for 20 years), so i'm not positive i'm using that "raised_" mnemonic properly -- i've just seen it in the code/interfaces and figure that's what the standard event-invocation mnemonic was. if i'm wrong, update the code, school me, and repost.

4. there is an Initialize() method added to the class. this is glaringly absent from the sample code (especially since there's a Shutdown() method there). you call Initialize() directly after you create an instance of *any* Screen-derived class (even if they don't fade-in/out it's still good practice to set up a proper initialization function, so you can override it later).

anyway, Initialize() lets the Screen class do things that rely on what a derived class does in its constructor code, since the derived class' constructor is run *after* the base class' constructor. in this solution, Initialize() sets up the fade-in color before the first call to Render() (which apparently happens before the call to Update()). you could call DoFadeIn() in an overridden Initialize() method, then call base.Initialize(), but calling DoFadeIn() from the derived class' constructor is fine for this solution.

5. since i don't know this system well enough yet, i had to resort to using a 16x16 black bitmap (PNG) for the actual rendering of the fade-in/out. so, you'll need that file (called "black.png") to run this code -- if you want it to be completely portable, you'd specify the texture name from your derived class (or not use textures at all, yay).

*someone, feel free to implement a better mechanism for the fade effect, but i'm happy with it being a global thing, and this is enough to get other people on the right track at least. search the code for the string "black.png" and you'll find the spot. note that i'm not using any paths, so have the black PNG file in the same dir as your app or change the code.

6. finally, make sure you call Screen.Render() *AFTER* you do your derived class' drawing routines (since the effect used is overlayed on top of a completely drawn screen).

sorry for this, i'm a posting newb so i'm just putting the entire class definition (Screen.cs) in this post:

BEGIN CODE ("Screen.cs" Enhancement)
// Updated Screen class that does Fade-Ins and Fade-Outs
// By David T. Kaplan (2006-10-03)

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ParticleMan
{
/// <summary>
/// Screen represents a unit of rendering for the game, generally transitional point such
/// as splash screens, selection screens and the actual game levels.
/// </summary>
abstract public class Screen
{
/// <summary>
/// The root of the scene graph for this screen
/// </summary>
public SceneItem Scene = null;

/// <summary>
/// Overlay points to a screen that will be drawn AFTER this one, more than likely overlaying it
/// </summary>
public Screen Overlay;

/// <summary>
/// Lets a derived class request a fade-in effect at startup
/// </summary>
protected bool doFadeIn = false;
protected bool doFadeOut = false;
protected int fadeTimeMS = 0;
protected Vector4 fadeStartColor = new Vector4(0f, 0f, 0f, 1);
protected Vector4 fadeEndColor = new Vector4(0f, 0f, 0f, 0);

protected SpriteBatch batch = null;

protected Game game = null;

// internal fade-in stuff
private TimeSpan fadeStart = TimeSpan.Zero;
private TimeSpan fadeEnd = TimeSpan.Zero;
private Color fadeColor;

public Game GameInstance
{
get
{
return game;
}
}

public SpriteBatch spriteBatch
{
get
{
return batch;
}
}

public Screen(Game game)
{
this.game = game;
Scene = new SceneItem(game);

IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)game.GameServices.GetService(typeof(IGraphicsDeviceService));
batch = new SpriteBatch(graphicsService.GraphicsDevice);
}

public virtual void DoFadeIn(int fadeInMS)
{
doFadeOut = false;
doFadeIn = true;
fadeStart = TimeSpan.Zero;
fadeTimeMS = fadeInMS;
}

public virtual void DoFadeOut(int fadeOutMS)
{
doFadeIn = false;
doFadeOut = true;
fadeStart = TimeSpan.Zero;
fadeTimeMS = fadeOutMS;
}

public virtual void Initialize()
{
if (doFadeIn == true)
fadeColor = new Color(fadeStartColor);
}

public virtual void raise_FadedIn()
{
}

public virtual void raise_FadedOut()
{
}

/// <summary>
/// Update changes the layout and positions based on input or other variables
/// Base class updates all items in the scene then calls any overlays to get them to render themselves
/// </summary>
/// <param name="time">Total game time since it was started</param>
/// <param name="elapsedTime">Elapsed game time since the last call to update</param>
/// <returns>The next gamestate to transition to. Default is the return value of an overlay or NONE. Override Update if you want to change this behaviour</returns>
public virtual GameState Update(TimeSpan time, TimeSpan elapsedTime)
{
//Update the Scene
Scene.Update(time, elapsedTime);

if (Overlay != null)
Overlay.Update(time, elapsedTime);

if ((doFadeIn == true)||(doFadeOut == true))
{
if (fadeStart == TimeSpan.Zero)
{
// begin fade-in/out
fadeStart = time;
fadeEnd = fadeStart;
fadeEnd += new TimeSpan(0, 0, 0, 0, fadeTimeMS);
if (doFadeIn == true)
fadeColor = new Color(fadeStartColor);
else
fadeColor = new Color(fadeEndColor);
}
else if (time < fadeEnd)
{
float percentLife = (float)((fadeEnd.TotalMilliseconds - time.TotalMilliseconds) / fadeTimeMS);
if (doFadeIn == true)
{
Vector4 startColor = fadeStartColor;
Vector4 endColor = fadeEndColor;
Vector4 curColor = Vector4.Lerp(endColor, startColor, percentLife);
fadeColor = new Color(curColor);
}
else
{
Vector4 startColor = fadeEndColor;
Vector4 endColor = fadeStartColor;
Vector4 curColor = Vector4.Lerp(endColor, startColor, percentLife);
fadeColor = new Color(curColor);
}
}
else
{
if (doFadeIn == true)
{
doFadeIn = false;
fadeStart = TimeSpan.Zero;
raise_FadedIn();
}
else
{
doFadeOut = false;
fadeStart = TimeSpan.Zero;
raise_FadedOut();
}
}
}

//Default is no state changes, override the class if you want a different state
return GameState.None;
}

/// <summary>
/// Renders this scene. The base class renders everything in the sceen graph and then calls any overlays to
/// get them to render themselves
/// </summary>
public virtual void Render()
{
//Render this scene then any overlays
Scene.Render();

if (Overlay != null)
Overlay.Render();

if ((doFadeIn == true) || (doFadeOut == true))
{
IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)GameInstance.GameServices.GetService(typeof(IGraphicsDeviceService));
GraphicsDevice device = graphicsService.GraphicsDevice;
Texture2D tex = PMGame.TextureCache.GetTexture(@"black.png");

spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
device.RenderState.DepthBufferWriteEnable = false;

spriteBatch.Draw(tex,
new Rectangle(0, 0, PMGame.ScreenWidth, PMGame.ScreenHeight),
null, fadeColor, 0,
new Vector2(0, 0),
SpriteEffects.None, 0);

spriteBatch.End();
}
}

/// <summary>
/// Tidies up the scene. Base class does nothing but calls the overlays
/// </summary>
public virtual void Shutdown()
{
if (Overlay != null)
Overlay.Shutdown();

if (batch != null)
{
batch.Dispose();
batch = null;
}
}

/// <summary>
/// OnCreateDevice is called when the device is created
/// </summary>
public virtual void OnCreateDevice()
{
//Re-Create the Sprite Batch!
IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)game.GameServices.GetService(typeof(IGraphicsDeviceService));
batch = new SpriteBatch(graphicsService.GraphicsDevice);
}
}
}

END CODE ("Screen.cs" Enhancement)

[10675 byte] By [DaveKaplan] at [2007-12-25]