| #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␍␊ |
| }␍␊ |
| }␍␊ |