Isolating event-handling/management and game logic

Started by
7 comments, last by nimrodson 12 years, 2 months ago
Hi!

Before start, i want apologize for my poor english.

Well, I'm a beginner programmer and recently I've taken interest in game programming.
So now, i'm a little concerned about the event-handling (or event-management). Next on, i'll put a python-code as example:

def main():
"""Main module"""

initGame()
while True:
catchGameEvents()
updateGameState()
drawGameState()
def catchEvents():
"""Right here we intercept all user input events and dispatch them
throught all game entities"""

for event in pygame.event.get():
if event.type == QUIT:
sys.quit() #It's part of game logic, right?
elif event.type == KEYUP:
if event.key == UP:
#Manage game logic?
if event.key == DOWN:
#Manage game logic?
if ... #so on



As I know, the previous code mixes game logic and events and I'd like to separate both things.
Trying to resolve this problem, i was searching for an oriented-object solution and I found this: http://ezide.com/gam...ing-games.html. It essentialy solves the problem, but it seems to me very "objected" and a little tricky (and long-term slow???).

How I could resolve this issue?

Thank you!
Advertisement
Hi!

Before start, i want apologize for my poor english.

Well, I'm a beginner programmer and recently I've taken interest in game programming.
So now, i'm a little concerned about the event-handling (or event-management). Next on, i'll put a python-code as example:

def main():
"""Main module"""

initGame()
while True:
catchGameEvents()
updateGameState()
drawGameState()
def catchEvents():
"""Right here we intercept all user input events and dispatch them
throught all game entities"""

for event in pygame.event.get():
if event.type == QUIT:
sys.quit() #It's part of game logic, right?
elif event.type == KEYUP:
if event.key == UP:
#Manage game logic?
if event.key == DOWN:
#Manage game logic?
if ... #so on



As I know, the previous code mixes game logic and events and I'd like to separate both things.
Trying to resolve this problem, i was searching for an oriented-object solution and I found this: http://ezide.com/gam...ing-games.html. It essentialy solves the problem, but it seems to me very "objected" and a little tricky (and long-term slow???).

How I could resolve this issue?

Thank you!
For user input you should delegate to some sort of an "input handler"

THis handler would then take the key events it recieves and match them up to game events. Those events then would be queued and the game its self could check the input handler for queued notifications and handle them.


def main():
"""Main module"""
game = initGame()
while True:
catchGameEvents(game)
updateGameState(game) #<-- in here you would check game.inputHandler for queued events and then handle them.
# the input handler would probably change them to be "game events" instead of key events.
# for instance "UP" key would become "MoveForward"
drawGameState(game)

def catchGameEvents(game):
"""Right here we intercept all user input events and dispatch them
throught all game entities"""

for event in pygame.event.get():
if event.type == QUIT:
game.quit()
elif event.type == KEYUP:
game.inputHandler.notify(event)
elif event.type == KEYDOWN:
game.inputHandler.notify(event)

As an example...

class InputHandler:
def __init__(self):
self.eventMapping = {}
self.events = {}
self.nonEventQueue = []

def addHandler(self, key, gameEvent):
self.eventMapping[key] = gameEvent
self.events[gameEvent] = false

def notify(self, event):
if event.key in self.eventMapping and event.type == KEYUP:
self.events[self.eventMapping[event.key]] = true;
elif event.key in self.eventMapping and event.type == KEYDOWN:
self.events[self.eventMapping[event.key]] = false;
else:
pass #ignore non-mapped entries

def checkEvent(self, gameEvent):
return self.events[gameEvent]

#sample usage...
inputHandler = InputHandler()
inputHandler.addHandler(UP, GameEvent_MoveForward)

inputHandler.notify(event) #assume this was a UP key press

if inputHandler.checkEvent(GameEvent_MoveForward):
#handle move forward event

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Ok, thank you very much! I'm going to re-think my design.
To be honest, I think that every reasonable solution would be an instance of the Mediator (like in the linked article) or the Observer pattern, so you'd better start from these (imho).

[ About me ]

On some basic level your event handling is going to have to mix in with game logic to some degree. But the idea is that your game relies on abstract events and event responses as opposed to hard coded object dependancies. So to add new functionality you just need to hook into your event handler and send out an appropriate event/response. Depending on the type of the response it would get sent to the appropriate function in the game logic. (Event parent class and KeyEvent, CollisionEvent, etc. subclasses)

The general idea looks something like this:

-A game object wants to listen for keyboard presses.
-It registers itself as a listener to the Input Handler
-The game object needs an event response function.(Game Logic)
-Input Handler records a key down.
-Input Handler send out an event message to its listeners


So in your case:

def main():
"""Main module"""
initGame()
while True:
catchGameEvents()
updateGameState()
drawGameState()
def catchEvents():

"""Here we dispatch the events to anyone who is listening"""
for event in pygame.event.get():
for eventListener in EventListenerArray
eventListener.Notify(event);




......"""In each individual game object handle that objects response to differing events"""

class Player implements EventListener
{

def onEvent(KeyUpEvent event)
{
""".....Object/game logic"""

if event.key == UP:
#player keyup

if event.key == DOWN:
#player keydown
}

def onEvent(CollisionEvent event)
{
if event.owner == ENEMY:
#player explodes
}

}

On some basic level your event handling is going to have to mix in with game logic to some degree. But the idea is that your game relies on abstract events and event responses as opposed to hard coded object dependancies. So to add new functionality you just need to hook into your event handler and send out an appropriate event/response. Depending on the type of the response it would get sent to the appropriate function in the game logic. (Event parent class and KeyEvent, CollisionEvent, etc. subclasses)

The general idea looks something like this:

-A game object wants to listen for keyboard presses.
-It registers itself as a listener to the Input Handler
-The game object needs an event response function.(Game Logic)
-Input Handler records a key down.
-Input Handler send out an event message to its listeners


So in your case:

def main():
"""Main module"""
initGame()
while True:
catchGameEvents()
updateGameState()
drawGameState()
def catchEvents():

"""Here we dispatch the events to anyone who is listening"""
for event in pygame.event.get():
for eventListener in EventListenerArray
eventListener.Notify(event);




......"""In each individual game object handle that objects response to differing events"""

class Player implements EventListener
{

def onEvent(KeyUpEvent event)
{
""".....Object/game logic"""

if event.key == UP:
#player keyup

if event.key == DOWN:
#player keydown
}

def onEvent(CollisionEvent event)
{
if event.owner == ENEMY:
#player explodes
}

}



First all, I wanna thank you for your answer. It cleared me several questions smile.png
Recently, I just have completed a very primitive test-game based partialty on what you posted. Here is:


import sys, pygame, abc
from pygame.locals import *

RED = (255,0,0)
BLACK = (0,0,0)
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4

class Event(object):
"""We have events here"""
_metaclass__ = abc.ABCMeta
listeners = []

def __init__(self):
"""Creates an event"""
super(Event, self).__init__()

def post (self):
"""Posts the existence of this event to their own listeners"""
for l in Event.listeners:
self.notifyFromEvent(l)

@classmethod
def addSuscriptor (self, eventListener):
""" Add an eventListener """
Event.listeners.append(eventListener)

@classmethod
def removeSuscriptor (self, eventListener):
""" Removes an eventListener """
Event.listeners.remove(eventListener)

@abc.abstractmethod
def notifyFromEvent(self, listener):
"""Start the double-dispatching event-driven system"""
print "Shouldn't execute this. Must be redefined by derivated class"

class MoveEvent(Event):
"""Move event"""
def __init__(self, keyStroked):
super(MoveEvent, self).__init__()
self.msg = "I'm a move event"
self.keyStroked = keyStroked

def notifyFromEvent(self, listener):
"""Implementig Event method."""
listener.notifyFromMoveEvent(self)

########################################################################

#interface
class EventListener(object):
""" This pseudo-interface has methods triggered from specific
events ("event-notifiers"). The model object overrides those
"event-notifiers" will handle """

def __init__ (self):
""" Class initialiser """
super(EventListener, self).__init__()

def suscribeTo (self, EventClass):
""" An object suscribes to an Event class to receive notifications
from them """
EventClass.addSuscriptor(self)

def unsuscribeTo (self, EventClass):
""" Stop receiving notifications from EventClass """
EventClass.removeSuscriptor(self)

def notifyFromMoveEvent(self, moveEvent):
"""Handles MoveEvent"""
pass

########################################################################

class Game(object):
"""The main class. It manages everything; maybe its function is
to be a central controller"""

def __init__(self, gameSprites):
super(Game, self).__init__()
self.gameSprites = gameSprites
self.eventList = []
pygame.init()
self.screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption('Prueba 1')

def catchUserInput(self):
"""Catch user inputs (from keyboard) and creates custom Events"""

ev = None
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit() #FIXIT: It's part of game logic
elif event.type == KEYDOWN:
if event.key == K_UP:
ev = MoveEvent(UP)
elif event.key == K_DOWN:
ev = MoveEvent(DOWN)
elif event.key == K_LEFT:
ev = MoveEvent(LEFT)
elif event.key == K_RIGHT:
ev = MoveEvent(RIGHT)
else:
pass
else:
pass

if ev:
self.eventList.append(ev)

def updateState(self):
"""Updates all game entities states posting all events stored"""
for ev in self.eventList:
ev.post()
self.eventList.remove(ev)

def drawState(self):
"""Draws all game entities states"""
self.screen.fill(BLACK)
for gs in self.gameSprites:
self.screen.blit(gs.image, gs.model.pos)
pygame.display.update()

########################################################################

class BallSprite(pygame.sprite.Sprite):
"""A ball sprite :)"""

def __init__(self, color, ballModel):
super(BallSprite, self).__init__()
self.model = ballModel
#Create the proper sprite
self.image = pygame.Surface((15,15))
self.image.fill(color)
# Make our top-left corner the passed-in location.
self.rect = self.image.get_rect()
self.rect.topleft = ballModel.pos

def update(self):
"""Updates the position of the ball sprites"""
pass

class Ball(EventListener):
"""Ball object from model"""
def __init__(self, initPos):
super(Ball, self).__init__()
self.pos = initPos

#//EventListener method response-events implementations

def notifyFromMoveEvent(self, moveEvent):
"""Move throught the map"""
if moveEvent.keyStroked == UP:
self.pos = (self.pos[0], self.pos[1] - 10)
if moveEvent.keyStroked == DOWN:
self.pos = (self.pos[0], self.pos[1] + 10)
if moveEvent.keyStroked == LEFT:
self.pos = (self.pos[0] - 10, self.pos[1])
if moveEvent.keyStroked == RIGHT:
self.pos = (self.pos[0] + 10, self.pos[1])

def main():
"""Main module"""

b = Ball((5,5))
b.suscribeTo(MoveEvent)
game = Game((BallSprite(RED,b),))
while True:
game.catchUserInput()
game.updateState()
game.drawState()

if __name__ == '__main__':
main()


It should work reasonability well in python 2.7. I'd like to receive critics for what I've done, any comment will be appreciated.
Thanks!!
Merged the two topics.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.


Merged the two topics.

Ok.

This topic is closed to new replies.

Advertisement