ScreenManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. //-----------------------------------------------------------------------------
  2. // ScreenManager.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Diagnostics;
  9. using System.Collections.Generic;
  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.IO;
  15. using System.IO.IsolatedStorage;
  16. namespace GameStateManagement
  17. {
  18. /// <summary>
  19. /// The screen manager is a component which manages one or more GameScreen
  20. /// instances. It maintains a stack of screens, calls their Update and Draw
  21. /// methods at the appropriate times, and automatically routes input to the
  22. /// topmost active screen.
  23. /// </summary>
  24. public class ScreenManager : DrawableGameComponent
  25. {
  26. List<GameScreen> screens = new List<GameScreen>();
  27. List<GameScreen> screensToUpdate = new List<GameScreen>();
  28. InputState input = new InputState();
  29. SpriteBatch spriteBatch;
  30. SpriteFont font;
  31. Texture2D blankTexture;
  32. bool isInitialized;
  33. bool traceEnabled;
  34. /// <summary>
  35. /// A default SpriteBatch shared by all the screens. This saves
  36. /// each screen having to bother creating their own local instance.
  37. /// </summary>
  38. public SpriteBatch SpriteBatch
  39. {
  40. get { return spriteBatch; }
  41. }
  42. /// <summary>
  43. /// A default font shared by all the screens. This saves
  44. /// each screen having to bother loading their own local copy.
  45. /// </summary>
  46. public SpriteFont Font
  47. {
  48. get { return font; }
  49. }
  50. /// <summary>
  51. /// If true, the manager prints out a list of all the screens
  52. /// each time it is updated. This can be useful for making sure
  53. /// everything is being added and removed at the right times.
  54. /// </summary>
  55. public bool TraceEnabled
  56. {
  57. get { return traceEnabled; }
  58. set { traceEnabled = value; }
  59. }
  60. /// <summary>
  61. /// Constructs a new screen manager component.
  62. /// </summary>
  63. public ScreenManager(Game game)
  64. : base(game)
  65. {
  66. // we must set EnabledGestures before we can query for them, but
  67. // we don't assume the game wants to read them.
  68. TouchPanel.EnabledGestures = GestureType.None;
  69. }
  70. /// <summary>
  71. /// Initializes the screen manager component.
  72. /// </summary>
  73. public override void Initialize()
  74. {
  75. base.Initialize();
  76. isInitialized = true;
  77. }
  78. /// <summary>
  79. /// Load your graphics content.
  80. /// </summary>
  81. protected override void LoadContent()
  82. {
  83. // Load content belonging to the screen manager.
  84. ContentManager content = Game.Content;
  85. spriteBatch = new SpriteBatch(GraphicsDevice);
  86. font = content.Load<SpriteFont>("Fonts/MenuFont");
  87. blankTexture = content.Load<Texture2D>("Textures/Backgrounds/blank");
  88. // Tell each of the screens to load their content.
  89. foreach (GameScreen screen in screens)
  90. {
  91. screen.LoadContent();
  92. }
  93. }
  94. /// <summary>
  95. /// Unload your graphics content.
  96. /// </summary>
  97. protected override void UnloadContent()
  98. {
  99. // Tell each of the screens to unload their content.
  100. foreach (GameScreen screen in screens)
  101. {
  102. screen.UnloadContent();
  103. }
  104. }
  105. /// <summary>
  106. /// Allows each screen to run logic.
  107. /// </summary>
  108. public override void Update(GameTime gameTime)
  109. {
  110. // Read the keyboard and gamepad.
  111. input.Update();
  112. // Make a copy of the master screen list, to avoid confusion if
  113. // the process of updating one screen adds or removes others.
  114. screensToUpdate.Clear();
  115. foreach (GameScreen screen in screens)
  116. screensToUpdate.Add(screen);
  117. bool otherScreenHasFocus = !Game.IsActive;
  118. bool coveredByOtherScreen = false;
  119. // Loop as long as there are screens waiting to be updated.
  120. while (screensToUpdate.Count > 0)
  121. {
  122. // Pop the topmost screen off the waiting list.
  123. GameScreen screen = screensToUpdate[screensToUpdate.Count - 1];
  124. screensToUpdate.RemoveAt(screensToUpdate.Count - 1);
  125. // Update the screen.
  126. screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  127. if (screen.ScreenState == ScreenState.TransitionOn ||
  128. screen.ScreenState == ScreenState.Active)
  129. {
  130. // If this is the first active screen we came across,
  131. // give it a chance to handle input.
  132. if (!otherScreenHasFocus)
  133. {
  134. screen.HandleInput(input);
  135. otherScreenHasFocus = true;
  136. }
  137. // If this is an active non-popup, inform any subsequent
  138. // screens that they are covered by it.
  139. if (!screen.IsPopup)
  140. coveredByOtherScreen = true;
  141. }
  142. }
  143. // Print debug trace?
  144. if (traceEnabled)
  145. TraceScreens();
  146. }
  147. /// <summary>
  148. /// Prints a list of all the screens, for debugging.
  149. /// </summary>
  150. void TraceScreens()
  151. {
  152. List<string> screenNames = new List<string>();
  153. foreach (GameScreen screen in screens)
  154. screenNames.Add(screen.GetType().Name);
  155. Debug.WriteLine(string.Join(", ", screenNames.ToArray()));
  156. }
  157. /// <summary>
  158. /// Tells each screen to draw itself.
  159. /// </summary>
  160. public override void Draw(GameTime gameTime)
  161. {
  162. foreach (GameScreen screen in screens)
  163. {
  164. if (screen.ScreenState == ScreenState.Hidden)
  165. continue;
  166. screen.Draw(gameTime);
  167. }
  168. }
  169. /// <summary>
  170. /// Adds a new screen to the screen manager.
  171. /// </summary>
  172. public void AddScreen(GameScreen screen, PlayerIndex? controllingPlayer)
  173. {
  174. screen.ControllingPlayer = controllingPlayer;
  175. screen.ScreenManager = this;
  176. screen.IsExiting = false;
  177. // If we have a graphics device, tell the screen to load content.
  178. if (isInitialized)
  179. {
  180. screen.LoadContent();
  181. }
  182. screens.Add(screen);
  183. // update the TouchPanel to respond to gestures this screen is interested in
  184. TouchPanel.EnabledGestures = screen.EnabledGestures;
  185. }
  186. /// <summary>
  187. /// Removes a screen from the screen manager. You should normally
  188. /// use GameScreen.ExitScreen instead of calling this directly, so
  189. /// the screen can gradually transition off rather than just being
  190. /// instantly removed.
  191. /// </summary>
  192. public void RemoveScreen(GameScreen screen)
  193. {
  194. // If we have a graphics device, tell the screen to unload content.
  195. if (isInitialized)
  196. {
  197. screen.UnloadContent();
  198. }
  199. screens.Remove(screen);
  200. screensToUpdate.Remove(screen);
  201. // if there is a screen still in the manager, update TouchPanel
  202. // to respond to gestures that screen is interested in.
  203. if (screens.Count > 0)
  204. {
  205. TouchPanel.EnabledGestures = screens[screens.Count - 1].EnabledGestures;
  206. }
  207. }
  208. /// <summary>
  209. /// Expose an array holding all the screens. We return a copy rather
  210. /// than the real master list, because screens should only ever be added
  211. /// or removed using the AddScreen and RemoveScreen methods.
  212. /// </summary>
  213. public GameScreen[] GetScreens()
  214. {
  215. return screens.ToArray();
  216. }
  217. /// <summary>
  218. /// Helper draws a translucent black fullscreen sprite, used for fading
  219. /// screens in and out, and for darkening the background behind popups.
  220. /// </summary>
  221. public void FadeBackBufferToBlack(float alpha)
  222. {
  223. Viewport viewport = GraphicsDevice.Viewport;
  224. spriteBatch.Begin();
  225. spriteBatch.Draw(blankTexture,
  226. new Rectangle(0, 0, viewport.Width, viewport.Height),
  227. Color.Black * alpha);
  228. spriteBatch.End();
  229. }
  230. /// <summary>
  231. /// Informs the screen manager to serialize its state to disk.
  232. /// </summary>
  233. public void SerializeState()
  234. {
  235. // open up isolated storage
  236. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  237. {
  238. // if our screen manager directory already exists, delete the contents
  239. if (storage.DirectoryExists("ScreenManager"))
  240. {
  241. DeleteState(storage);
  242. }
  243. // otherwise just create the directory
  244. else
  245. {
  246. storage.CreateDirectory("ScreenManager");
  247. }
  248. // create a file we'll use to store the list of screens in the stack
  249. using (IsolatedStorageFileStream stream = storage.CreateFile("ScreenManager\\ScreenList.dat"))
  250. {
  251. using (BinaryWriter writer = new BinaryWriter(stream))
  252. {
  253. // write out the full name of all the types in our stack so we can
  254. // recreate them if needed.
  255. foreach (GameScreen screen in screens)
  256. {
  257. if (screen.IsSerializable)
  258. {
  259. writer.Write(screen.GetType().AssemblyQualifiedName);
  260. }
  261. }
  262. }
  263. }
  264. // now we create a new file stream for each screen so it can save its state
  265. // if it needs to. we name each file "ScreenX.dat" where X is the index of
  266. // the screen in the stack, to ensure the files are uniquely named
  267. int screenIndex = 0;
  268. foreach (GameScreen screen in screens)
  269. {
  270. if (screen.IsSerializable)
  271. {
  272. string fileName = string.Format("ScreenManager\\Screen{0}.dat", screenIndex);
  273. // open up the stream and let the screen serialize whatever state it wants
  274. using (IsolatedStorageFileStream stream = storage.CreateFile(fileName))
  275. {
  276. screen.Serialize(stream);
  277. }
  278. screenIndex++;
  279. }
  280. }
  281. }
  282. }
  283. public bool DeserializeState()
  284. {
  285. // open up isolated storage
  286. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  287. {
  288. // see if our saved state directory exists
  289. if (storage.DirectoryExists("ScreenManager"))
  290. {
  291. try
  292. {
  293. // see if we have a screen list
  294. if (storage.FileExists("ScreenManager\\ScreenList.dat"))
  295. {
  296. // load the list of screen types
  297. using (IsolatedStorageFileStream stream =
  298. storage.OpenFile("ScreenManager\\ScreenList.dat", FileMode.Open, FileAccess.Read))
  299. {
  300. using (BinaryReader reader = new BinaryReader(stream))
  301. {
  302. while (reader.BaseStream.Position < reader.BaseStream.Length)
  303. {
  304. // read a line from our file
  305. string line = reader.ReadString();
  306. // if it isn't blank, we can create a screen from it
  307. if (!string.IsNullOrEmpty(line))
  308. {
  309. Type screenType = Type.GetType(line);
  310. GameScreen screen = Activator.CreateInstance(screenType) as GameScreen;
  311. AddScreen(screen, PlayerIndex.One);
  312. }
  313. }
  314. }
  315. }
  316. }
  317. // next we give each screen a chance to deserialize from the disk
  318. for (int i = 0; i < screens.Count; i++)
  319. {
  320. string filename = string.Format("ScreenManager\\Screen{0}.dat", i);
  321. using (IsolatedStorageFileStream stream =
  322. storage.OpenFile(filename, FileMode.Open, FileAccess.Read))
  323. {
  324. screens[i].Deserialize(stream);
  325. }
  326. }
  327. return true;
  328. }
  329. catch (Exception)
  330. {
  331. // if an exception was thrown while reading, odds are we cannot recover
  332. // from the saved state, so we will delete it so the game can correctly
  333. // launch.
  334. DeleteState(storage);
  335. }
  336. }
  337. }
  338. return false;
  339. }
  340. /// <summary>
  341. /// Deletes the saved state files from isolated storage.
  342. /// </summary>
  343. private void DeleteState(IsolatedStorageFile storage)
  344. {
  345. // get all of the files in the directory and delete them
  346. string[] files = storage.GetFileNames("ScreenManager\\*");
  347. foreach (string file in files)
  348. {
  349. storage.DeleteFile(Path.Combine("ScreenManager", file));
  350. }
  351. }
  352. }
  353. }