State Pattern - More Example

Check the example Jumper below:)

Jumper

  • 2 Player vertical scrolling platform

  • Screen scrolls up as the players climb the platforms

  • The bottom of the screen is game over


  • Goal: Climb faster than the other player


We’ve seen how physics was added to the game

  • Platforms / Wall extend StaticObject
  • Players extend DynamicObject
  • FUlly compatible with the Physics Engine HW

Jumper - Physics

Walls and Platforms extend StaticObject

  • Add behavior after collision with player
1
2
3
4
class JumperObject(location: PhysicsVector, dimensions: PhysicsVector) extends StaticObject(location, dimensions){
val objectID: Int = JumperObject.nextID
JumperObject.nextID += 1
}
1
2
3
4
5
6
7
8
9
10
11
class Platform(location: PhysicsVector, dimensions: PhysicsVector) extends JumperObject (location, dimensions) {

override def collideWithDynamicObject(otherObject: DynamicObject, face: Integer): Unit = {

if (face == Face.top) {
otherObject.velocity.z = 0.0
otherObject.location.z = this.location.z + this.dimensions.z
otherObject.onGround()
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
class Wall(location: PhysicsVector, dimensions: PhysicsVector) extends JumperObject(location, dimensions) {

override def collideWithDynamicObject(otherObject: DynamicObject, face: Integer): Unit = {
if(face == Face.negativeX){
otherObject.velocity.x = 0.0
otherObject.location.x = this.location.x - otherObject.dimensions.x
} else if (face == Face.positiveX){
otherObject.velocity.x = 0.0
otherObject.location.x = this.location.x + this.dimensions.x
}
}
}

Players extend DynamicObject

  • Physics engine applies since all objects in our game are StaticObjects or DynamicObject
  • The Player class will set its own velocity based on user inputs
    • Velocities are updated by gravity and collisions
    • User inputs are effectively the “inended” velocity
  • How does the Player set its velocity?
1
2
3
4
class Player(playerLocation: PhysicsVector, playerDimensions: PhysicsVector) extends DynamicObject(playerLocation, playerDimensions) {

// ...
}

Jumper - Player

How does the Player set its velocity?

  • User inputs
  • States! <– Good stuff

Only 3 inputs to control each player

  • Left button
  • Right button
  • Jump button

Player 1:

  • a,d,w //wasd 方向键
    Player 2:
  • Left, right, up arrows

Jumper Player Behavior

Each player should:

  • Walk left and right when keys are pressed
  • Jump when jump is pressed
  • Jump higher if walking instead of standing still
  • Jump at different heights based on how long the jump button is held after a jump
  • Move left and right slower while in the air if the direction is changed
  • Jump through platforms while jumping up
  • Land on platforms while falling down
  • Fall if walked off a ledge
  • Block all inputs if the bottom of the screen is reached

We could write all this behavior without the state patten

  • Code will likely be hard to follow
  • Diffcult to add new features

How to implement these features?

  • Write your API
    • What methods will change behavior depending on the current state of the object
    • These methos define your API ans are declared in the state abstract class
  • Decide what states should exist
    • Any situation where the behavior is different should be a new state
  • Determine the transitions between states

Each player should

  • Walk left and right when keys are pressed
  • Jump when jump is pressed
  • Jump higher if walking instead of standing still
  • Jump at different heights based on how long the jump button is held after a jump
  • Move left and right slower while in the air if the direction is changed
  • Jump throught platforms while jumping up
  • Land on platforms while fallin down
  • Fall if walked off a ledge
  • Block all inputs if the bottom of the screen is reached

API:

  • left / right / jump pressed or realeased or released
    • 6 methods
  • Land on a platform

How to implement these features?

  • Decide what stats should exits

States:

  • Standing
  • Walking
  • Jumping / Rising
  • Falling
  • Dead (Bellow Screen)

State Transitions:

  • Standing -> Walking
    • left / right pressed
  • Walking -> Standing
    • left / right pressed
  • Walking / Standing -> Jumping
    • Jump pressed
  • Falling -> Standing
    • Land on a platform
  • Walking -> Falling
    • Walk off a platform
  • Jumping -> Falling
    • Apex of jump reached
  • Any -> GameOver
    • Reach the bottom of the screen

Let’s visualize the states and transitions in a state diagram

Jumper Player Behavior

For each state implement the API methods with the desired havior in that state

  • Add default behavior in the state subclass

Use inheritance to limit duplicate code

  • Factor out common behavior between states into new class

Jumper Player Behavior

Adding Functionality

Task: Add a double jumper to Jumper

  • How can we add a double jump?
    • Players can jump 1 additional time while in the air

  • With poor design
    • This could be extremely difficult!
    • May required modifying a significant amount of existing code

  • With our state patetrn
    • No Problem at all

  • Add functionality to existing states
    • Rising and Falling states now react to the jump button by jumping again (Set velocity.z to the jump velocity)
  • We’ll add new states
    • RisingAfterDoubleJump / FallingAfterDoubleJump
    • Extend Rising / Falling resprectively
    • Override the jump button press to do nothing
  • Update state transitions
    • Pree jump from Rising / Falling transitions to the resprective AfterDoubleJump state
    • Reaching the apex in RisingAfterDoubleJump transitions to FallingAfterDoubleJump (Not Falling)

  • This task could have been completed with a boolean flag instead of using new states
  • If this approach is used for many features the code will be harder to maintian
  • More to the point: What if your professor says you can’t use control flow, but you have a situation where a button should only work once?
    • Try adding more states
1
2
3
4
5
6
7
8
var usedDoubleJump = false

override def jumpPressed(): Unit = {
if(!this.usedDoubleJump) {
player.velocity.z = player.standingJumpVelocity
this.usedDoubleJump = true
}
}

Jumper Player Behavior

Jumper Player Behavior

Lecture Question

Question:

  • Simulate a TV without using control flow (ie. Use the state pattern)
  • In a package named oop.tv, create a Class named TV with no constructor parameters
  • The TV class must contain the following methods as its API:
    • volumeUp(): Unit
    • volumeDown(): Unit
    • mute(): Unit power(): Unit
    • currentVolume(): Int
  • In the tests package, write a test suite named TestTV that will test all the functionality on the spec sheet
    • Note: Only call the API methods while testing. Other methods/ variables you create will not exist in the grader submissions

TV Spec Sheet

  • TV is initially off when created
  • Initial volume is 5
  • When the TV is off:
    • Volume up/down and mute buttons do nothing
    • Current volume is 0
  • The power button turns the TV on/off
  • Volume up button increases volume by 1 up to a maximum volume of 10
  • Volume down button decreases volume by 1 down to minimum volume of 0
  • Pressing the mute button mutes the TV
  • When the TV is muted:
    • Current volume is 0
    • Pressing the mute, volume up, or volume down buttons will unmute the TV and restore the volume to the pre- mute volume (Do not in/decrease the volume)
  • When turning the TV back on, the volume should return to its value when the TV was last on
    • When the TV is first turned on the volume will be 5
  • If the TV was turned off while muted, when it is turned back on it should not be muted