LoadingScreen.cs 9.5 KB

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