The last thing we need to cover is some built in states that are commonly used. For this we use a static class called States to create and manage a few State structs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | namespace AdvancedStateSystem { public static class States { private static State none; private static State onHold; private static State transitioning; private static State removeReady; private static State active; private static State background; static States() { none = new State(); none.CanDraw = false; none.CanUpdate = false; none.Key = 0; none.Time = 0.0f; onHold = new State(); onHold.CanDraw = false; onHold.CanUpdate = false; onHold.Key = 1; onHold.Time = 0.0f; transitioning = new State(); transitioning.CanDraw = true; transitioning.CanUpdate = true; transitioning.Key = 2; transitioning.Time = 0.0f; active = new State(); active.CanDraw = true; active.CanUpdate = true; active.Key = 3; active.Time = 0.5f; background = new State(); background.CanDraw = true; background.CanUpdate = true; background.Key = 4; background.Time = 0.5f; removeReady = new State(); removeReady.CanDraw = false; removeReady.CanUpdate = true; removeReady.Key = 5; removeReady.Time = 0.5f; } public static State None { get { return none; } } public static State OnHold { get { return onHold; } } public static State Transitioning { get { return transitioning; } } public static State Active { get { return active; } } public static State Background { get { return background; } } public static State RemoveReady { get { return removeReady; } } } } |
Unfortunately we cannot stop there! We also need to declare a basic object to use all this functionality so that you know how to transition between states! For this we declare a new class called StateObject.
1 2 3 4 5 6 | using Microsoft.Xna.Framework; namespace AdvancedStateSystem { class StateObject : IStateObject { |
This object will need only a few private fields. The rest of the properties that the interfaces require can be anonymous.
1 2 3 4 | private bool isVisible = true; private bool isEnabled = true; private float transitionTime = 0.5f; private float currentTime = 0.0f; |
The big kahuna in this object is how we transition between states. In Thrust, I utilize a queue of states to allow for continuous transitioning. In the case of this article, I have limited it to one state to simplify things. Having said that, the Transition function simply changes states and sets up the timing mechanism.
1 2 3 4 5 6 7 8 | public virtual void Transition(State newState) { NextState = newState; CurrentState = States.Transitioning; transitionTime = newState.Time; currentTime = 0.0f; } |
The next three methods are simply placeholders for an inheriting class. The third method, Update, checks on the current transition and updates it accordingly.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public virtual void Initialize(Game game) { IsInitialized = true; } public virtual void Draw(GameTime gameTime) { } public virtual void Update(GameTime gameTime) { UpdateTransition(gameTime); } |
Here is the UpdateTransition function. Note that if we are not in a transitioning state, there is no need to update the transition! This is simply because there is none in that situation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | protected virtual void UpdateTransition(GameTime gameTime) { if (CurrentState != States.Transitioning) return; currentTime += (float)gameTime.ElapsedGameTime.TotalSeconds; if (currentTime >= transitionTime) { CurrentState = NextState; NextState = States.None; OnStateChanged(); } } protected virtual void OnStateChanged() { } |
And finally the properties of the class. Note that the IsVisible property takes into account whether or not the current state allows us to draw or not. Similarly the IsEnabled property takes into consideration whether or not the state allows the object to be updated. I have also added a TransitionPosition property that will help any inheriting classes know where in a transition the object is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public bool IsVisible { get { return isVisible && (CurrentState == null || CurrentState.CanDraw); } set { isVisible = value; } } protected float TransitionPosition { get { if (transitionTime == 0f) return 0f; return currentTime / transitionTime; } } public bool IsEnabled { get { return isEnabled && (CurrentState == null || CurrentState.CanUpdate); } set { isEnabled = value; } } public bool IsInitialized { get; private set; } public int DrawOrder { get; set; } public int UpdateOrder { get; set; } public State CurrentState { get; private set; } public State NextState { get; private set; } public StateManager StateManager { get; set; } } |
And there you have it! The base layer to our Advanced State System. In the next installment we will drill down into more advanced transitioning techniques. You can download the source code for part one below.
DMS Prototype (78) - 15.38 kB