ScreenManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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. Console.WriteLine(string.Join(", ", screenNames.ToArray()));
  156. }
  157. int count = 0;
  158. /// <summary>
  159. /// Tells each screen to draw itself.
  160. /// </summary>
  161. public override void Draw(GameTime gameTime)
  162. {
  163. foreach (GameScreen screen in screens)
  164. {
  165. if (screen.ScreenState == ScreenState.Hidden)
  166. continue;
  167. screen.Draw(gameTime);
  168. }
  169. }
  170. /// <summary>
  171. /// Adds a new screen to the screen manager.
  172. /// </summary>
  173. public void AddScreen(GameScreen screen, PlayerIndex? controllingPlayer)
  174. {
  175. screen.ControllingPlayer = controllingPlayer;
  176. screen.ScreenManager = this;
  177. screen.IsExiting = false;
  178. // If we have a graphics device, tell the screen to load content.
  179. if (isInitialized)
  180. {
  181. screen.LoadContent();
  182. }
  183. screens.Add(screen);
  184. // update the TouchPanel to respond to gestures this screen is interested in
  185. TouchPanel.EnabledGestures = screen.EnabledGestures;
  186. }
  187. /// <summary>
  188. /// Removes a screen from the screen manager. You should normally
  189. /// use GameScreen.ExitScreen instead of calling this directly, so
  190. /// the screen can gradually transition off rather than just being
  191. /// instantly removed.
  192. /// </summary>
  193. public void RemoveScreen(GameScreen screen)
  194. {
  195. // If we have a graphics device, tell the screen to unload content.
  196. if (isInitialized)
  197. {
  198. screen.UnloadContent();
  199. }
  200. screens.Remove(screen);
  201. screensToUpdate.Remove(screen);
  202. // if there is a screen still in the manager, update TouchPanel
  203. // to respond to gestures that screen is interested in.
  204. if (screens.Count > 0)
  205. {
  206. TouchPanel.EnabledGestures = screens[screens.Count - 1].EnabledGestures;
  207. }
  208. }
  209. /// <summary>
  210. /// Expose an array holding all the screens. We return a copy rather
  211. /// than the real master list, because screens should only ever be added
  212. /// or removed using the AddScreen and RemoveScreen methods.
  213. /// </summary>
  214. public GameScreen[] GetScreens()
  215. {
  216. return screens.ToArray();
  217. }
  218. /// <summary>
  219. /// Helper draws a translucent black fullscreen sprite, used for fading
  220. /// screens in and out, and for darkening the background behind popups.
  221. /// </summary>
  222. public void FadeBackBufferToBlack(float alpha)
  223. {
  224. Viewport viewport = GraphicsDevice.Viewport;
  225. spriteBatch.Begin();
  226. spriteBatch.Draw(blankTexture,
  227. new Rectangle(0, 0, viewport.Width, viewport.Height),
  228. Color.Black * alpha);
  229. spriteBatch.End();
  230. }
  231. /// <summary>
  232. /// Informs the screen manager to serialize its state to disk.
  233. /// </summary>
  234. public void SerializeState()
  235. {
  236. // open up isolated storage
  237. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  238. {
  239. // if our screen manager directory already exists, delete the contents
  240. if (storage.DirectoryExists("ScreenManager"))
  241. {
  242. DeleteState(storage);
  243. }
  244. // otherwise just create the directory
  245. else
  246. {
  247. storage.CreateDirectory("ScreenManager");
  248. }
  249. // create a file we'll use to store the list of screens in the stack
  250. using (IsolatedStorageFileStream stream = storage.CreateFile("ScreenManager\\ScreenList.dat"))
  251. {
  252. using (BinaryWriter writer = new BinaryWriter(stream))
  253. {
  254. // write out the full name of all the types in our stack so we can
  255. // recreate them if needed.
  256. foreach (GameScreen screen in screens)
  257. {
  258. if (screen.IsSerializable)
  259. {
  260. writer.Write(screen.GetType().AssemblyQualifiedName);
  261. }
  262. }
  263. }
  264. }
  265. // now we create a new file stream for each screen so it can save its state
  266. // if it needs to. we name each file "ScreenX.dat" where X is the index of
  267. // the screen in the stack, to ensure the files are uniquely named
  268. int screenIndex = 0;
  269. foreach (GameScreen screen in screens)
  270. {
  271. if (screen.IsSerializable)
  272. {
  273. string fileName = string.Format("ScreenManager\\Screen{0}.dat", screenIndex);
  274. // open up the stream and let the screen serialize whatever state it wants
  275. using (IsolatedStorageFileStream stream = storage.CreateFile(fileName))
  276. {
  277. screen.Serialize(stream);
  278. }
  279. screenIndex++;
  280. }
  281. }
  282. }
  283. }
  284. public bool DeserializeState()
  285. {
  286. // open up isolated storage
  287. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  288. {
  289. // see if our saved state directory exists
  290. if (storage.DirectoryExists("ScreenManager"))
  291. {
  292. try
  293. {
  294. // see if we have a screen list
  295. if (storage.FileExists("ScreenManager\\ScreenList.dat"))
  296. {
  297. // load the list of screen types
  298. using (IsolatedStorageFileStream stream =
  299. storage.OpenFile("ScreenManager\\ScreenList.dat", FileMode.Open, FileAccess.Read))
  300. {
  301. using (BinaryReader reader = new BinaryReader(stream))
  302. {
  303. while (reader.BaseStream.Position < reader.BaseStream.Length)
  304. {
  305. // read a line from our file
  306. string line = reader.ReadString();
  307. // if it isn't blank, we can create a screen from it
  308. if (!string.IsNullOrEmpty(line))
  309. {
  310. Type screenType = Type.GetType(line);
  311. GameScreen screen = Activator.CreateInstance(screenType) as GameScreen;
  312. AddScreen(screen, PlayerIndex.One);
  313. }
  314. }
  315. }
  316. }
  317. }
  318. // next we give each screen a chance to deserialize from the disk
  319. for (int i = 0; i < screens.Count; i++)
  320. {
  321. string filename = string.Format("ScreenManager\\Screen{0}.dat", i);
  322. using (IsolatedStorageFileStream stream =
  323. storage.OpenFile(filename, FileMode.Open, FileAccess.Read))
  324. {
  325. screens[i].Deserialize(stream);
  326. }
  327. }
  328. return true;
  329. }
  330. catch (Exception)
  331. {
  332. // if an exception was thrown while reading, odds are we cannot recover
  333. // from the saved state, so we will delete it so the game can correctly
  334. // launch.
  335. DeleteState(storage);
  336. }
  337. }
  338. }
  339. return false;
  340. }
  341. /// <summary>
  342. /// Deletes the saved state files from isolated storage.
  343. /// </summary>
  344. private void DeleteState(IsolatedStorageFile storage)
  345. {
  346. // get all of the files in the directory and delete them
  347. string[] files = storage.GetFileNames("ScreenManager\\*");
  348. foreach (string file in files)
  349. {
  350. storage.DeleteFile(Path.Combine("ScreenManager", file));
  351. }
  352. }
  353. }
  354. }