axiosengine/axios/ScreenSystem/ScreenManager.cs

505 lines
18 KiB
C#

#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
{
/// <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;
SpriteBatch spriteBatch;
SpriteFont font;
Texture2D blankTexture;
bool isInitialized;
bool traceEnabled;
/// <summary>
/// Contains all the fonts avaliable for use.
/// </summary>
private SpriteFonts _spriteFonts;
#endregion
#region Properties
public InputState InputState
{
get { return input; }
private set { input = value; }
}
public SpriteFonts Fonts
{
get { return _spriteFonts; }
}
/// <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;
this.input = new InputState(this);
}
/// <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 = new ContentManager(this.Game.Services, "Content/Fonts");
_spriteFonts = new SpriteFonts(content);
spriteBatch = new SpriteBatch(GraphicsDevice);
font = content.Load<SpriteFont>("menufont");
blankTexture = Game.Content.Load<Texture2D>("Materials/blank");
GameServices.AddService<GraphicsDevice>(this.Game.GraphicsDevice);
GameServices.AddService<ContentManager>(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<Random>(new Random());
AxiosRandom.init();
input.LoadContent();
// 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(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();
}
/// <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);
}
input.Draw();
}
#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>
/// Adds a new screen to the screen manager with a default PlayerIndex of one
/// </summary>
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;
}
/// <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
}
}