Transitioning States in GameStateManager

Jun 11, 2011 at 10:34 PM
Edited Jun 12, 2011 at 8:14 AM

We're trying to implement a transition feature inside of the GameStateManager and I'm curious if anyone can provide some advice on how to go about doing this.  As it is, this looks like it could be handled independently inside of the individual states themselves, mostly because the Pop and Push calls cannot be overridden.  However, since states seemingly shouldn't be initiating manipulations of the state stack themselves, it's a bit more complicated.  Thanks for any assistance.

Just as a heads-up, I am aware of how it's done inside of the AppHub sample, using render targets to draw the transitioning states.  Trying to work something similar into the Nuclex state library appears to be fairly involved.  Because the AppHub sample calls Draw for the individual states explicitly, render targets can be manipulated in between the draw calls for each screen on the stack.  However, because the Nuclex GameStateManager makes each of the Draw calls for each GameState on the stack in the base method, playing with what's being drawn for a particular GameState isn't as isolated.

Joseph G.

Coordinator
Jun 12, 2011 at 8:22 AM
Edited Jun 12, 2011 at 8:23 AM

How do you initiate transitions if the states themselves shouldn't manipulate the state stack?

The information about when a switch needs to occur (eg. at end of intro or upon starting a game from the main menu) lies within the state. You could try to decouple this by letting the state fire an event at the appropriate time and subscribe some state switcher, but that would only move the logic into some game state factory (where it clearly shouldn't be) or perhaps a kind of switch table requiring lots of reflection magic.

For transitions (I'm assuming you mean some kind of animated bridge between two states), I would simply add a TransitionState that is used to blend over or otherwise provide a transition, eg.

public class TransitionState : DrawableGameState {

  public TransitionState(
    IGameStateService gameStateService, IGameState to
  ) {
    this.gameStateService = gameStateService;
    this.from = this.gameStateService.ActiveState;
    this.to = to;
    this.duration = TimeSpan.FromSeconds(1);
  }

  public override void Update(GameTime gameTime) {
    this.elapsedTime += gameTime.ElapsedGameTime;
    if (this.elapsedTime >= this.duration) {
      this.gameStateService.Switch(this.to);
    }
  }

  public override void Draw(GameTime gameTime) {
    double t = Math.Min(
      1.0, elapsedTime.TotalMilliseconds / duration.TotalMilliseconds
    );
    // TODO: Draw transition at instant t,
    //       where 0.0 is the 'from' state and 1.0 is the 'to' state
  }

  private IGameStateService gameStateService;
  private IGameState from;
  private IGameState to;
  private TimeSpan elapsedTime;
  private TimeSpan duration;

}

For easy usage, maybe sprinkle with some extension methods:

public static class GameStateServiceHelper {

  public static void TransitionTo(this IGameStateService service, IGameState to) {
    service.Switch(new TransitionState(service, to));
  }

}
Jun 14, 2011 at 10:13 AM
Edited Jun 18, 2011 at 4:32 AM

I appreciate the advice. Below is what I've come up with at this point.  Rather then creating a separate state to handle the transition between two states, it seemed logical for a state to contain its own transition methods.

I am seeing a slight loss in framerate, and I haven't been able to pinpoint the biggest cause, but it's very slight; around four or five frames per second.  I'd like to know if this appears suitable as far as the logic of the framework is concerned.

 

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Nuclex.Game.States;

namespace FarseerPhysics.SamplesFramework
{
    public enum TransitionableGameStateStatus
    {
        Active,
        Entering,
        Exiting,
        Hidden
    }

    public abstract class TransitionableGameState : DrawableGameState
    {
        private readonly GraphicsDevice _graphicsDevice;

        private readonly SpriteBatch _spriteBatch;

        /// <summary>
        ///   The length of time the state takes to transition on/off
        /// </summary>
        protected TimeSpan TransitionDuration;

        protected RenderTarget2D TransitionTarget;

        private TimeSpan _elapsedTime;

        private TransitionableGameStateStatus _status;
        private float _transitionAlpha;

        /// <summary>
        ///   Constructs the game state.
        /// </summary>
        /// <param name = "graphicsDevice">Device to set the render target</param>
        /// <param name = "spriteBatch">Batch to draw the render target</param>
        /// <param name = "transitionDuration">Duration of the transition on or off</param>
        protected TransitionableGameState(GraphicsDevice graphicsDevice, SpriteBatch spriteBatch,
                                          TimeSpan transitionDuration)
        {
            _graphicsDevice = graphicsDevice;
            _spriteBatch = spriteBatch;
            TransitionDuration = transitionDuration;

            _status = TransitionableGameStateStatus.Entering;
        }

        protected bool IsTransitioning
        {
            get
            {
                return (_status == TransitionableGameStateStatus.Entering
                        || _status == TransitionableGameStateStatus.Exiting);
            }
        }

        public TransitionableGameStateStatus Status
        {
            get { return _status; }

            protected internal set
            {
                _status = value;
                switch (_status)
                {
                    case TransitionableGameStateStatus.Entering:
                    case TransitionableGameStateStatus.Exiting:
                        _elapsedTime = TimeSpan.Zero;
                        return;
                }
            }
        }

        /// <summary>
        ///   Updates the transition position if transitioning.
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            // Update transition timer
            _elapsedTime += gameTime.ElapsedGameTime;

            if (IsTransitioning)
            {
                UpdateTransition(gameTime);
            }
        }

        protected virtual void UpdateTransition(GameTime gameTime)
        {
            if (_elapsedTime >= TransitionDuration)
            {
                switch (_status)
                {
                    case TransitionableGameStateStatus.Exiting:
                        _status = TransitionableGameStateStatus.Hidden;
                        break;

                    default:
                        _status = TransitionableGameStateStatus.Active;
                        break;
                }
                return;
            }

            _transitionAlpha = (float)Math.Min(1, _elapsedTime.TotalMilliseconds
/ TransitionDuration.TotalMilliseconds); if (_status != TransitionableGameStateStatus.Exiting) { return; } _transitionAlpha = 1 - _transitionAlpha; } /// <summary> /// Draws the state or rendered target if transitioning /// </summary> /// <param name = "gameTime"></param> public override sealed void Draw(GameTime gameTime) { if (!IsTransitioning) { DrawContent(); return; } SetRenderTarget(); DrawContent(); DrawTransition(); } private void SetRenderTarget() { PresentationParameters pp = _graphicsDevice.PresentationParameters; TransitionTarget = new RenderTarget2D(_graphicsDevice, pp.BackBufferWidth,
pp.BackBufferHeight, false, SurfaceFormat.Color, pp.DepthStencilFormat,
pp.MultiSampleCount, RenderTargetUsage.DiscardContents); _graphicsDevice.SetRenderTarget(TransitionTarget); _graphicsDevice.Clear(Color.Transparent); } protected virtual void DrawTransition() { // Clear graphics device _graphicsDevice.SetRenderTarget(null); _graphicsDevice.Clear(Color.Black); // Draw render target _spriteBatch.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend); _spriteBatch.Draw(TransitionTarget, Vector2.Zero, Color.White * _transitionAlpha); _spriteBatch.End(); // Dispose of render target TransitionTarget.Dispose(); } /// <summary> /// Draws the derived state /// </summary> protected abstract void DrawContent(); } }