using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using RecrownedAthenaeum.Camera; using System; using System.Diagnostics; namespace RecrownedAthenaeum.ScreenSystem { /// /// Called when the first screen is being shown. /// /// The screen to show after the loading screen. public delegate void ShowFirstScreen(Screen screen); /// /// Custom spritebatch begin call. /// /// public delegate void BeginBatch(SpriteBatch spriteBatch); /// /// A manager for screens. Helps with transitions and updating screens as well as resizes. /// public class ScreenManager : IDisposable { bool disposed = false; /// /// Called when the first loading screen is done, and needs to show the landing screen. /// public event ShowFirstScreen ShowFirstScreenEvent; /// /// The function to call that begins the batch. /// public BeginBatch beginBatchFunc; private GraphicsDeviceManager graphics; private Screen previousScreen; private RenderTarget2D previousScreenRenderTarget; private Screen currentScreen; private Camera2D camera; private bool firstScreenChangeComplete; private bool resizing; /// /// Currently displayed screen. /// public Screen Screen { get { return currentScreen; } set { previousScreen = currentScreen; currentScreen = value; if (!Screen.Initiated) { Screen.Initiate(new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), camera); } if (previousScreen != null && previousScreenRenderTarget == null && previousScreen.UseRenderTargetForExitTransition) { previousScreenRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); } graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); graphics.GraphicsDevice.Clear(Color.Black); Debug.WriteLine("Showing " + value.GetType().Name); Screen.Show(); previousScreen?.Hide(); } } /// /// Creates a screen manager that helps manage multiple screens and their transitions. /// /// The camera to be used to perform the correct translations and transformations. Will use default set in if left null. /// The graphics device manager to be used. Will use default set in if left null. /// The function to call to begin a batch to be used generally. Will use the built-in one in screen manager if not provided. public ScreenManager(Camera2D camera = null, GraphicsDeviceManager graphicsDeviceManager = null, BeginBatch beginBatchFunction = null) { if (beginBatchFunction == null) beginBatchFunction = BasicBeginBatch; if (camera == null) camera = Configuration.Camera2D; if (graphicsDeviceManager == null) graphicsDeviceManager = Configuration.GraphicsDeviceManager; graphics = graphicsDeviceManager ?? throw new ArgumentNullException("Graphics device manager argument cannot be null if setup's graphics device manager is also null."); this.camera = camera ?? throw new ArgumentNullException("2d camera argument cannot be null if setup's 2d camera is also null."); beginBatchFunc = beginBatchFunction; } /// /// Updates the screens. Should be called once every frame. /// /// Contains the time that has passed from the last frame. /// Whether or not there is something a transition should be waiting for. Usually used to wait for assets to complete loading. public void UpdateCurrentScreen(GameTime gameTime, bool waiting) { switch (Screen.State) { case ScreenState.EnterTransition: if (previousScreen != null && previousScreen.UseRenderTargetForExitTransition) { previousScreen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); } Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); break; case ScreenState.ExitTransition: if (Screen.UseRenderTargetForExitTransition || Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting)) { if (!firstScreenChangeComplete) { firstScreenChangeComplete = true; OnFirstScreenChange(Screen); } if (Screen.NextScreen != null) { Debug.WriteLine("Changing to the next given screen."); Screen = Screen.NextScreen; } else if (previousScreen != null) { Debug.WriteLine("Changing to previous screen."); Screen = previousScreen; } else { throw new InvalidOperationException("No next screen provided and no previous screen to return to."); } } break; } Screen.Update(gameTime); } /// /// Renders screen into window. /// /// Uses this batch to render. public void DrawCurrentScreen(SpriteBatch spriteBatch) { graphics.GraphicsDevice.Clear(Screen.BackgroundColor); if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition) { graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor); beginBatchFunc(spriteBatch); previousScreen.Draw(spriteBatch); spriteBatch.End(); graphics.GraphicsDevice.SetRenderTarget(null); Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget); } beginBatchFunc(spriteBatch); Screen.Draw(spriteBatch); spriteBatch.End(); } /// /// Should be called when resize is occurring to change to a loading screen. /// This will notify the screen of the status of the assets, change the screen to a loading screen, and dispose of the previous screen buffer. /// /// The loading screen to change to. public void Resize(LoadingScreen loadingScreen) { if (resizing) throw new InvalidOperationException("Already resizing."); resizing = true; Screen.AssetLoadStateChange(false); Screen = loadingScreen; previousScreenRenderTarget.Dispose(); previousScreenRenderTarget = null; } /// /// Notifies all screen that assets have completed being loaded after a resize. /// public void PostResize() { if (!resizing) throw new InvalidOperationException("Was never resizing."); Screen.AssetLoadStateChange(true); } private void OnFirstScreenChange(Screen screen) { ShowFirstScreenEvent?.Invoke(screen); } /// /// Disposes this. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// An overridable dispose. /// /// True of user invoked dispose called. public virtual void Dispose(bool disposing) { if (disposed) return; if (disposing) { previousScreenRenderTarget?.Dispose(); } disposing = true; } /// /// Destructor. /// ~ScreenManager() { Dispose(false); } private void BasicBeginBatch(SpriteBatch spriteBatch) { spriteBatch.Begin(effect: camera.BasicEffect, blendState: BlendState.NonPremultiplied); } } }