#region File Description␍␊ |
//-----------------------------------------------------------------------------␍␊ |
// ScreenManager.cs␍␊ |
//␍␊ |
// Microsoft XNA Community Game Platform␍␊ |
// Copyright (C) Microsoft Corporation. All rights reserved.␍␊ |
//-----------------------------------------------------------------------------␍␊ |
#endregion␍␊ |
␍␊ |
#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 Microsoft.Xna.Framework.Input.Touch;␍␊ |
using System.IO;␍␊ |
using System.IO.IsolatedStorage;␍␊ |
using System.Xml.Linq;␍␊ |
#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␍␊ |
␍␊ |
private const string StateFilename = "ScreenManagerState.xml";␍␊ |
␍␊ |
List<GameScreen> screens = new List<GameScreen>();␍␊ |
List<GameScreen> tempScreensList = new List<GameScreen>();␍␊ |
␍␊ |
InputState input = new InputState();␍␊ |
␍␊ |
SpriteBatch spriteBatch;␍␊ |
SpriteFont font;␍␊ |
Texture2D blankTexture;␍␊ |
␍␊ |
bool isInitialized;␍␊ |
␍␊ |
bool traceEnabled;␍␊ |
␍␊ |
#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; }␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <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; }␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <summary>␍␊ |
/// Gets a blank texture that can be used by the screens.␍␊ |
/// </summary>␍␊ |
public Texture2D BlankTexture␍␊ |
{␍␊ |
get { return blankTexture; }␍␊ |
}␍␊ |
␍␊ |
␍␊ |
#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();␍␊ |
␍␊ |
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>("menufont");␍␊ |
blankTexture = content.Load<Texture2D>("blank");␍␊ |
␍␊ |
// Tell each of the screens to load their content.␍␊ |
foreach (GameScreen screen in screens)␍␊ |
{␍␊ |
screen.Activate(false);␍␊ |
}␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <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.Unload();␍␊ |
}␍␊ |
}␍␊ |
␍␊ |
␍␊ |
#endregion␍␊ |
␍␊ |
#region Update and Draw␍␊ |
␍␊ |
␍␊ |
/// <summary>␍␊ |
/// Allows each screen to run logic.␍␊ |
/// </summary>␍␊ |
public override void Update(GameTime gameTime)␍␊ |
{␍␊ |
// 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.␍␊ |
tempScreensList.Clear();␍␊ |
␍␊ |
foreach (GameScreen screen in screens)␍␊ |
tempScreensList.Add(screen);␍␊ |
␍␊ |
bool otherScreenHasFocus = !Game.IsActive;␍␊ |
bool coveredByOtherScreen = false;␍␊ |
␍␊ |
// Loop as long as there are screens waiting to be updated.␍␊ |
while (tempScreensList.Count > 0)␍␊ |
{␍␊ |
// Pop the topmost screen off the waiting list.␍␊ |
GameScreen screen = tempScreensList[tempScreensList.Count - 1];␍␊ |
␍␊ |
tempScreensList.RemoveAt(tempScreensList.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(gameTime, 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();␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <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)␍␊ |
{␍␊ |
foreach (GameScreen screen in screens)␍␊ |
{␍␊ |
if (screen.ScreenState == ScreenState.Hidden)␍␊ |
continue;␍␊ |
␍␊ |
screen.Draw(gameTime);␍␊ |
}␍␊ |
}␍␊ |
␍␊ |
␍␊ |
#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.Activate(false);␍␊ |
}␍␊ |
␍␊ |
screens.Add(screen);␍␊ |
␍␊ |
// update the TouchPanel to respond to gestures this screen is interested in␍␊ |
TouchPanel.EnabledGestures = screen.EnabledGestures;␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <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.Unload();␍␊ |
}␍␊ |
␍␊ |
screens.Remove(screen);␍␊ |
tempScreensList.Remove(screen);␍␊ |
␍␊ |
// if there is a screen still in the manager, update TouchPanel␍␊ |
// to respond to gestures that screen is interested in.␍␊ |
if (screens.Count > 0)␍␊ |
{␍␊ |
TouchPanel.EnabledGestures = screens[screens.Count - 1].EnabledGestures;␍␊ |
}␍␊ |
}␍␊ |
␍␊ |
␍␊ |
/// <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)␍␊ |
{␍␊ |
spriteBatch.Begin();␍␊ |
spriteBatch.Draw(blankTexture, GraphicsDevice.Viewport.Bounds, Color.Black * alpha);␍␊ |
spriteBatch.End();␍␊ |
}␍␊ |
␍␊ |
/// <summary>␍␊ |
/// Informs the screen manager to serialize its state to disk.␍␊ |
/// </summary>␍␊ |
public void Deactivate()␍␊ |
{␍␊ |
#if !WINDOWS_PHONE␍␊ |
return;␍␊ |
#else␍␊ |
// Open up isolated storage␍␊ |
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())␍␊ |
{␍␊ |
// Create an XML document to hold the list of screen types currently in the stack␍␊ |
XDocument doc = new XDocument();␍␊ |
XElement root = new XElement("ScreenManager");␍␊ |
doc.Add(root);␍␊ |
␍␊ |
// Make a copy of the master screen list, to avoid confusion if␍␊ |
// the process of deactivating one screen adds or removes others.␍␊ |
tempScreensList.Clear();␍␊ |
foreach (GameScreen screen in screens)␍␊ |
tempScreensList.Add(screen);␍␊ |
␍␊ |
// Iterate the screens to store in our XML file and deactivate them␍␊ |
foreach (GameScreen screen in tempScreensList)␍␊ |
{␍␊ |
// Only add the screen to our XML if it is serializable␍␊ |
if (screen.IsSerializable)␍␊ |
{␍␊ |
// We store the screen's controlling player so we can rehydrate that value␍␊ |
string playerValue = screen.ControllingPlayer.HasValue␍␊ |
? screen.ControllingPlayer.Value.ToString()␍␊ |
: "";␍␊ |
␍␊ |
root.Add(new XElement(␍␊ |
"GameScreen",␍␊ |
new XAttribute("Type", screen.GetType().AssemblyQualifiedName),␍␊ |
new XAttribute("ControllingPlayer", playerValue)));␍␊ |
}␍␊ |
␍␊ |
// Deactivate the screen regardless of whether we serialized it␍␊ |
screen.Deactivate();␍␊ |
}␍␊ |
␍␊ |
// Save the document␍␊ |
using (IsolatedStorageFileStream stream = storage.CreateFile(StateFilename))␍␊ |
{␍␊ |
doc.Save(stream);␍␊ |
}␍␊ |
}␍␊ |
#endif␍␊ |
}␍␊ |
␍␊ |
public bool Activate(bool instancePreserved)␍␊ |
{␍␊ |
#if !WINDOWS_PHONE␍␊ |
return false;␍␊ |
#else␍␊ |
// If the game instance was preserved, the game wasn't dehydrated so our screens still exist.␍␊ |
// We just need to activate them and we're ready to go.␍␊ |
if (instancePreserved)␍␊ |
{␍␊ |
// Make a copy of the master screen list, to avoid confusion if␍␊ |
// the process of activating one screen adds or removes others.␍␊ |
tempScreensList.Clear();␍␊ |
␍␊ |
foreach (GameScreen screen in screens)␍␊ |
tempScreensList.Add(screen);␍␊ |
␍␊ |
foreach (GameScreen screen in tempScreensList)␍␊ |
screen.Activate(true);␍␊ |
}␍␊ |
␍␊ |
// Otherwise we need to refer to our saved file and reconstruct the screens that were present␍␊ |
// when the game was deactivated.␍␊ |
else␍␊ |
{␍␊ |
// Try to get the screen factory from the services, which is required to recreate the screens␍␊ |
IScreenFactory screenFactory = Game.Services.GetService(typeof(IScreenFactory)) as IScreenFactory;␍␊ |
if (screenFactory == null)␍␊ |
{␍␊ |
throw new InvalidOperationException(␍␊ |
"Game.Services must contain an IScreenFactory in order to activate the ScreenManager.");␍␊ |
}␍␊ |
␍␊ |
// Open up isolated storage␍␊ |
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())␍␊ |
{␍␊ |
// Check for the file; if it doesn't exist we can't restore state␍␊ |
if (!storage.FileExists(StateFilename))␍␊ |
return false;␍␊ |
␍␊ |
// Read the state file so we can build up our screens␍␊ |
using (IsolatedStorageFileStream stream = storage.OpenFile(StateFilename, FileMode.Open))␍␊ |
{␍␊ |
XDocument doc = XDocument.Load(stream);␍␊ |
␍␊ |
// Iterate the document to recreate the screen stack␍␊ |
foreach (XElement screenElem in doc.Root.Elements("GameScreen"))␍␊ |
{␍␊ |
// Use the factory to create the screen␍␊ |
Type screenType = Type.GetType(screenElem.Attribute("Type").Value);␍␊ |
GameScreen screen = screenFactory.CreateScreen(screenType);␍␊ |
␍␊ |
// Rehydrate the controlling player for the screen␍␊ |
PlayerIndex? controllingPlayer = screenElem.Attribute("ControllingPlayer").Value != ""␍␊ |
? (PlayerIndex)Enum.Parse(typeof(PlayerIndex), screenElem.Attribute("ControllingPlayer").Value, true)␍␊ |
: (PlayerIndex?)null;␍␊ |
screen.ControllingPlayer = controllingPlayer;␍␊ |
␍␊ |
// Add the screen to the screens list and activate the screen␍␊ |
screen.ScreenManager = this;␍␊ |
screens.Add(screen);␍␊ |
screen.Activate(false);␍␊ |
␍␊ |
// update the TouchPanel to respond to gestures this screen is interested in␍␊ |
TouchPanel.EnabledGestures = screen.EnabledGestures;␍␊ |
}␍␊ |
}␍␊ |
}␍␊ |
}␍␊ |
␍␊ |
return true;␍␊ |
#endif␍␊ |
}␍␊ |
␍␊ |
#endregion␍␊ |
}␍␊ |
}␍␊ |