LoadingScreen.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // LoadingScreen.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 System;
  11. using System.Threading;
  12. using System.Diagnostics;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Graphics;
  15. using Microsoft.Xna.Framework.Net;
  16. using GameStateManagement;
  17. #endregion
  18. namespace CatapultGame
  19. {
  20. /// <summary>
  21. /// The loading screen coordinates transitions between the menu system and the
  22. /// game itself. Normally one screen will transition off at the same time as
  23. /// the next screen is transitioning on, but for larger transitions that can
  24. /// take a longer time to load their data, we want the menu system to be entirely
  25. /// gone before we start loading the game. This is done as follows:
  26. ///
  27. /// - Tell all the existing screens to transition off.
  28. /// - Activate a loading screen, which will transition on at the same time.
  29. /// - The loading screen watches the state of the previous screens.
  30. /// - When it sees they have finished transitioning off, it activates the real
  31. /// next screen, which may take a long time to load its data. The loading
  32. /// screen will be the only thing displayed while this load is taking place.
  33. /// </summary>
  34. class LoadingScreen : GameScreen
  35. {
  36. #region Fields
  37. bool loadingIsSlow;
  38. bool otherScreensAreGone;
  39. GameScreen[] screensToLoad;
  40. Thread backgroundThread;
  41. EventWaitHandle backgroundThreadExit;
  42. GraphicsDevice graphicsDevice;
  43. NetworkSession networkSession;
  44. IMessageDisplay messageDisplay;
  45. GameTime loadStartTime;
  46. TimeSpan loadAnimationTimer;
  47. #endregion
  48. #region Initialization
  49. /// <summary>
  50. /// The constructor is private: loading screens should
  51. /// be activated via the static Load method instead.
  52. /// </summary>
  53. private LoadingScreen (ScreenManager screenManager,bool loadingIsSlow,
  54. GameScreen[] screensToLoad)
  55. {
  56. this.loadingIsSlow = loadingIsSlow;
  57. this.screensToLoad = screensToLoad;
  58. TransitionOnTime = TimeSpan.FromSeconds (0.5);
  59. // If this is going to be a slow load operation, create a background
  60. // thread that will update the network session and draw the load screen
  61. // animation while the load is taking place.
  62. if (loadingIsSlow) {
  63. backgroundThread = new Thread (BackgroundWorkerThread);
  64. backgroundThreadExit = new ManualResetEvent (false);
  65. graphicsDevice = screenManager.GraphicsDevice;
  66. // Look up some services that will be used by the background thread.
  67. IServiceProvider services = screenManager.Game.Services;
  68. networkSession = (NetworkSession)services.GetService (
  69. typeof(NetworkSession));
  70. messageDisplay = (IMessageDisplay)services.GetService (
  71. typeof(IMessageDisplay));
  72. }
  73. }
  74. /// <summary>
  75. /// Activates the loading screen.
  76. /// </summary>
  77. public static void Load (ScreenManager screenManager, bool loadingIsSlow,
  78. PlayerIndex? controllingPlayer,
  79. params GameScreen[] screensToLoad)
  80. {
  81. // Tell all the current screens to transition off.
  82. foreach (GameScreen screen in screenManager.GetScreens ())
  83. screen.ExitScreen ();
  84. // Create and activate the loading screen.
  85. LoadingScreen loadingScreen = new LoadingScreen (screenManager,
  86. loadingIsSlow,
  87. screensToLoad);
  88. screenManager.AddScreen (loadingScreen, controllingPlayer);
  89. }
  90. #endregion
  91. #region Update and Draw
  92. /// <summary>
  93. /// Updates the loading screen.
  94. /// </summary>
  95. public override void Update (GameTime gameTime, bool otherScreenHasFocus,
  96. bool coveredByOtherScreen)
  97. {
  98. base.Update (gameTime, otherScreenHasFocus, coveredByOtherScreen);
  99. // If all the previous screens have finished transitioning
  100. // off, it is time to actually perform the load.
  101. if (otherScreensAreGone) {
  102. // Start up the background thread, which will update the network
  103. // session and draw the animation while we are loading.
  104. if (backgroundThread != null) {
  105. loadStartTime = gameTime;
  106. backgroundThread.Start ();
  107. }
  108. // Perform the load operation.
  109. ScreenManager.RemoveScreen (this);
  110. foreach (GameScreen screen in screensToLoad) {
  111. if (screen != null) {
  112. ScreenManager.AddScreen (screen, ControllingPlayer);
  113. }
  114. }
  115. // Signal the background thread to exit, then wait for it to do so.
  116. if (backgroundThread != null) {
  117. backgroundThreadExit.Set ();
  118. backgroundThread.Join ();
  119. }
  120. // Once the load has finished, we use ResetElapsedTime to tell
  121. // the game timing mechanism that we have just finished a very
  122. // long frame, and that it should not try to catch up.
  123. ScreenManager.Game.ResetElapsedTime ();
  124. }
  125. }
  126. /// <summary>
  127. /// Draws the loading screen.
  128. /// </summary>
  129. public override void Draw (GameTime gameTime)
  130. {
  131. // If we are the only active screen, that means all the previous screens
  132. // must have finished transitioning off. We check for this in the Draw
  133. // method, rather than in Update, because it isn't enough just for the
  134. // screens to be gone: in order for the transition to look good we must
  135. // have actually drawn a frame without them before we perform the load.
  136. if ((ScreenState == ScreenState.Active) &&
  137. (ScreenManager.GetScreens ().Length == 1)) {
  138. otherScreensAreGone = true;
  139. }
  140. // The gameplay screen takes a while to load, so we display a loading
  141. // message while that is going on, but the menus load very quickly, and
  142. // it would look silly if we flashed this up for just a fraction of a
  143. // second while returning from the game to the menus. This parameter
  144. // tells us how long the loading is going to take, so we know whether
  145. // to bother drawing the message.
  146. if (loadingIsSlow) {
  147. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  148. SpriteFont font = ScreenManager.Font;
  149. string message = Resources.Loading;
  150. // Center the text in the viewport.
  151. Viewport viewport = ScreenManager.GraphicsDevice.Viewport;
  152. Vector2 viewportSize = new Vector2 (viewport.Width, viewport.Height);
  153. Vector2 textSize = font.MeasureString (message);
  154. Vector2 textPosition = (viewportSize - textSize) / 2;
  155. Color color = Color.White * TransitionAlpha;
  156. // Animate the number of dots after our "Loading..." message.
  157. loadAnimationTimer += gameTime.ElapsedGameTime;
  158. int dotCount = (int)(loadAnimationTimer.TotalSeconds * 5) % 10;
  159. message += new string ('.', dotCount);
  160. // Draw the text.
  161. spriteBatch.Begin ();
  162. spriteBatch.DrawString (font, message, textPosition, color);
  163. spriteBatch.End ();
  164. }
  165. }
  166. #endregion
  167. #region Background Thread
  168. /// <summary>
  169. /// Worker thread draws the loading animation and updates the network
  170. /// session while the load is taking place.
  171. /// </summary>
  172. void BackgroundWorkerThread ()
  173. {
  174. long lastTime = Stopwatch.GetTimestamp ();
  175. // EventWaitHandle.WaitOne will return true if the exit signal has
  176. // been triggered, or false if the timeout has expired. We use the
  177. // timeout to update at regular intervals, then break out of the
  178. // loop when we are signalled to exit.
  179. while (!backgroundThreadExit.WaitOne (1000 / 30)) {
  180. //GameTime gameTime = GetGameTime (ref lastTime);
  181. //DrawLoadAnimation (gameTime);
  182. UpdateNetworkSession ();
  183. }
  184. }
  185. // /// <summary>
  186. // /// Works out how long it has been since the last background thread update.
  187. // /// </summary>
  188. // GameTime GetGameTime (ref long lastTime)
  189. // {
  190. // long currentTime = Stopwatch.GetTimestamp ();
  191. // long elapsedTicks = currentTime - lastTime;
  192. // lastTime = currentTime;
  193. //
  194. // TimeSpan elapsedTime = TimeSpan.FromTicks (elapsedTicks *
  195. // TimeSpan.TicksPerSecond /
  196. // Stopwatch.Frequency);
  197. //
  198. // return new GameTime (loadStartTime.TotalGameTime + elapsedTime, elapsedTime);
  199. // }
  200. /// <summary>
  201. /// Calls directly into our Draw method from the background worker thread,
  202. /// so as to update the load animation in parallel with the actual loading.
  203. /// </summary>
  204. void DrawLoadAnimation (GameTime gameTime)
  205. {
  206. if ((graphicsDevice == null) || graphicsDevice.IsDisposed)
  207. return;
  208. try {
  209. graphicsDevice.Clear (Color.Black);
  210. // Draw the loading screen.
  211. Draw (gameTime);
  212. // If we have a message display component, we want to display
  213. // that over the top of the loading screen, too.
  214. if (messageDisplay != null) {
  215. messageDisplay.Update (gameTime);
  216. messageDisplay.Draw (gameTime);
  217. }
  218. graphicsDevice.Present ();
  219. } catch {
  220. // If anything went wrong (for instance the graphics device was lost
  221. // or reset) we don't have any good way to recover while running on a
  222. // background thread. Setting the device to null will stop us from
  223. // rendering, so the main game can deal with the problem later on.
  224. graphicsDevice = null;
  225. }
  226. }
  227. /// <summary>
  228. /// Updates the network session from the background worker thread, to avoid
  229. /// disconnecting due to network timeouts even if loading takes a long time.
  230. /// </summary>
  231. void UpdateNetworkSession ()
  232. {
  233. if ((networkSession == null) ||
  234. (networkSession.SessionState == NetworkSessionState.Ended))
  235. return;
  236. try {
  237. networkSession.Update ();
  238. } catch {
  239. // If anything went wrong, we don't have a good way to report that
  240. // error while running on a background thread. Setting the session to
  241. // null will stop us from updating it, so the main game can deal with
  242. // the problem later on.
  243. networkSession = null;
  244. }
  245. }
  246. #endregion
  247. }
  248. }