States in a Game Wide
Game Framework for RubyCocoa > States in a Game

State at a glance - a context based Model manipulator

Typical scenario

As stated in the previous section, the current state class receives an event from the Controller. It, then, invokes a method in a Model to manipulate data inside the Model. When a certain condition becomes true, the state transit to another.

Roles

As a bridge between the Controller and a Model, the State class has five basic roles:

  • Registers event handlers
    • for the events that you want to receive
  • Generates timers
    • if needed
  • Manipulates its relevant Model object
    • when an event occurs
  • Transits to another state
    • when a certain condition becomes true
  • Cleans up event handlers and timers
    • before it transits to another state

Important Methods in State

There are two important methods in State - enter and leave. The enter method is invoked when entering a state, and the leave method is invoked at the time the current state is being changed. The enter method implements the first two roles of State, which are shown above, and the leave method implements the last one. Other roles of State should be implemented in event handlers or in the methods that are invoked from the event handlers.

Implementation

The subclass of State in the game template is implemented as shown below.

class NewGamePlayingState < State
  # define your timer IDs here
  NEWGAME_TIMER = 1000

  def enter()
    super()
    @model.init()

    # Define context dependent event handlers (event context)
    # Each context is defined in a form of [EventType, Content, Proc object]
    # See KeyEvent.rb, MouseEvent.rb and TimerEvent.rb for event specific info.
    # Other game's source code would also help you
    contexts = [[TimerEvent::Fired, NEWGAME_TIMER, proc {|id| tick() }]]
    contexts.each {|context| EventContext.addContext(*context) }
    @timer = TimerEvent.new(0.1, true, NEWGAME_TIMER)
  end
  
   # Timer event handler
   def tick()
    # do something when a timer event occurs
    return true
  end
end

Behaviors of State

This section illustrates the basic behaviors of State.

Entering a state

When the current state is changed from one state to another, State.enter is invoked. Each subclass of State needs to initialize the state at the method. Typical initialization process is:

  1. call super.enter()
    to register common event handlers
  2. add event handlers
    by using EventContext.addContexts (See EventContext class reference)
  3. generate timers if needed
  4. call @model.init if needed

State.enter is defined as follows:

def enter()
  contexts = [[OSX::NSKeyDown, [KeyEvent::KEY_Q, KeyEvent::WITH_CONTROL], proc{|event| quit(); true}]]
  EventContext.setContexts(contexts)
 end

This method registers the common event handler (called the common event context) among all the states in a game. See Event Handlings? for farther information about event contexts.

Manipulating a Model

A State object manipulates its relevant Model when it receives an event through its event handlers and / or event contexts. The following event handlers are defined in State (actually in its mix-in EventHandler module):

  • Key Event Handlers
    • keyDown
    • keyUp
  • Mouse Event Handlers
    • leftMouseDown
    • leftMouseUp
    • rightMouseDown
    • rightMouseUp
    • mouseMoved
    • leftMouseDragged
    • rightMouseDragged
    • mouseEntered
    • mouseExited
  • Timer Event Handler
    • tick

These methods do nothing but return false. You need to override some of these methods to handle the events that you need. Each event handler must return either true or false. The return value of these methods means the necessity of updating its relevant View. View.drawRect will be called if an event handler returns true.

Another way to receive events is using event context, a pair of a concrete event and an action for the event. See Event Handlings? for farther information about handling events.

An event handler, including an action in an event context, manipulates its relevant Model object by invoking some methods that are defined in the Model class. Each State object can access its relevant Model object by referring to its instance variable @model that is set by the game framework.

Transiting to another state

When a certain condition becomes true as a result of manipulating a Model in an event handler, a state transits to another. A state transition is triggered by invoking changeState(klass). The argument "klass" is a subclass of State. Invoking changeState(TankGamePlayingState), for example, at one of the event handlers in TankGamePlaingState makes a transition to TankGamePlayingState.

Leaving a state

When a state is about to transit, State.leave is called. This method cleans up all event handlers and event contexts, as well as invalidating timers if needed. Fortunately, State.leave does most of this clean-up for you. This means that you don't have to write leave methods in most of State's subclasses. You need to write a leave method only if you want to do something more than that is written in State.leave as shown below.

def leave()
  if (defined?(@timer) && @timer && defined?(@timer.invalidate))
    @timer.invalidate()
    @timer = nil
  end
  EventContext.setContexts([])
end

Quitting a game

When a user attempts to quit a game by pressing Ctrl-Q, the current state must be set to TitleSelectingState, which means it returns to the main title. As the event context for this event and the quit method are already implemented in State, you don't have to write the same code in your game. It's enough to call super.enter() at the beginning of enter methods in a subclass of State.

State transition among games

When you launch an application, the current state is set to TitleSelectingState in MainTitle.rb. Selecting a game by pressing the space key changes the current state to the initial state of the selected game. The initial state is defined in a subclass of Game. Take a look at the definition of TypinToddler class.

class TypinToddler < Game
  def initialize()
    @bindings = [[TypinToddlerReadyState, TypinToddlerModel, TypinToddlerView],
                 [TypinToddlerPlayingState, TypinToddlerModel, TypinToddlerView],
                 [TypinToddlerWaitingState, TypinToddlerModel, TypinToddlerView]]
    @initialState = TypinToddlerReadyState
  end

  def self.title() "Typin' Toddler" end
  def self.titleImage() "TypinToddler" end
end

There is an instance variable @initialState. When Typin' Toddler game is selected at main title, MainTitle requests the initial state from the TypinToddler class. the TitleSelectingState object in MainTitle, then, invokes changeState(@initialState).

Technically, MainTitle is a kind of game, but no games other than MainTitle are allowed to transit into a state in other games. To quit a game, simply invoke quit() at a subclass of State.

Leave a comment

Begin the comment with //pukiwiki if you want to write a comment in PukiWiki format.

You must be logged in to post a comment.