""" Acorn: A simple game framework This is a way to organize the events of a game. As I tried to make more complex games, I ran into problems involving how to get information from one part of a game to another, such as the game's title screen, walking-around screen, and quick-travel screen. Fearing infinite recursion from one game function calling another and vice versa, I tried to link these "screens" by having a central game object put them on a stack, controlled by a loop, with a message stack that they could use to exchange information on various game or UI events. That system quickly got confusing and buggy. Here is what will hopefully be a more workable system. There's a central game object with a main loop calling functions for various game states/screens, but now the main loop actually runs constantly, farming out logic and drawing to a pair of functions based on the current state name but staying in charge of basic tasks like updating the screen and maintaining the framerate. Messaging between parts of the game can best be handled, in this system, by modifying a "game world" class that is part of a subclass of AcornFramework. That is, logic functions should call functions of that world object to make changes to it such as adding characters. """ __author__ = "Kris Schnee" __date__ = "2007.12.13" __license__ = "Public Domain" import time ## For FPS counter, not really necessary. import pygame.time ## For framerate regulation. class AcornFramework: """The main class of the game. Attempts to follow the MVC Architecture: -Model: class World (to be created by you) -View: *Draw functions -Controller (and game logic): *Logic functions Extend this class with state functions (*Draw and *Logic) and a reference to/inclusion of a model/world. """ def __init__(self,starting_state=None): self.logic_function = None self.draw_function = None self.setup_function = None self.states = [] if starting_state: self.PushState(starting_state) if self.setup_function: self.setup_function() ## Framerate regulation. self.clock = pygame.time.Clock() self.framerate = 20 """ State Control """ def GetStateFunctions(self,state_name): try: name = state_name + "Logic" logic_function = getattr(self,name) name = state_name + "Draw" draw_function = getattr(self,name) except: raise "Error: Couldn't find matching logic/draw functions for state \""+state_name+"\"." name = state_name + "Setup" ## Optional if hasattr(self,name): setup_function = getattr(self,name) else: setup_function = None return logic_function, draw_function, setup_function def PushState(self,state_name): """Start a new game state, but revert when done.""" ## print "Pushing state '"+state_name+"'" logic_function, draw_function, setup_function = self.GetStateFunctions(state_name) self.logic_function = logic_function self.draw_function = draw_function self.setup_function = setup_function if self.setup_function: self.setup_function() self.states.append(state_name) ## print "States: "+str(self.states) def PopState(self): """End the current game state.""" try: self.states.pop() ## print "Popped state. Remaining: "+str(self.states) if self.states: self.logic_function, self.draw_function, self.setup_function = self.GetStateFunctions(self.states[-1]) if self.setup_function: ## Run it if it exists. self.setup_function() else: self.logic_function, self.draw_function, setup_function = None, None, None except: raise "Error: There are no game states to pop!" def JumpState(self,state_name): """Pop and push to end this state and switch to another.""" ## print "running JumpState to: "+state_name self.PopState() self.PushState(state_name) ## print "Current setup function exists: "+str(self.setup_function is not None) if self.setup_function: self.setup_function() """ Main Loop """ def MainLoop(self): """Do basic event-handling until the program ends. The game logic and drawing are handled by a pair of functions that can be replaced on the fly to represent switching to different aspects of gameplay, such as a title screen, battle screen etc..""" cycles = 0 starting_time = time.time() while self.states: ## Until there's nothing to do: self.draw_function() unhandled_events = self.logic_function() if unhandled_events: for event in unhandled_events: if event.type == QUIT: return elif event.type == KEYDOWN and event.key == K_ESC: return pygame.display.update() self.clock.tick(self.framerate) cycles += 1 ## At program's end, display the overall framerate. if cycles: ending_time = time.time() fps = cycles/float(ending_time - starting_time) print "FPS: "+str(fps) """ Other """ def SetDesiredFramerate(self,fps=20): self.framerate = fps ## Autorun (Demo) if __name__ == "__main__": print "Running demo." import time import pygame a = AcornFramework() a.MainLoop()