There are three kinds of events in the game framework - KeyEvent, MouseEvent, and TimerEvent. These classes encapsulate events that are defined in Cocoa so that the game framework can delegate these events to the current state if required. This chapter illustrates how to handle these events in your game, as well as registering and unregistering event handlers.
Event handing in the game framework
As mentioned in the previous chapter, a game receives events in subclasses of State. There are two different ways to handle events in these classes: 1) by using default event handlers, and 2) by using EventContext.
Using Default Event Handlers
As same as Cocoa applications, there are default event handlers as listed below.
- Key Event Handlers
- keyDown(event)
- keyUp(event)
- Mouse Event Handlers
- leftMouseDown(event)
- leftMouseUp(event)
- rightMouseDown(event)
- rightMouseUp(event)
- mouseMoved(event)
- leftMouseDragged(event)
- rightMouseDragged(event)
- mouseEntered(event)
- mouseExited(event)
- Timer Event Handler
- tick(object)
All the handlers except tick take an NSEvent object as an argument (tick takes a timer ID).
To receive an event in a subclass of State, overrides the default event handlers in the subclass. Unlike writing a subclass of NSView in Ruby, you don't have to write ns_overrides for these methods since State is a pure ruby class (FYI, RubyCocoa in cvs doesn't require ns_overrides any more as of Sep/10/2006). You also don't have to be worried about NSResponder related stuff. The game framework does these things for you.
To Receive the NSKeyDown events where its key code is either KeyEvent::SPACE or KeyEvent::KEY_A, for example, you may write the code like this in a subclass of State:
def enter()
super.enter() # registers some common event handlers
# do some other initialization if any
end
def keyDown(event)
if (event.keyCode == KeyEvent::SPACE)
# do something
elsif (event.keyCode == KeyEvent::KEY_A)
# do something
end
return false # return true if it needs to update a View
end
For your convenience, KeyEvent defines all the key codes (such as KeyEvent::SPACE and KeyEvent::KEY_A). See KeyEvent class reference for available key code constants.
Receiving an event through the default event handlers is similar to that in Cocoa applications. See NSEvent and NSTimer class references for more details about these events. You can also refer to the following class references of the game framework.
Using Event Context
The event context, another kind of event handler, is a pair of a concrete event that you want to receive, and an action for the event. To receive a concrete event through the game framework, you need to register an event context by using either EventContext.setContexts or EventContext.addContext, typically at the enter method in a subclass of State.
If you want to receive a space key event, for example, to launch a missile, write the following code at the enter method of a subclass of State:
EventContext.addContext(NSKeyDown,
KeyEvent::SPACE,
proc { |event| @model.launch(); false })
The first two arguments, NSKeyDown and KeyEvent::SPACE, specify a concrete event that you want to receive. The third argument, a Ruby Proc object { |event| @model.launch(); false }, specifies an action for the event. (this is like an unnamed method that takes event as an argument.) The false at the end of the Ruby proc returns false to the game framework, which means there is no need to update the relevant view. Returning true here makes the framework invoke View.drawRect.
There are some default event contexts (such as Ctrl-Q key to quit the game) that are defined in State.enter (super class's method). If you want to use the default event contexts, use EventContext.addContext to add one or more contexts besides the common event contexts. If you want to use only your event contexts, use EventContext.setContexts. This method overrides all predefined event contexts.
Priority between EventContext and Default Event Handlers
When an event occurs, an Event object (e.g. KeyEvent) asks EventContext if there is a registered event context that matches the event. If matched, the Event object invokes the Ruby proc that is designated in the matched event context. If there is no matched context, the Event object tries to invoke its default event handler (e.g. keyDown for key events, or tick for timer events).
EventContext vs. Default Event Handlers
Though both EventContext and Default Event Handlers do similar things, there are some differences between these. I show you pros and cons of those two different means of handling events so that you can choose the right one depending on a situation.
Pros of EventContext
No need to write if - else / switch statements in an event handler
As EventContext directly maps a specific event to an action, you don't have to write "if - else" or "switch" statements only to find which specific event is received(e.g. which key is pressed). Take a look at the code:
contexts =
[[NSKeyDown, KeyEvent::SPACE, proc {|event| @model.launchMissile(); false}],
[NSKeyDown, KeyEvent::LEFT_ARROW, proc {|event| @model.moveLeft(); false}],
[NSKeyDown, KeyEvent::RIGHT_ARROW, proc {|event| @model.moveRight(); false}],
[NSKeyDown, KeyEvent::UP_ARROW, proc {|event| @model.moveUp(); false}],
[NSKeyDown, KeyEvent::DOWN_ARROW, proc {|event| @model.moveDown(); false}]]
contexts.each {|context| EventContext.addContext(*context)}
This technique can get rid of "if - else" statements out of an event handler. Unlike using EventContext, a default event handler receives all events that are related to it. The keyDown handler for the events at the example above will be like this:
def keyDown(event)
if (event.keyCode == KeyEvent::SPACE)
@model.fire()
elsif (event.keyCode == KeyEvent::LEFT_ARROW)
@model.moveLeft()
elsif (event.keyCode == KeyEvent::RIGHT_ARROW)
@model.moveRight()
elsif (event.keyCode == KeyEvent::UP_ARROW)
@model.moveUp()
elsif (event.keyCode == KeyEvent::DOWN_ARROW)
@model.moveDown()
end
return false
end
No need to write an event handler in some cases
In the code above, you don't have to write an event handler because the Proc object in the event context directly manipulates a Model. This improves the performance a little bit compared to using keyDown method since the game framework invokes @model.xxxxx directly whereas keyDown indirectly invokes @model.xxxxx from the handler (the framework invokes keyDown, and then, keyDown invokes @model.xxxx ).
Easier to write a common action among states for a certain event
If there is more than one state in your game, these might have some common actions for a specific event. TankGame in Mini Game Tutorial, for example, has three states - TankGamePlayingState, TankGameStartState, and TankGameOverState.
These three states share a common action quit in respond to a "Control-Q" key event. In this case, their super class (State) registers the event context that specifies the action for the event. This prevents developers from writing the same code at these three classes.
In TankGame, this common event context is registered at State.enter as it is shared among all the states in MiniKidsGames. To share such a common event context other than quit within your game, make a subclass of State, say SharedActionHolder, to register a common event context at its enter method. Each subclass of SharedActionHolder calls super.enter() to register the common event context. And then, add state specific event contexts by using EventContext.addContext at the enter methods in those classes respectively.
Cons of EventContext
- Need to add or set event contexts before you use these
- Incompatible with other Cocoa applications
- A little overkill for mouse events
- since mouse event handlers are very concrete. (e.g. you don't have to check what button is pressed.)
Pros of Default Event Handlers
- No need to register event handlers.
- Easier to handle different key events in the same way
e.g. drawing a character on a window for key events - Compatible with other Cocoa applications
Cons of Default Event Handlers
Cons of default event handlers are the reflection of Pros of EventContext.
- Need to write "if - else" or "switch" statements for handling different key events in different ways
- A common action for a certain event among states might be embedded in all the states.
Which way should you use?
It totally depends on a situation. As a matter of fact, mixing these two methods sometimes makes your code simpler and efficient. Here are some criteria to help you choose one of these.
- Prefer using EventContext:
- when there are some common actions for a certain event among states
- when you use more than one timer at a state
- if you don't want to write "if - else" or "switch" statements for keyDown events
- Prefer using Default Event Handlers:
- for mouse events
- for sharing an action among event handlers
- Use alias :rightMouseDown :leftMouseDown when an action for leftMouseDown and rightMouseDown are the same
Differences between the game framework and Cocoa
There are three differences between these in terms of handling events.







