My current style of choice rotates around a pseudo-engine. The actual loop might be just...
frame = 0
while happen.process() and not key[K_ESCAPE]:
frame += 1
...where
happen is an instance of my EventHandler class, managing a stack of function/method pointers with game states, input, logic, graphics and so on.
A pointer added into the EventHandler is wrapped in an Event class containing the duration of the event (in frames), arguments for the function, a new event to trigger after the duration runs out, and of course a function for executing the event and reporting if it's active.
class EventHandler():
def __init__(self):
self.events = []
def add_event(self, effect, duration=-1, args=None, followup=None, index=-1):
new = Event(effect, duration, args, followup)
self.events.insert(index, new)
return new
def process(self):
x = 0
while x < len(self.events):
event = self.events[x]
active = event.activate()
consequence = event.followup is not None
if not active:
self.events.pop(x)
if consequence:
self.add_event(event.followup, index=x)
x += active or consequence
return len(self.events)
class Event():
def __init__(self, effect, duration, args, followup):
self.act = effect
self.time = duration
self.args = args
self.followup = followup
def activate(self):
if self.args is not None:
if self.act(self.args)==0:
return 0
else:
if self.act()==0:
return 0
self.time -= self.time>0
return bool(self.time)
So it's a breeze to initialize a small demo and get it rolling with the main loop above.
def init():
happen.add_event(clock.tick, args=60) #Limits the speed to 60 FPS
happen.add_event(get_key) #Sets the global 'key' dictionary for checking input
happen.add_event(pygame.event.get) #Ensures window focus
happen.add_event(scr.fill, args=(112, 112, 190)) #Fills the screen
#The order matters - first added objects are processed first
#(Note: "Consequence" events are pushed at the "cause" event's position in the stack)
for obj in [cloud, infobox1, player, bush]:
#'act' being a polymorphic movement+draw method
#If any of these guys return 0, infobox2 will pop up; but only infobox1 will, after Enter is pressed
happen.add_event(obj.act, followup=infobox2.act)
#At the end of the stack, update the screen and take a screenshot
happen.add_event(pygame.display.update)
happen.add_event(screenshot, duration=360)
The result:
