XNA Screen Manager problem with transitions
Posted
by
NexAddo
on Game Development
See other posts from Game Development
or by NexAddo
Published on 2012-03-14T16:40:37Z
Indexed on
2012/04/14
23:49 UTC
Read the original article
Hit count: 611
I'm having issues using the game statemanagement example in the game I am developing.
I have no issues with my first three screens transitioning between one another. I have a main menu screen, a splash screen and a high score screen that cycle:
mainMenuScreen->splashScreen->highScoreScreen->mainMenuScreen
The screens change every 15 seconds.
Transition times
public MainMenuScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(0.5);
TransitionOffTime = TimeSpan.FromSeconds(0.0);
currentCreditAmount = Global.CurrentCredits;
}
public SplashScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(0.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
}
public HighScoreScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(0.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
}
public GamePlayScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(0.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
}
When a user inserts credits they can play the game after pressing start
mainMenuScreen->splashScreen->highScoreScreen->(loops forever)
|| || ||
===========Credits In=============
||
Start
||
\/
LoadingScreen
||
Start
||
\/
GamePlayScreen
During each of these transitions, between screens, the same code is used, which exits(removes) all current active screens and respects transitions, then adds the new screen to the screen manager:
foreach (GameScreen screen in ScreenManager.GetScreens())
screen.ExitScreen();
//AddScreen takes a new screen to manage and the controlling player
ScreenManager.AddScreen(new NameOfScreenHere(), null);
Each screen is removed from the ScreenManager with ExitScreen() and using this function, each screen transition is respected.
The problem I am having is with my gamePlayScreen. When the current game is finished and the transition is complete for the gamePlayScreen, it should be removed and the next screens should be added to the ScreenManager.
GamePlayScreen Code Snippet
private void FinishCurrentGame()
{
AudioManager.StopSounds();
this.UnloadContent();
if (Global.SaveDevice.IsReady)
Stats.Save();
if (HighScoreScreen.IsInHighscores(timeLimit))
{
foreach (GameScreen screen in ScreenManager.GetScreens())
screen.ExitScreen();
Global.TimeRemaining = timeLimit;
ScreenManager.AddScreen(new BackgroundScreen(), null);
ScreenManager.AddScreen(new MessageBoxScreen("Enter your Initials", true), null);
}
else
{
foreach (GameScreen screen in ScreenManager.GetScreens())
screen.ExitScreen();
ScreenManager.AddScreen(new BackgroundScreen(), null);
ScreenManager.AddScreen(new MainMenuScreen(), null);
}
}
The problem is that when isExiting is set to true by screen.ExitScreen() for the gamePlayScreen, the transition never completes the transition and removes the screen from the ScreenManager.
Every other screen that I use the same technique to add and remove each screen fully transitions On/Off and is removed at the appropriate time from the ScreenManager, but noy my GamePlayScreen. Has anyone that has used the GameStateManagement example experienced this issue or can someone see the mistake I am making?
EDIT
This is what I tracked down.
When the game is done, I call
foreach (GameScreen screen in ScreenManager.GetScreens())
screen.ExitScreen();
to start the transition off process for the gameplay screen. At this point there is only 1 screen on the ScreenManager stack. The gamePlay screen gets isExiting set to true and starts to transition off.
Right after the above call to ExitScreen()
I add a background screen and menu screen to the screenManager:
ScreenManager.AddScreen(new background(), null);
ScreenManager.AddScreen(new Menu(), null);
The count of the ScreenManager is now 3.
What I noticed while stepping through the updates for GameScreen and ScreenManager, the gameplay screen never gets to the point where the transistion process finishes so the ScreenManager can remove it from the stack.
This anomaly does not happen to any of my other screens when I switch between them.
Screen Manager Code
#region File Description
//-----------------------------------------------------------------------------
// ScreenManager.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#define DEMO
#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using PerformanceUtility.GameDebugTools;
#endregion
namespace GameStateManagement
{
/// <summary>
/// The screen manager is a component which manages one or more GameScreen
/// instances. It maintains a stack of screens, calls their Update and Draw
/// methods at the appropriate times, and automatically routes input to the
/// topmost active screen.
/// </summary>
public class ScreenManager : DrawableGameComponent
{
#region Fields
List<GameScreen> screens = new List<GameScreen>();
List<GameScreen> screensToUpdate = new List<GameScreen>();
InputState input = new InputState();
SpriteBatch spriteBatch;
SpriteFont font;
Texture2D blankTexture;
bool isInitialized;
bool getOut;
bool traceEnabled;
#if DEBUG
DebugSystem debugSystem;
Stopwatch stopwatch = new Stopwatch();
bool debugTextEnabled;
#endif
#endregion
#region Properties
/// <summary>
/// A default SpriteBatch shared by all the screens. This saves
/// each screen having to bother creating their own local instance.
/// </summary>
public SpriteBatch SpriteBatch
{
get { return spriteBatch; }
}
/// <summary>
/// A default font shared by all the screens. This saves
/// each screen having to bother loading their own local copy.
/// </summary>
public SpriteFont Font
{
get { return font; }
}
public Rectangle ScreenRectangle
{
get { return new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); }
}
/// <summary>
/// If true, the manager prints out a list of all the screens
/// each time it is updated. This can be useful for making sure
/// everything is being added and removed at the right times.
/// </summary>
public bool TraceEnabled
{
get { return traceEnabled; }
set { traceEnabled = value; }
}
#if DEBUG
public bool DebugTextEnabled
{
get { return debugTextEnabled; }
set { debugTextEnabled = value; }
}
public DebugSystem DebugSystem
{
get { return debugSystem; }
}
#endif
#endregion
#region Initialization
/// <summary>
/// Constructs a new screen manager component.
/// </summary>
public ScreenManager(Game game)
: base(game)
{
// we must set EnabledGestures before we can query for them, but
// we don't assume the game wants to read them.
//TouchPanel.EnabledGestures = GestureType.None;
}
/// <summary>
/// Initializes the screen manager component.
/// </summary>
public override void Initialize()
{
base.Initialize();
#if DEBUG
debugSystem = DebugSystem.Initialize(Game, "Fonts/MenuFont");
#endif
isInitialized = true;
}
/// <summary>
/// Load your graphics content.
/// </summary>
protected override void LoadContent()
{
// Load content belonging to the screen manager.
ContentManager content = Game.Content;
spriteBatch = new SpriteBatch(GraphicsDevice);
font = content.Load<SpriteFont>(@"Fonts\menufont");
blankTexture = content.Load<Texture2D>(@"Textures\Backgrounds\blank");
// Tell each of the screens to load their content.
foreach (GameScreen screen in screens)
{
screen.LoadContent();
}
}
/// <summary>
/// Unload your graphics content.
/// </summary>
protected override void UnloadContent()
{
// Tell each of the screens to unload their content.
foreach (GameScreen screen in screens)
{
screen.UnloadContent();
}
}
#endregion
#region Update and Draw
/// <summary>
/// Allows each screen to run logic.
/// </summary>
public override void Update(GameTime gameTime)
{
#if DEBUG
debugSystem.TimeRuler.StartFrame();
debugSystem.TimeRuler.BeginMark("Update", Color.Blue);
if (debugTextEnabled && getOut == false)
{
debugSystem.FpsCounter.Visible = true;
debugSystem.TimeRuler.Visible = true;
debugSystem.TimeRuler.ShowLog = true;
getOut = true;
}
else if (debugTextEnabled == false)
{
getOut = false;
debugSystem.FpsCounter.Visible = false;
debugSystem.TimeRuler.Visible = false;
debugSystem.TimeRuler.ShowLog = false;
}
#endif
// Read the keyboard and gamepad.
input.Update();
// Make a copy of the master screen list, to avoid confusion if
// the process of updating one screen adds or removes others.
screensToUpdate.Clear();
foreach (GameScreen screen in screens)
screensToUpdate.Add(screen);
bool otherScreenHasFocus = !Game.IsActive;
bool coveredByOtherScreen = false;
// Loop as long as there are screens waiting to be updated.
while (screensToUpdate.Count > 0)
{
// Pop the topmost screen off the waiting list.
GameScreen screen = screensToUpdate[screensToUpdate.Count - 1];
screensToUpdate.RemoveAt(screensToUpdate.Count - 1);
// Update the screen.
screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
if (screen.ScreenState == ScreenState.TransitionOn ||
screen.ScreenState == ScreenState.Active)
{
// If this is the first active screen we came across,
// give it a chance to handle input.
if (!otherScreenHasFocus)
{
screen.HandleInput(input);
otherScreenHasFocus = true;
}
// If this is an active non-popup, inform any subsequent
// screens that they are covered by it.
if (!screen.IsPopup)
coveredByOtherScreen = true;
}
}
// Print debug trace?
if (traceEnabled)
TraceScreens();
#if DEBUG
debugSystem.TimeRuler.EndMark("Update");
#endif
}
/// <summary>
/// Prints a list of all the screens, for debugging.
/// </summary>
void TraceScreens()
{
List<string> screenNames = new List<string>();
foreach (GameScreen screen in screens)
screenNames.Add(screen.GetType().Name);
Debug.WriteLine(string.Join(", ", screenNames.ToArray()));
}
/// <summary>
/// Tells each screen to draw itself.
/// </summary>
public override void Draw(GameTime gameTime)
{
#if DEBUG
debugSystem.TimeRuler.StartFrame();
debugSystem.TimeRuler.BeginMark("Draw", Color.Yellow);
#endif
foreach (GameScreen screen in screens)
{
if (screen.ScreenState == ScreenState.Hidden)
continue;
screen.Draw(gameTime);
}
#if DEBUG
debugSystem.TimeRuler.EndMark("Draw");
#endif
#if DEMO
SpriteBatch.Begin();
SpriteBatch.DrawString(font,
"DEMO - NOT FOR RESALE", new Vector2(20, 80), Color.White);
SpriteBatch.End();
#endif
}
#endregion
#region Public Methods
/// <summary>
/// Adds a new screen to the screen manager.
/// </summary>
public void AddScreen(GameScreen screen, PlayerIndex? controllingPlayer)
{
screen.ControllingPlayer = controllingPlayer;
screen.ScreenManager = this;
screen.IsExiting = false;
// If we have a graphics device, tell the screen to load content.
if (isInitialized)
{
screen.LoadContent();
}
screens.Add(screen);
}
/// <summary>
/// Removes a screen from the screen manager. You should normally
/// use GameScreen.ExitScreen instead of calling this directly, so
/// the screen can gradually transition off rather than just being
/// instantly removed.
/// </summary>
public void RemoveScreen(GameScreen screen)
{
// If we have a graphics device, tell the screen to unload content.
if (isInitialized)
{
screen.UnloadContent();
}
screens.Remove(screen);
screensToUpdate.Remove(screen);
}
/// <summary>
/// Expose an array holding all the screens. We return a copy rather
/// than the real master list, because screens should only ever be added
/// or removed using the AddScreen and RemoveScreen methods.
/// </summary>
public GameScreen[] GetScreens()
{
return screens.ToArray();
}
/// <summary>
/// Helper draws a translucent black fullscreen sprite, used for fading
/// screens in and out, and for darkening the background behind popups.
/// </summary>
public void FadeBackBufferToBlack(float alpha)
{
Viewport viewport = GraphicsDevice.Viewport;
spriteBatch.Begin();
spriteBatch.Draw(blankTexture,
new Rectangle(0, 0, viewport.Width, viewport.Height),
Color.Black * alpha);
spriteBatch.End();
}
#endregion
}
}
Game Screen Parent of GamePlayScreen
#region File Description
//-----------------------------------------------------------------------------
// GameScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
//using Microsoft.Xna.Framework.Input.Touch;
using System.IO;
#endregion
namespace GameStateManagement
{
/// <summary>
/// Enum describes the screen transition state.
/// </summary>
public enum ScreenState
{
TransitionOn,
Active,
TransitionOff,
Hidden,
}
/// <summary>
/// A screen is a single layer that has update and draw logic, and which
/// can be combined with other layers to build up a complex menu system.
/// For instance the main menu, the options menu, the "are you sure you
/// want to quit" message box, and the main game itself are all implemented
/// as screens.
/// </summary>
public abstract class GameScreen
{
#region Properties
/// <summary>
/// Normally when one screen is brought up over the top of another,
/// the first screen will transition off to make room for the new
/// one. This property indicates whether the screen is only a small
/// popup, in which case screens underneath it do not need to bother
/// transitioning off.
/// </summary>
public bool IsPopup
{
get { return isPopup; }
protected set { isPopup = value; }
}
bool isPopup = false;
/// <summary>
/// Indicates how long the screen takes to
/// transition on when it is activated.
/// </summary>
public TimeSpan TransitionOnTime
{
get { return transitionOnTime; }
protected set { transitionOnTime = value; }
}
TimeSpan transitionOnTime = TimeSpan.Zero;
/// <summary>
/// Indicates how long the screen takes to
/// transition off when it is deactivated.
/// </summary>
public TimeSpan TransitionOffTime
{
get { return transitionOffTime; }
protected set { transitionOffTime = value; }
}
TimeSpan transitionOffTime = TimeSpan.Zero;
/// <summary>
/// Gets the current position of the screen transition, ranging
/// from zero (fully active, no transition) to one (transitioned
/// fully off to nothing).
/// </summary>
public float TransitionPosition
{
get { return transitionPosition; }
protected set { transitionPosition = value; }
}
float transitionPosition = 1;
/// <summary>
/// Gets the current alpha of the screen transition, ranging
/// from 1 (fully active, no transition) to 0 (transitioned
/// fully off to nothing).
/// </summary>
public float TransitionAlpha
{
get { return 1f - TransitionPosition; }
}
/// <summary>
/// Gets the current screen transition state.
/// </summary>
public ScreenState ScreenState
{
get { return screenState; }
protected set { screenState = value; }
}
ScreenState screenState = ScreenState.TransitionOn;
/// <summary>
/// There are two possible reasons why a screen might be transitioning
/// off. It could be temporarily going away to make room for another
/// screen that is on top of it, or it could be going away for good.
/// This property indicates whether the screen is exiting for real:
/// if set, the screen will automatically remove itself as soon as the
/// transition finishes.
/// </summary>
public bool IsExiting
{
get { return isExiting; }
protected internal set { isExiting = value; }
}
bool isExiting = false;
/// <summary>
/// Checks whether this screen is active and can respond to user input.
/// </summary>
public bool IsActive
{
get
{
return !otherScreenHasFocus &&
(screenState == ScreenState.TransitionOn ||
screenState == ScreenState.Active);
}
}
bool otherScreenHasFocus;
/// <summary>
/// Gets the manager that this screen belongs to.
/// </summary>
public ScreenManager ScreenManager
{
get { return screenManager; }
internal set { screenManager = value; }
}
ScreenManager screenManager;
public KeyboardState KeyboardState
{
get {return Keyboard.GetState();}
}
/// <summary>
/// Gets the index of the player who is currently controlling this screen,
/// or null if it is accepting input from any player. This is used to lock
/// the game to a specific player profile. The main menu responds to input
/// from any connected gamepad, but whichever player makes a selection from
/// this menu is given control over all subsequent screens, so other gamepads
/// are inactive until the controlling player returns to the main menu.
/// </summary>
public PlayerIndex? ControllingPlayer
{
get { return controllingPlayer; }
internal set { controllingPlayer = value; }
}
PlayerIndex? controllingPlayer;
/// <summary>
/// Gets whether or not this screen is serializable. If this is true,
/// the screen will be recorded into the screen manager's state and
/// its Serialize and Deserialize methods will be called as appropriate.
/// If this is false, the screen will be ignored during serialization.
/// By default, all screens are assumed to be serializable.
/// </summary>
public bool IsSerializable
{
get { return isSerializable; }
protected set { isSerializable = value; }
}
bool isSerializable = true;
#endregion
#region Initialization
/// <summary>
/// Load graphics content for the screen.
/// </summary>
public virtual void LoadContent() { }
/// <summary>
/// Unload content for the screen.
/// </summary>
public virtual void UnloadContent() { }
#endregion
#region Update and Draw
/// <summary>
/// Allows the screen to run logic, such as updating the transition position.
/// Unlike HandleInput, this method is called regardless of whether the screen
/// is active, hidden, or in the middle of a transition.
/// </summary>
public virtual void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
this.otherScreenHasFocus = otherScreenHasFocus;
if (isExiting)
{
// If the screen is going away to die, it should transition off.
screenState = ScreenState.TransitionOff;
if (!UpdateTransition(gameTime, transitionOffTime, 1))
{
// When the transition finishes, remove the screen.
ScreenManager.RemoveScreen(this);
}
}
else if (coveredByOtherScreen)
{
// If the screen is covered by another, it should transition off.
if (UpdateTransition(gameTime, transitionOffTime, 1))
{
// Still busy transitioning.
screenState = ScreenState.TransitionOff;
}
else
{
// Transition finished!
screenState = ScreenState.Hidden;
}
}
else
{
// Otherwise the screen should transition on and become active.
if (UpdateTransition(gameTime, transitionOnTime, -1))
{
// Still busy transitioning.
screenState = ScreenState.TransitionOn;
}
else
{
// Transition finished!
screenState = ScreenState.Active;
}
}
}
/// <summary>
/// Helper for updating the screen transition position.
/// </summary>
bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction)
{
// How much should we move by?
float transitionDelta;
if (time == TimeSpan.Zero)
transitionDelta = 1;
else
transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds /
time.TotalMilliseconds);
// Update the transition position.
transitionPosition += transitionDelta * direction;
// Did we reach the end of the transition?
if (((direction < 0) && (transitionPosition <= 0)) ||
((direction > 0) && (transitionPosition >= 1)))
{
transitionPosition = MathHelper.Clamp(transitionPosition, 0, 1);
return false;
}
// Otherwise we are still busy transitioning.
return true;
}
/// <summary>
/// Allows the screen to handle user input. Unlike Update, this method
/// is only called when the screen is active, and not when some other
/// screen has taken the focus.
/// </summary>
public virtual void HandleInput(InputState input)
{
}
public KeyboardState currentKeyState;
public KeyboardState lastKeyState;
public bool IsKeyHit(Keys key)
{
if (currentKeyState.IsKeyDown(key) && lastKeyState.IsKeyUp(key))
return true;
return false;
}
/// <summary>
/// This is called when the screen should draw itself.
/// </summary>
public virtual void Draw(GameTime gameTime)
{
}
#endregion
#region Public Methods
/// <summary>
/// Tells the screen to serialize its state into the given stream.
/// </summary>
public virtual void Serialize(Stream stream) { }
/// <summary>
/// Tells the screen to deserialize its state from the given stream.
/// </summary>
public virtual void Deserialize(Stream stream) { }
/// <summary>
/// Tells the screen to go away. Unlike ScreenManager.RemoveScreen, which
/// instantly kills the screen, this method respects the transition timings
/// and will give the screen a chance to gradually transition off.
/// </summary>
public void ExitScreen()
{
if (TransitionOffTime == TimeSpan.Zero)
{
// If the screen has a zero transition time, remove it immediately.
ScreenManager.RemoveScreen(this);
}
else
{
// Otherwise flag that it should transition off and then exit.
isExiting = true;
}
}
#endregion
#region Helper Methods
/// <summary>
/// A helper method which loads assets using the screen manager's
/// associated game content loader.
/// </summary>
/// <typeparam name="T">Type of asset.</typeparam>
/// <param name="assetName">Asset name, relative to the loader root
/// directory, and not including the .xnb extension.</param>
/// <returns></returns>
public T Load<T>(string assetName)
{
return ScreenManager.Game.Content.Load<T>(assetName);
}
#endregion
}
}
© Game Development or respective owner