#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; using FarseerPhysics.SamplesFramework; using Axios.Engine; #endregion namespace GameStateManagement { /// /// 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. /// public class ScreenManager : DrawableGameComponent { #region Fields private const string StateFilename = "ScreenManagerState.xml"; List screens = new List(); List tempScreensList = new List(); InputState input; SpriteBatch spriteBatch; SpriteFont font; Texture2D blankTexture; bool isInitialized; bool traceEnabled; /// /// Contains all the fonts avaliable for use. /// private SpriteFonts _spriteFonts; #endregion #region Properties public InputState InputState { get { return input; } private set { input = value; } } public SpriteFonts Fonts { get { return _spriteFonts; } } /// /// A default SpriteBatch shared by all the screens. This saves /// each screen having to bother creating their own local instance. /// public SpriteBatch SpriteBatch { get { return spriteBatch; } } /// /// A default font shared by all the screens. This saves /// each screen having to bother loading their own local copy. /// public SpriteFont Font { get { return font; } } /// /// 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. /// public bool TraceEnabled { get { return traceEnabled; } set { traceEnabled = value; } } /// /// Gets a blank texture that can be used by the screens. /// public Texture2D BlankTexture { get { return blankTexture; } } #endregion #region Initialization /// /// Constructs a new screen manager component. /// 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; this.input = new InputState(this); } /// /// Initializes the screen manager component. /// public override void Initialize() { base.Initialize(); isInitialized = true; } /// /// Load your graphics content. /// protected override void LoadContent() { // Load content belonging to the screen manager. ContentManager content = new ContentManager(this.Game.Services, "Content/Fonts"); _spriteFonts = new SpriteFonts(content); spriteBatch = new SpriteBatch(GraphicsDevice); font = content.Load("menufont"); blankTexture = Game.Content.Load("Materials/blank"); GameServices.AddService(this.Game.GraphicsDevice); GameServices.AddService(this.Game.Content); // It is advised to use one instance of Random // because Random is seeded with the current time // initilizing random objects too quickly can cause // the impression of generating the same value // http://stackoverflow.com/questions/2727538/random-encounter-not-so-random GameServices.AddService(new Random()); AxiosRandom.init(); input.LoadContent(); // Tell each of the screens to load their content. foreach (GameScreen screen in screens) { screen.Activate(false); } } /// /// Unload your graphics content. /// 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 /// /// Allows each screen to run logic. /// public override void Update(GameTime gameTime) { // Read the keyboard and gamepad. input.Update(gameTime); // 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) { // The default implementation of screens aren't aware that it's // being woke up input.ShowCursor = screen.HasCursor; input.EnableVirtualStick = screen.HasVirtualStick; 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(); } /// /// Prints a list of all the screens, for debugging. /// void TraceScreens() { List screenNames = new List(); foreach (GameScreen screen in screens) screenNames.Add(screen.GetType().Name); Debug.WriteLine(string.Join(", ", screenNames.ToArray())); } /// /// Tells each screen to draw itself. /// public override void Draw(GameTime gameTime) { foreach (GameScreen screen in screens) { if (screen.ScreenState == ScreenState.Hidden) continue; screen.Draw(gameTime); } input.Draw(); } #endregion #region Public Methods /// /// Adds a new screen to the screen manager. /// 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; } /// /// Adds a new screen to the screen manager with a default PlayerIndex of one /// public void AddScreen(GameScreen screen) { screen.ControllingPlayer = PlayerIndex.One; 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; } /// /// 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. /// 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; } } /// /// 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. /// public GameScreen[] GetScreens() { return screens.ToArray(); } /// /// Helper draws a translucent black fullscreen sprite, used for fading /// screens in and out, and for darkening the background behind popups. /// public void FadeBackBufferToBlack(float alpha) { spriteBatch.Begin(); spriteBatch.Draw(blankTexture, GraphicsDevice.Viewport.Bounds, Color.Black * alpha); spriteBatch.End(); } /// /// Informs the screen manager to serialize its state to disk. /// 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 } }