Title: Game State and State Machines
1Game State and State Machines
- Splash Screens
- Menus
- Sequencing
- Branching
- Text Entry
2State Machines
- State Machines represent the flow of control in
your game
Config
Splash
Menu
Game
3Big State vs. Little State
- Big State
- Screens
- Splash, main screens, levels, etc.
- Little State
- State within a screen/level
TT
4Big State vs. Little State
- Big State
- Screens
- Splash, main screens, levels, etc.
- Little State
- State within a screen/level
- Sequencing of an animation, acquisition of
resources, etc.
5Splash Screens, Levels, etc.
- All you really need
- Object-oriented Programming!
- Superclass
- Create a superclass for all game screens
6GameScreen class
public class GameScreen // Game that
uses this screen private MyGame game
public MyGame Game get return game
public GameScreen(MyGame game)
this.game game
// // These are the functions game
screens need to support. //
public virtual void Initialize()
public virtual void LoadContent()
public virtual void Activate() public
virtual void Deactivate() public
virtual void Update(GameTime gameTime)
public virtual void Draw(GameTime gameTime)
public virtual void DrawSprites(GameTime
gameTime, SpriteBatch spriteBatch)
Initialization and keeping track of the game that
uses this screen.
7Activate and Deactivate functions
public class GameScreen // Game that
uses this screen private MyGame game
public MyGame Game get return game
public GameScreen(MyGame game)
this.game game
// // These are the functions game
screens need to support. //
public virtual void Initialize()
public virtual void LoadContent()
public virtual void Activate() public
virtual void Deactivate() public
virtual void Update(GameTime gameTime)
public virtual void Draw(GameTime gameTime)
public virtual void DrawSprites(GameTime
gameTime, SpriteBatch spriteBatch)
Most of the functions are just mapping standard
functions every screen needs to know how to do.
Activate and Deactivate are new. Well call
Activate when a screen becomes active. Well
call Deactivate when a screen is taken down.
8DrawSprites function
public class GameScreen // Game that
uses this screen private MyGame game
public MyGame Game get return game
public GameScreen(MyGame game)
this.game game
// // These are the functions game
screens need to support. //
public virtual void Initialize()
public virtual void LoadContent()
public virtual void Activate() public
virtual void Deactivate() public
virtual void Update(GameTime gameTime)
public virtual void Draw(GameTime gameTime)
public virtual void DrawSprites(GameTime
gameTime, SpriteBatch spriteBatch)
To draw 2D, we begin the sprite batch, then draw.
This function allows us to put that
functionality in one place
9Adding a Splash to Step 4
- Created GameScreen class
- Created XwingGameScreen class derived from
GameScreen - Moved most of the functionality to
XwingGameScreen - Created a way to keep track of the current screen
- Called current screen functions from main game
class - Added SplashGameScreen class derived from
GameScreen - Created means to switch in the main game class
10The XwingGameScreen class
public class XwingGameScreen GameScreen
public XwingGameScreen(XwingG
ame game) base(game)
xwing new Xwing(Game) asteroids
new AsteroidField(Game)
public override void LoadContent()
public override void
Activate()
base.Activate() lastKeyboardState
Keyboard.GetState() lastGamePadState
GamePad.GetState(PlayerIndex.One)
public override void Update(GameTime
gameTime)
base.Update(gameTime) public
override void Draw(GameTime gameTime)
xwing.Draw(Game.Graphics, gameTime)
asteroids.Draw(Game.Graphics,
gameTime) public override
void DrawSprites(GameTime gameTime,
SpriteBatch spriteBatch)
spriteBatch.DrawString(scoreFont, scoreString,
new Vector2(10, 10), Color.White)
11Keeping track of current screen
// Our game screens
XwingGameScreen xwingScreen null
SplashGameScreen splashScreen null //
The game screen we are playing GameScreen
screen null
public XwingGame()
xwingScreen new
XwingGameScreen(this) splashScreen
new SplashGameScreen(this) screen
splashScreen
12Calling GameScreen functions from main game class
protected override void Initialize()
base.Initialize()
camera.Initialize() camera.Eye
new Vector3(2000, 2000, 2000)
camera.ZNear 100 camera.ZFar
1000000 xwingScreen.Initialize()
splashScreen.Initialize()
protected override void LoadContent()
xwingScreen.LoadCont
ent() splashScreen.LoadContent()
screen.Activate()
protected override void Update(GameTime
gameTime)
screen.Update(gameTime)
camera.Update(gameTime)
base.Update(gameTime)
protected override void Draw(GameTime gameTime)
graphics.GraphicsDevice.Clear(
Color.Black) screen.Draw(gameTime)
spriteBatch.Begin(SpriteBlendMode.Alp
haBlend,
SpriteSortMode.BackToFront,
SaveStateMode.SaveStat
e) screen.DrawSprites(gameTime,
spriteBatch) spriteBatch.End()
base.Draw(gameTime)
13SplashGameScreen
public class SplashGameScreen GameScreen
private Texture2D splash
private double time public
SplashGameScreen(XwingGame game) base(game)
public override void
LoadContent() splash
Game.Content.LoadltTexture2Dgt("xwingsplash")
public override void Activate()
base.Activate()
time 0
14SplashGameScreen
public override void Update(Microsoft.Xna.
Framework.GameTime gameTime)
base.Update(gameTime) time
gameTime.ElapsedGameTime.TotalSeconds
if (time gt 3)
Game.SetScreen(XwingGame.GameScreens.Xwing)
public override void
DrawSprites(GameTime gameTime, SpriteBatch
spriteBatch) int wid
Game.Graphics.GraphicsDevice.Viewport.Width
int hit Game.Graphics.GraphicsDevice.View
port.Height int imgWid
(int)((16.0 / 9.0) hit) int
tooWide imgWid - wid Rectangle
rect new Rectangle(-tooWide / 2, 0, imgWid,
hit) spriteBatch.Draw(splash, rect,
Color.White)
15Means to switch screens in main game class
public enum GameScreens Splash, Xwing
public void SetScreen(GameScreens
newScreen)
screen.Deactivate() switch
(newScreen) case
GameScreens.Splash screen
splashScreen break
case GameScreens.Xwing
screen xwingScreen
break
screen.Activate()
Game.SetScreen(XwingGame.GameScreens.Xwing)
This is how it is called
16Important Properties
Youll need to be able to access some things that
would have been just member variables before.
The camera class is a good example. Create
public properties for this purpose.
public Camera Camera get return
camera public SoundBank SoundBank
get return soundBank public
GraphicsDeviceManager Graphics get return
graphics
In my main game class (XwingGame)
17Class Designer
18A few words about splash screen images
- Create them with a 169 aspect ratio (1067 x 600
for example) - Ensure they work okay at a 43 aspect ratio (pad
on left and right)
19And resize to a power of two in each dimension
Well stretch it back out when we draw it.
- 1024x512 resized
- Graphics systems require images to be powers of 2
in each dimension!
20Drawing our splash image
This code works for 169 or 43 aspect ratios.
Will work for any aspect ratio by making the
image fill top to bottom.
public override void DrawSprites(GameTime
gameTime, SpriteBatch spriteBatch)
int wid Game.Graphics.GraphicsDevice.View
port.Width int hit
Game.Graphics.GraphicsDevice.Viewport.Height
int imgWid (int)((16.0 / 9.0) hit)
int tooWide imgWid - wid
Rectangle rect new Rectangle(-tooWide / 2, 0,
imgWid, hit) spriteBatch.Draw(splash,
rect, Color.White)
Notice No reference to how big the image is!
21How do we do this?
- This is called a state machine
- or Finite State Machines (FSM)
- Our state is our current screen.
- Transitions between states are implemented by
calls to change the game screen.
Config
Splash
Menu
Game
22Little State state within a page
Ladder is on ground, rising, at window, then
dropping.
23State Diagram
Visio does these very nicely
What do we need to keep track of?
TT
24State Diagram
private enum States OnGround, Lifting,
OnSill, Dropping private States state
States.OnGround private float
stateTime 0
25 public override void Update(GameTime
gameTime) float t 0
stateTime (float)gameTime.ElapsedGameTim
e.TotalSeconds switch (state)
case States.OnGround
t 0 if
(stateTime gt 2.0f)
state States.Lifting
stateTime 0
break
case States.Lifting t
stateTime / 4.0f // Time to lift
if (t gt 1)
state States.OnSill
stateTime 0
t 1
break
case States.OnSill
t 1 if (stateTime gt
2.0f)
state States.Dropping
stateTime 0
break case
States.Dropping t 1 -
stateTime / 4.0f if (t lt
0)
state States.OnGround
stateTime 0 t 0
break
ladderDir
Slerp(originalDir, finalDir, t)
ladderUp Slerp(originalUp, finalUp, t)
UpdateOrientation()
26The basics
- State is indicated by
- enum value, time within state
case States.Lifting
t stateTime / 4.0f // Time to
lift if (t gt 1)
state
States.OnSill stateTime
0 t 1
break
- Transitions are implemented by
- Time expiration
27Other possibilities
- State might include
- Remaining ammunition, power
- Transitions can be due to
- End of an animation
- Key or button press
- Collisions
-
28A somewhat more complicated example
TT
29The States
// // State Management
// private enum State Start, BoxClosed,
BoxOpening, ReadyToLoad,
Loading1, Loading2, Loading3, Loaded,
Firing, Empty, BoxClosing
private State state private double
stateTime // Amount of remaining
ammunition private int ammoRemaining 2
30Time-safe state loops
double deltaTotal gameTime.ElapsedGam
eTime.TotalSeconds while
(deltaTotal gt 0) //
Elapsed time we'll use double
delta deltaTotal switch
(state)
case State.Start
boxClip.Rewind()
boxClip.Speed 0 state
State.BoxClosed delta
0 break
case State.BoxOpening
if (boxClip.Time lt BoxOpenTime)
if
(delta gt BoxOpenTime - boxClip.Time)
delta BoxOpenTime -
boxClip.Time
else
// Open is complete,
switch to ReadyToLoad
state State.ReadyToLoad
boxClip.Speed 0
delta 0
break
UpdateAnimations(delta)
deltaTotal - delta
A state may not consume all of the time available
to it. If not, we loop back to the next state
with the remaining time.
31Example
Three 1 second loadings should take 3 seconds.
But, what if each frame was 0.3 seconds? Suppose
I just keep track until time has expired?
32Frame times
Three 1 second loading should take 3 seconds.
But, what if each frame was 0.3 seconds?
Instead, it took 3.6 seconds. Why is that? Does
this matter?
33Example
Admittedly a contrived example, but suppose each
frame is 0.016 sec, we would accumulate an
average of 0.008 sec per state. Would that ever
matter?
34 public override void Update(GameTime
gameTime) // Total amount
of elapsed time. double delta
gameTime.ElapsedGameTime.TotalSeconds
switch (state)
case State.Loading1
position2 rocket.Position new Vector3(0, 60,
0) InterpolateRocket(ref
delta, LoadTime1, State.Loading2)
break case
State.Loading2 position2
Bazooka.Position Bazooka.Transform.Forward
90 InterpolateRocket(ref
delta, LoadTime2, State.Loading3)
break case
State.Loading3 position2
Bazooka.Position
InterpolateRocket(ref delta, LoadTime3,
State.Loaded) break
//...
UpdateAnimations(delta)
This code is broken, dont use as is!
private void InterpolateRocket(ref double
delta,
double loadTime, State
nextState) if (stateTime lt
loadTime) stateTime
delta float t
(float)(stateTime / loadTime)
rocket.Position Vector3.Lerp(position1,
position2, t) else
// Loading phase is
complete state nextState
stateTime 0
position1 rocketLoading.Position
TT
35 public override void Update(GameTime
gameTime) // Total amount
of elapsed time. double deltaTotal
gameTime.ElapsedGameTime.TotalSeconds
while (deltaTotal gt 0)
// Elapsed time we'll use
double delta deltaTotal
switch (state)
case State.Loading1
position2 rocket.Position new Vector3(0, 60,
0) InterpolateRocket(ref
delta, LoadTime1, State.Loading2)
break case
State.Loading2 position2
Bazooka.Position Bazooka.Transform.Forward
90 InterpolateRocket(ref
delta, LoadTime2, State.Loading3)
break case
State.Loading3 position2
Bazooka.Position
InterpolateRocket(ref delta, LoadTime3,
State.Loaded) break
UpdateAnimations(de
lta) deltaTotal - delta
This code is much better.
private void InterpolateRocket(ref double delta,
double loadTime, State nextState)
if (stateTime lt loadTime)
// Still loading
if (delta gt (loadTime - stateTime))
delta loadTime - stateTime
stateTime delta float t
(float)(stateTime / loadTime)
rocketLoading.Position Vector3.Lerp(position1,
position2, t) else
// Loading phase is
complete state nextState
stateTime 0
position1 rocketLoading.Position
orientation1 rocketLoading.Orientation
TT
36Keypress state transitions
switch (state)
case State.BoxClosed
if (e.KeyCode Keys.O)
state State.BoxOpening
boxClip.Speed 1
break
case State.ReadyToLoad
if (e.KeyCode Keys.L)
state State.Loading1
rocketLoading ammoRemaining
2 ? Rocket1 Rocket2
position1 rocketLoading.Position
orientation1 rocketLoading.Orientation
position2
rocketLoading.Position new Vector3(0, 60, 0)
orientation2
rocketLoading.Orientation
stateTime 0
break
37Animation-based State Transitions
case State.BoxOpening
if (boxClip.Time lt BoxOpenTime)
// See how much we can consume
if (delta gt BoxOpenTime - boxClip.Time)
delta
BoxOpenTime - boxClip.Time
else
// Open is
complete, switch to ReadyToLoad
state State.ReadyToLoad
boxClip.Speed 0
delta 0
break
case State.BoxClosing
if (boxClip.Time lt
boxClip.Duration)
// See how much we can
consume if (delta gt
boxClip.Duration - boxClip.Time)
delta boxClip.Duration -
boxClip.Time
else
// Open is complete,
switch to ReadyToLoad
state State.Start
boxClip.Speed 0
delta 0
break
38The rocket interpolation
private void InterpolateRocket(ref double
delta, double loadTime, State nextState)
if (stateTime lt loadTime)
// Still loading
if (delta gt (loadTime - stateTime))
delta loadTime - stateTime
stateTime delta float t
(float)(stateTime / loadTime)
rocketLoading.Position Vector3.Lerp(position1,
position2, t) rocketLoading.Orien
tation Quaternion.Slerp(orientation1,
orientation2, t) else
// Loading phase is
complete state nextState
stateTime 0
position1 rocketLoading.Position
orientation1 rocketLoading.Orientation
case State.Loading2
// Ending position and orientation
position2
Bazooka.Position Bazooka.Transform.Forward
90 orientation2
Bazooka.Orientation
InterpolateRocket(ref delta, LoadTime2,
State.Loading3) break
case State.Loading3
position2 Bazooka.Position
orientation2
Bazooka.Orientation
InterpolateRocket(ref delta, LoadTime3,
State.Loaded) break
39Conditional State Transition
case State.Firing
rocketFiring rocketLoading
ammoRemaining--
if (ammoRemaining gt 0)
state
State.ReadyToLoad
else
state
State.Empty
break
40Another type of state
How would you enter text?
41Another type of state
Text is complicated in XNA due to the keyboard
model they use. You have to determine a key has
been pressed since the last time update. This
may actually miss some keys. Some have hooked
the Windows keyboard event.
42XNA Keyboard Input Example
if (state States.NameEntry)
Keys keys
keyboardState.GetPressedKeys()
for (int i 0 i lt keys.Length i)
if (keysi lt Keys.A
keysi gt Keys.Z)
continue if
(lastKeyboardState.IsKeyUp(keysi))
// We have a
keypress char key
if (keyboardState.IsKeyDown(Keys.
LeftShift) keyboardState.IsKeyDown(Keys.RightSh
ift))
key Convert.ToChar(keysi)
else
key (char)(Convert.ToChar(keysi) - 'A'
'a')
name key
43(No Transcript)
44Displaying the name entry screen.
public override void DrawSprites(GraphicsD
evice graphics, SpriteBatch spriteBatch, GameTime
gameTime) int wid
graphics.Viewport.Width int hit
graphics.Viewport.Height
int imgWid (int)((16.0 / 9.0) hit)
int tooWide imgWid - wid
Rectangle rect new Rectangle(-tooWide / 2, 0,
imgWid, hit) spriteBatch.Draw(window,
rect, Color.White) int x
(int)(422.0 / 1066.0 imgWid) int y
(int)(497.0 / 600.0 hit)
spriteBatch.DrawString(font, name, new Vector2(x
- tooWide / 2, y), Color.Red)
Just like drawing a splash screen
Displays the name