diff options
Diffstat (limited to 'MAFH2/fortuneengine/fortuneengine/GameEngine.py')
-rw-r--r-- | MAFH2/fortuneengine/fortuneengine/GameEngine.py | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/MAFH2/fortuneengine/fortuneengine/GameEngine.py b/MAFH2/fortuneengine/fortuneengine/GameEngine.py new file mode 100644 index 0000000..48d7d3e --- /dev/null +++ b/MAFH2/fortuneengine/fortuneengine/GameEngine.py @@ -0,0 +1,502 @@ +# FortuneEngine is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FortuneEngine is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the FortuneEngine. If not, see <http://www.gnu.org/licenses/>. +# +# Author: Justin Lewis <jlew.blackout@gmail.com> + +import pygame +from time import time +from GameEngineConsole import GameEngineConsole +from GameInspect import GameInspect +from DrawableFontObject import DrawableFontObject +from Scene import Scene + + +class GameEngine(object): + """ + The Fortune Engine GameEngine is a main loop wrapper around pygame. + It manages the event and drawing loops allowing the user to just + register for user events and drawing time in the draw loop. + """ + instance = None + + def __init__(self, width=1200, height=900, always_draw=False, + fps_cap=15, version=False, title="FortuneEngine"): + """ + Constructor for the game engine. + + @param width: Window width + @param height: Window height + @param always_draw: Boolean to set the animation mode to always + draw vs draw when set_dirty is called + @param fps_cap: Sets the framerate cap. Set to 0 to disable + the cap. Warning: setting the cap to 0 when + always draw = False will cause cpu 100% when + not driving. + @param version: If true, use new rendering system, false uses + only the draw system + @param title: Window Title + """ + GameEngine.instance = self + pygame.init() + pygame.mouse.set_visible(False) + self.__version = version #true is new, false is old + + # Window Settings + self.width = width + self.height = height + size = width, height + self.screen = pygame.display.set_mode(size) + pygame.display.set_caption(title) + self.__fps = DrawableFontObject("", pygame.font.Font(None, 17)) + self.__fps.setPosition(0, 0) + self.__scene = Scene(self.__fps) + + # Engine Internal Variables + self.__fps_cap = fps_cap + self.__showfps = False + self.__dirty = True + self.__always_draw = always_draw + self.__font = pygame.font.Font(None, 17) + self.__run_event = False + + # Variables to hold game engine elements and callbacks + self.__event_cb = [] + self.__draw_lst = [] + self.__object_hold = {} + + + # Game Timers + self.__active_event_timers = [] + self.__active_event_timers_tick = [] + + # Game Clock + self.clock = pygame.time.Clock() + self.__tick_time = 0 + + # Inspector + self._inspector = GameInspect(self.__object_hold) + + # Time Profiler Timers + self.__draw_time = {} + self.__draw_calls = {} + self.__event_time = {} + self.__event_calls = {} + self.__timer_time = {} + self.__timer_calls = {} + + # Initialize Py Console + self.console = GameEngineConsole(self, (0, 0, width, height / 2)) + + # Disable Mouse Usage + # TODO Allow mouse motion on request + pygame.event.set_blocked(pygame.MOUSEMOTION) + + def set_dirty(self): + """ + Sets the dirty flag to force the engine to draw the next time + it enters the draw flag. + """ + self.__dirty = True + + def get_scene(self): + """ + Returns the scene object + + @return: Returns the scene object held by the game engine + """ + return self.__scene + + def start_event_timer(self, function_cb, time): + """ + Starts a timer that fires a user event into the queue every "time" + milliseconds + + @param function_cb: The function to call when timer fires + @param time: Milliseconds between fires + """ + avail_timer = len(self.__active_event_timers) + + if avail_timer + pygame.USEREVENT < pygame.NUMEVENTS: + if function_cb not in self.__active_event_timers: + self.__timer_time[str(function_cb)] = 0 + self.__timer_calls[str(function_cb)] = 0 + + self.__active_event_timers.append(function_cb) + self.__active_event_timers_tick.append(time) + pygame.time.set_timer(pygame.USEREVENT + avail_timer, time) + else: + print "ERROR TIMER IN LIST" + else: + print "Ran out of timers :(" + self.stop_event_loop() + + def stop_event_timer(self, function_cb): + """ + Stops the timer that has id from firing + + @param function_cb: The function registered with the timer that + should be canceled + """ + try: + timer_id = self.__active_event_timers.index(function_cb) + except ValueError: + return + + pygame.time.set_timer(pygame.USEREVENT + timer_id, 0) + del self.__active_event_timers[timer_id] + del self.__active_event_timers_tick[timer_id] + + # Timers have been removed, now need to clear any events + # already fired and in the queue + pygame.event.clear(pygame.USEREVENT + timer_id) + + def list_event_timers(self): + """ + returns a list of configured timers, if the timers has a time of 0 the + timer is disabled + """ + timer_list = "Event Timers:\n" + i = 0 + for timer_item in self.__active_event_timers: + timer_list += "\t%d: %d\n" % (timer_item, + self.__active_event_timers_tick[i]) + i = i + 1 + + return timer_list + + def list_timer_time(self): + """ + Returns a string representation of the time the game spends + in each timer callback. + """ + mystr = "Timer Times:\n\tName\tCalls\tTotal Time\tAvg" + for key in self.__timer_time: + timer_times = self.__timer_time[key] + timer_calls = self.__timer_calls[key] + if timer_calls == 0: + avg = 0 + else: + avg = timer_times / timer_calls + + mystr = "%s\n\t%s\n\t\t%d\t%f\t%f" % \ + (mystr, key, timer_calls, timer_times, avg) + return mystr + + def start_main_loop(self): + """ + Starts the game loop. + + This function does not return until after the game loop exits + """ + self.__run_event = True + self._event_loop() + + def _draw(self, tick_time): + """ + Draws all elements in draw callback to the screen + + @param tick_time: The amount of time passed since last + draw cycle. (should be produced by + pygamme.clock.tick method) + """ + screen = self.screen + + # If console is active, we want to draw console, pausing + # game drawing (events are still being fired, just no + # draw updates. + if self.__version: + if self.console.active: + self.console.draw() + pygame.display.flip() + else: + for fnc in self.__draw_lst: + start = time() + fnc() + self.__draw_time[str(fnc)] += time() - start + self.__draw_calls[str(fnc)] += 1 + # Print Frame Rate + if self.__showfps: + self.__fps.changeText('FPS: %d' % self.clock.get_fps(), + (255, 255, 255)) + else: + self.__fps.changeText('') + self.__scene.update(tick_time) + pygame.display.update(self.__scene.draw(screen)) + else: + if self.console.active: + self.console.draw() + pygame.display.flip() + else: + for fnc in self.__draw_lst: + start = time() + fnc(screen, tick_time) + self.__draw_time[str(fnc)] += time() - start + self.__draw_calls[str(fnc)] += 1 + # Print Frame Rate + if self.__showfps: + text = self.__font.render('FPS: %d' % \ + self.clock.get_fps(), False, (255, 255, 255), + (159, 182, 205)) + screen.blit(text, (0, 0)) + pygame.display.flip() + + def _event_loop(self): + """ + The main event loop. + """ + while self.__run_event: + + event = pygame.event.poll() + + # Handle Game Quit Message + if event.type == pygame.QUIT: + self.__run_event = False + + # No-Op sent, draw if set to always draw + elif event.type == pygame.NOEVENT: + # Tick even if not drawing + # We want to pause the cpu from getting into a + # 100% usage looping on the poll until something + # becomes dirty + self.__tick_time += self.clock.tick(self.__fps_cap) + if self.__always_draw or self.__dirty: + self.__dirty = False + self._draw(self.__tick_time) + self.__tick_time = 0 + + + # Handle User event Timers + elif event.type >= pygame.USEREVENT and \ + event.type < pygame.NUMEVENTS: + + timer_id = event.type - pygame.USEREVENT + + # Call timer + str_rep = str(self.__active_event_timers[timer_id]) + start = time() + self.__active_event_timers[timer_id]() + self.__timer_time[str_rep] += time() - start + self.__timer_calls[str_rep] += 1 + + # Check if we should activate the console + elif event.type == pygame.KEYDOWN and event.key == pygame.K_w \ + and pygame.key.get_mods() & pygame.KMOD_CTRL: + self.console.set_active() + self.set_dirty() + + # Pass event to console + elif self.console.process_input(event): + self.set_dirty() + + # Pass events to all others + else: + # Make a copy first so that adding events don't get fired + # right away + list_cp = self.__event_cb[:] + + # Reverse list so that newest stuff is on top + # TODO: cache this list + list_cp.reverse() + + for cb in list_cp: + # Fire the event for all in cb and stop + # if the callback returns True + start = time() + retur_val = cb(event) + self.__event_time[str(cb)] += time() - start + self.__event_calls[str(cb)] += 1 + + if retur_val: + break + + def stop_event_loop(self): + """ + Sends a pygame.QUIT event into the event queue which + exits the event loop + """ + pygame.event.post(pygame.event.Event(pygame.QUIT)) + + def add_event_callback(self, cb): + """ + Adds event callback to the event callback stack + + @param cb: Callback to be added to the stack when events are fired. + """ + self.__event_time[str(cb)] = 0 + self.__event_calls[str(cb)] = 0 + self.__event_cb.append(cb) + + def remove_event_callback(self, cb): + """ + Removes an event from the event callback stack + + @param cb: The callback to remove from the event callback stack + @return: Returns true if successful in removing callback + """ + try: + self.__event_cb.remove(cb) + return True + except: + return False + + def list_event_callbacks(self): + """ + Returns a string representation of all events registered with the game + engine + """ + event_callbacks = "Event Listeners:\n" + for eventlst in self.__event_cb: + event_callbacks = "%s\t%s\n" % (event_callbacks, str(eventlst)) + return event_callbacks + + def list_event_time(self): + """ + Returns a string representation of the time the game spends + in each event callback. + """ + mystr = "Event Times:\n\tName\tCalls\tTotal Time\tAvg" + for key in self.__event_time: + event_times = self.__event_time[key] + event_calls = self.__event_calls[key] + if event_calls == 0: + avg = 0 + else: + avg = event_times / event_calls + + mystr = "%s\n\t%s\n\t\t%d\t%f\t%f" % \ + (mystr, key, event_calls, event_times, avg) + return mystr + + def add_draw_callback(self, fnc): + """ + Adds a callback to the draw list. Function will be passed the + game screen it should draw too. + + @param fnc: The function to call when system is drawing + """ + + self.__draw_time[str(fnc)] = 0 + self.__draw_calls[str(fnc)] = 0 + self.__draw_lst.append(fnc) + + def pop_draw_callback(self): + """ + Removes top of draw stack and returns it + + @return: Returns the top callback function that was removed + """ + return self.__draw_lst.pop() + + def clear_draw_callback(self): + """ + Empties draw callback stack + """ + self.__draw_lst = [] + + def remove_draw_callback(self, fnc): + """ + Removes a draw callback from the game engine draw function + + @param fnc: The callback function to remove + @return: Returns true if successful removal of the function + """ + try: + self.__draw_lst.remove(fnc) + return True + except: + return False + + def list_draw_callbacks(self): + """ + Lists all the draw callbacks currently registered with the game engine + """ + + callbacks = "Draw Callbacks:\n" + for eventlst in self.__draw_lst: + callbacks += "\t%s\n" % str(eventlst) + return callbacks + + def list_draw_time(self): + """ + Returns a string representation of the time the game spends + in each drawing callback. + """ + mystr = "Drawing Times:\n\tName\tCalls\tTotal Time\tAvg" + for key in self.__draw_time: + draw_times = self.__draw_time[key] + draw_calls = self.__draw_calls[key] + if draw_calls == 0: + avg = 0 + else: + avg = draw_times / draw_calls + + mystr = "%s\n\t%s\n\t\t%d\t%f\t%f" % \ + (mystr, key, draw_calls, draw_times, avg) + return mystr + + def has_object(self, name): + """ + Returns true if object is stored in game engine + + @param name: Name of the object to check if exists + @return: Returns true if object found + """ + return name in self.__object_hold + + def add_object(self, name, obj): + """ + Adds an object to the game engine datastore + + @param name: The name used to store the object + @param obj: The object to store + """ + self.__object_hold[name] = obj + + def get_object(self, name): + """ + Returns an object from the game engine datastore + + @param name: The name of object to return + @return: Returns the object + """ + return self.__object_hold[name] + + def remove_object(self, name): + """ + Removes an object from the game engine datastore + + @param name: The name of the object to remove + """ + del self.__object_hold[name] + + def list_objects(self): + """ + Returns a sting of registered objects + """ + objlist = "Objects Registered:\n" + for eventlst in self.__object_hold: + objlist += "\t%s\n" % str(eventlst) + return objlist + + def toggle_fps(self): + """ + Toggles fps display + """ + self.__showfps = not self.__showfps + + def art_scale(self, original, expected, width=True): + if width: + return int(self.width / float(expected) * float(original)) + else: + + return int(self.height / float(expected) * float(original)) |