ScreenManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // ScreenManager.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using Microsoft.Xna.Framework;
  11. using Microsoft.Xna.Framework.Content;
  12. using Microsoft.Xna.Framework.Graphics;
  13. using Microsoft.Xna.Framework.Input.Touch;
  14. using System.Collections.Generic;
  15. using System.Diagnostics;
  16. #endregion
  17. namespace GameStateManagement
  18. {
  19. /// <summary>
  20. /// The screen manager is a component which manages one or more GameScreen
  21. /// instances. It maintains a stack of screens, calls their Update and Draw
  22. /// methods at the appropriate times, and automatically routes input to the
  23. /// topmost active screen.
  24. /// </summary>
  25. public class ScreenManager : DrawableGameComponent
  26. {
  27. #region Fields
  28. private const string StateFilename = "ScreenManagerState.xml";
  29. List<GameScreen> screens = new List<GameScreen>();
  30. List<GameScreen> tempScreensList = new List<GameScreen>();
  31. InputState input = new InputState();
  32. SpriteBatch spriteBatch;
  33. SpriteFont font;
  34. Texture2D blankTexture;
  35. bool isInitialized;
  36. bool traceEnabled;
  37. #endregion
  38. #region Properties
  39. /// <summary>
  40. /// A default SpriteBatch shared by all the screens. This saves
  41. /// each screen having to bother creating their own local instance.
  42. /// </summary>
  43. public SpriteBatch SpriteBatch
  44. {
  45. get { return spriteBatch; }
  46. }
  47. /// <summary>
  48. /// A default font shared by all the screens. This saves
  49. /// each screen having to bother loading their own local copy.
  50. /// </summary>
  51. public SpriteFont Font
  52. {
  53. get { return font; }
  54. }
  55. /// <summary>
  56. /// If true, the manager prints out a list of all the screens
  57. /// each time it is updated. This can be useful for making sure
  58. /// everything is being added and removed at the right times.
  59. /// </summary>
  60. public bool TraceEnabled
  61. {
  62. get { return traceEnabled; }
  63. set { traceEnabled = value; }
  64. }
  65. /// <summary>
  66. /// Gets a blank texture that can be used by the screens.
  67. /// </summary>
  68. public Texture2D BlankTexture
  69. {
  70. get { return blankTexture; }
  71. }
  72. #endregion
  73. #region Initialization
  74. /// <summary>
  75. /// Constructs a new screen manager component.
  76. /// </summary>
  77. public ScreenManager(Game game)
  78. : base(game)
  79. {
  80. // we must set EnabledGestures before we can query for them, but
  81. // we don't assume the game wants to read them.
  82. TouchPanel.EnabledGestures = GestureType.None;
  83. }
  84. /// <summary>
  85. /// Initializes the screen manager component.
  86. /// </summary>
  87. public override void Initialize()
  88. {
  89. base.Initialize();
  90. isInitialized = true;
  91. }
  92. /// <summary>
  93. /// Load your graphics content.
  94. /// </summary>
  95. protected override void LoadContent()
  96. {
  97. // Load content belonging to the screen manager.
  98. ContentManager content = Game.Content;
  99. spriteBatch = new SpriteBatch(GraphicsDevice);
  100. font = content.Load<SpriteFont>("menufont");
  101. blankTexture = content.Load<Texture2D>("blank");
  102. // Tell each of the screens to load their content.
  103. foreach (GameScreen screen in screens)
  104. {
  105. screen.Activate(false);
  106. }
  107. }
  108. /// <summary>
  109. /// Unload your graphics content.
  110. /// </summary>
  111. protected override void UnloadContent()
  112. {
  113. // Tell each of the screens to unload their content.
  114. foreach (GameScreen screen in screens)
  115. {
  116. screen.Unload();
  117. }
  118. }
  119. #endregion
  120. #region Update and Draw
  121. /// <summary>
  122. /// Allows each screen to run logic.
  123. /// </summary>
  124. public override void Update(GameTime gameTime)
  125. {
  126. // Read the keyboard and gamepad.
  127. input.Update();
  128. // Make a copy of the master screen list, to avoid confusion if
  129. // the process of updating one screen adds or removes others.
  130. tempScreensList.Clear();
  131. foreach (GameScreen screen in screens)
  132. tempScreensList.Add(screen);
  133. bool otherScreenHasFocus = !Game.IsActive;
  134. bool coveredByOtherScreen = false;
  135. // Loop as long as there are screens waiting to be updated.
  136. while (tempScreensList.Count > 0)
  137. {
  138. // Pop the topmost screen off the waiting list.
  139. GameScreen screen = tempScreensList[tempScreensList.Count - 1];
  140. tempScreensList.RemoveAt(tempScreensList.Count - 1);
  141. // Update the screen.
  142. screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  143. if (screen.ScreenState == ScreenState.TransitionOn ||
  144. screen.ScreenState == ScreenState.Active)
  145. {
  146. // If this is the first active screen we came across,
  147. // give it a chance to handle input.
  148. if (!otherScreenHasFocus)
  149. {
  150. screen.HandleInput(gameTime, input);
  151. otherScreenHasFocus = true;
  152. }
  153. // If this is an active non-popup, inform any subsequent
  154. // screens that they are covered by it.
  155. if (!screen.IsPopup)
  156. coveredByOtherScreen = true;
  157. }
  158. }
  159. // Print debug trace?
  160. if (traceEnabled)
  161. TraceScreens();
  162. }
  163. /// <summary>
  164. /// Prints a list of all the screens, for debugging.
  165. /// </summary>
  166. void TraceScreens()
  167. {
  168. List<string> screenNames = new List<string>();
  169. foreach (GameScreen screen in screens)
  170. screenNames.Add(screen.GetType().Name);
  171. Debug.WriteLine(string.Join(", ", screenNames.ToArray()));
  172. }
  173. /// <summary>
  174. /// Tells each screen to draw itself.
  175. /// </summary>
  176. public override void Draw(GameTime gameTime)
  177. {
  178. foreach (GameScreen screen in screens)
  179. {
  180. if (screen.ScreenState == ScreenState.Hidden)
  181. continue;
  182. screen.Draw(gameTime);
  183. }
  184. }
  185. #endregion
  186. #region Public Methods
  187. /// <summary>
  188. /// Adds a new screen to the screen manager.
  189. /// </summary>
  190. public void AddScreen(GameScreen screen, PlayerIndex? controllingPlayer)
  191. {
  192. screen.ControllingPlayer = controllingPlayer;
  193. screen.ScreenManager = this;
  194. screen.IsExiting = false;
  195. // If we have a graphics device, tell the screen to load content.
  196. if (isInitialized)
  197. {
  198. screen.Activate(false);
  199. }
  200. screens.Add(screen);
  201. // update the TouchPanel to respond to gestures this screen is interested in
  202. TouchPanel.EnabledGestures = screen.EnabledGestures;
  203. }
  204. /// <summary>
  205. /// Removes a screen from the screen manager. You should normally
  206. /// use GameScreen.ExitScreen instead of calling this directly, so
  207. /// the screen can gradually transition off rather than just being
  208. /// instantly removed.
  209. /// </summary>
  210. public void RemoveScreen(GameScreen screen)
  211. {
  212. // If we have a graphics device, tell the screen to unload content.
  213. if (isInitialized)
  214. {
  215. screen.Unload();
  216. }
  217. screens.Remove(screen);
  218. tempScreensList.Remove(screen);
  219. // if there is a screen still in the manager, update TouchPanel
  220. // to respond to gestures that screen is interested in.
  221. if (screens.Count > 0)
  222. {
  223. TouchPanel.EnabledGestures = screens[screens.Count - 1].EnabledGestures;
  224. }
  225. }
  226. /// <summary>
  227. /// Expose an array holding all the screens. We return a copy rather
  228. /// than the real master list, because screens should only ever be added
  229. /// or removed using the AddScreen and RemoveScreen methods.
  230. /// </summary>
  231. public GameScreen[] GetScreens()
  232. {
  233. return screens.ToArray();
  234. }
  235. /// <summary>
  236. /// Helper draws a translucent black fullscreen sprite, used for fading
  237. /// screens in and out, and for darkening the background behind popups.
  238. /// </summary>
  239. public void FadeBackBufferToBlack(float alpha)
  240. {
  241. spriteBatch.Begin();
  242. spriteBatch.Draw(blankTexture, GraphicsDevice.Viewport.Bounds, Color.Black * alpha);
  243. spriteBatch.End();
  244. }
  245. /// <summary>
  246. /// Informs the screen manager to serialize its state to disk.
  247. /// </summary>
  248. public void Deactivate()
  249. {
  250. #if !WINDOWS_PHONE
  251. return;
  252. #else
  253. // Open up isolated storage
  254. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  255. {
  256. // Create an XML document to hold the list of screen types currently in the stack
  257. XDocument doc = new XDocument();
  258. XElement root = new XElement("ScreenManager");
  259. doc.Add(root);
  260. // Make a copy of the master screen list, to avoid confusion if
  261. // the process of deactivating one screen adds or removes others.
  262. tempScreensList.Clear();
  263. foreach (GameScreen screen in screens)
  264. tempScreensList.Add(screen);
  265. // Iterate the screens to store in our XML file and deactivate them
  266. foreach (GameScreen screen in tempScreensList)
  267. {
  268. // Only add the screen to our XML if it is serializable
  269. if (screen.IsSerializable)
  270. {
  271. // We store the screen's controlling player so we can rehydrate that value
  272. string playerValue = screen.ControllingPlayer.HasValue
  273. ? screen.ControllingPlayer.Value.ToString()
  274. : "";
  275. root.Add(new XElement(
  276. "GameScreen",
  277. new XAttribute("Type", screen.GetType().AssemblyQualifiedName),
  278. new XAttribute("ControllingPlayer", playerValue)));
  279. }
  280. // Deactivate the screen regardless of whether we serialized it
  281. screen.Deactivate();
  282. }
  283. // Save the document
  284. using (IsolatedStorageFileStream stream = storage.CreateFile(StateFilename))
  285. {
  286. doc.Save(stream);
  287. }
  288. }
  289. #endif
  290. }
  291. public bool Activate(bool instancePreserved)
  292. {
  293. #if !WINDOWS_PHONE
  294. return false;
  295. #else
  296. // If the game instance was preserved, the game wasn't dehydrated so our screens still exist.
  297. // We just need to activate them and we're ready to go.
  298. if (instancePreserved)
  299. {
  300. // Make a copy of the master screen list, to avoid confusion if
  301. // the process of activating one screen adds or removes others.
  302. tempScreensList.Clear();
  303. foreach (GameScreen screen in screens)
  304. tempScreensList.Add(screen);
  305. foreach (GameScreen screen in tempScreensList)
  306. screen.Activate(true);
  307. }
  308. // Otherwise we need to refer to our saved file and reconstruct the screens that were present
  309. // when the game was deactivated.
  310. else
  311. {
  312. // Try to get the screen factory from the services, which is required to recreate the screens
  313. IScreenFactory screenFactory = Game.Services.GetService(typeof(IScreenFactory)) as IScreenFactory;
  314. if (screenFactory == null)
  315. {
  316. throw new InvalidOperationException(
  317. "Game.Services must contain an IScreenFactory in order to activate the ScreenManager.");
  318. }
  319. // Open up isolated storage
  320. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  321. {
  322. // Check for the file; if it doesn't exist we can't restore state
  323. if (!storage.FileExists(StateFilename))
  324. return false;
  325. // Read the state file so we can build up our screens
  326. using (IsolatedStorageFileStream stream = storage.OpenFile(StateFilename, FileMode.Open))
  327. {
  328. XDocument doc = XDocument.Load(stream);
  329. // Iterate the document to recreate the screen stack
  330. foreach (XElement screenElem in doc.Root.Elements("GameScreen"))
  331. {
  332. // Use the factory to create the screen
  333. Type screenType = Type.GetType(screenElem.Attribute("Type").Value);
  334. GameScreen screen = screenFactory.CreateScreen(screenType);
  335. // Rehydrate the controlling player for the screen
  336. PlayerIndex? controllingPlayer = screenElem.Attribute("ControllingPlayer").Value != ""
  337. ? (PlayerIndex)Enum.Parse(typeof(PlayerIndex), screenElem.Attribute("ControllingPlayer").Value, true)
  338. : (PlayerIndex?)null;
  339. screen.ControllingPlayer = controllingPlayer;
  340. // Add the screen to the screens list and activate the screen
  341. screen.ScreenManager = this;
  342. screens.Add(screen);
  343. screen.Activate(false);
  344. // update the TouchPanel to respond to gestures this screen is interested in
  345. TouchPanel.EnabledGestures = screen.EnabledGestures;
  346. }
  347. }
  348. }
  349. }
  350. return true;
  351. #endif
  352. }
  353. #endregion
  354. }
  355. }