Пример #1
0
class Engine(Context):
    
    NO_WORLD = NO_WORLD
    SIMPLE_WORLD = SIMPLE_WORLD
    PYMUNK_WORLD = PYMUNK_WORLD
    
    def __init__(self,
        screen_surface=None, resolution=None, display_flags=0, caption=None,
        camera_target=None, camera_view=None, camera_view_rect=None,
        map=None, tile_size=None, map_size=None,
        update_speed=30, frame_speed=30, default_schedules=True,
        world_type=NO_WORLD, world_args={},
        set_state=True):
        """Construct an instance of Engine.
        
        This constructor does the following:
            
            The pygame display is initialized with an optional caption, and the
            resulting screen.Screen object is placed in State.screen.
            
            An empty map.BasicMap object is created and placed in State.map.
            
            An empty model.World* object is created and placed in State.world.
            
            State.world_type is set to one of the engine.*_WORLD values
            corresponding to the world object in State.world.
            
            A camera.Camera object is created and placed in State.camera. The
            camera target is either taken from the camera_target argument, or an
            appropriate target for world type is created. The target is *NOT*
            added to the world, as the target does not need to be an object
            subject to game rules. If target happens to be an avatar-type object
            then add it manually to world with the rest of the world entities.
            
            A game_clock.GameClock object is created and placed in State.clock.
            
            Joystick objects are created for connected controllers.
        
        The following arguments are used to initialize a Screen object:
            
            The screen_surface argument specifies the pygame top level surface
            to use for creating the State.screen object. The presence of this
            argument overrides initialization of the pygame display, and
            resolution and display_flags arguments are ignored. Use this if
            the pygame display has already been initialized in the calling
            program.
            
            The resolution argument specifies the width and height of the
            display.
            
            The display_flags argument specifies the pygame display flags to
            pass to the display initializer.
            
            The caption argument is a string to use as the window caption.
        
        The following arguments are used to initialize a Camera object:
            
            The camera_target argument is the target that the camera will track.
            If camera_target is None, Engine will create a default target
            appropriate for the world type.
            
            The camera_view argument is a screen.View object to use as the
            camera's view.
            
            The camera_view_rect argument specifies the pygame Rect from which
            to create a screen.View object for the camera's view.
            State.screen.surface is used as the source surface. This argument is
            ignored if camera_view is not None.
        
        The following arguments are used to initialize a BasicMap object:
            
            The tile_size and map_size arguments specify the width and height of
            a map tile, and width and height of a map in tiles, respectively.
        
        The following arguments are used to initialize a model.World* object:
            
            The world_type argument specifies which of the world classes to
            create. It must be one of engine.NO_WORLD, engine.SIMPLE_WORLD,
            engine.PYMUNK_WORLD.
            
            The world_args argument is a dict that can be passed verbatim to
            the world constructor (see the World* classes in the model module)
            like so: World(**world_args).
        
        The following arguments are used to initialize a Clock object:
            
            update_speed specifies the maximum updates that can occur per
            second.
            
            frame_speed specifies the maximum frames that can occur per second.
        
        The clock sacrifices frames per second in order to achieve the desired
        updates per second. If frame_speed is 0 the frame rate is uncapped.
        
        Engine.update() and Engine.draw() are registered as callbacks in the
        clock.
        
        By default the Engine class schedules these additional items:
            
            clock.schedule_update_priority(self._get_event, -2.0)
            clock.schedule_update_priority(State.world.step, -1.0)
            clock.schedule_update_priority(State.camera.update, 1.0)
            clock.schedule_frame_priority(State.camera.interpolate, -1.0)
            
        The first three items coincide with the clock's callback to
        Engine.update(). The last item coincides with Engine.draw().
        
        The use of priorities allows user items to be scheduled in between these
        default items by using an appropriate float value: lower priorities will
        be run first. See gameclock.GameClock.schedule_*_priority().
        
        To prevent scheduling the world and camera items, pass the constructor
        argument default_schedules=False. If these are not scheduled by Engine,
        the using program will either need to schedule them or place them
        directly in the overridden update() and draw() methods, as appropriate.
        """
        
        if __debug__: print 'Engine: -- new engine --'
        
        Context.__init__(self)
        
        ## If you don't use this engine, then in general you will still want
        ## to initialize your State objects in the same order you see here.
        
        self.world_type = world_type
        self.screen = None
        self.caption = caption
        self.map = None
        self.world = None
        self.camera = None
        self.camera_target = camera_target
        self.clock = None
        
        ## Screen.
        if screen_surface:
            if __debug__: print 'Engine: Screen(surface=screen_surface)'
            self.screen = Screen(surface=screen_surface)
        elif resolution:
            if __debug__: print 'Engine: Screen(resolution, display_flags)'
            self.screen = Screen(resolution, display_flags)
        else:
            if __debug__: print 'Engine: SKIPPING screen creation: no screen surface or resolution'
            self.screen = State.screen
        
        ## BasicMap.
        if map:
            if __debug__: print 'Engine: using pre-made map'
            self.map = map
        elif tile_size and map_size:
            if __debug__: print 'Engine: BasicMap(map_size, tile_size)'
            self.map = BasicMap(map_size[0], map_size[1], tile_size[0], tile_size[1])
        else:
            if __debug__: print 'Engine: SKIPPING map creation: no map, tile_size, or map_size'
        
        ## If you want to use the camera target as a world entity, you have to
        ## use the right object type. Type checking and exception handling are
        ## not done. This is to allow flexible initialization of the Engine
        ## context.
        if __debug__ and self.camera_target:
            print 'Engine: using pre-made camera target'
        if not self.map:
            if __debug__: print 'Engine: SKIPPING world creation: no map'
            pass
        elif world_type == NO_WORLD:
            if __debug__: print 'Engine: NoWorld(self.map.rect)'
            self.world = model.NoWorld(self.map.rect)
            if camera_target is None:
                if __debug__: print 'Engine: making camera target Object()'
                self.camera_target = model.Object()
        elif world_type == SIMPLE_WORLD:
            if __debug__: print 'Engine: World(self.map.rect)'
            self.world = model.World(self.map.rect)
            if camera_target is None:
                if __debug__: print 'Engine: making camera target Object()'
                self.camera_target = model.Object()
        elif world_type == PYMUNK_WORLD:
            if __debug__: print 'Engine: WorldPymunk(self.map.rect)'
            self.world = model.WorldPymunk(self.map.rect)
            if camera_target is None:
                if __debug__: print 'Engine: making camera target CircleBody()'
                self.camera_target = model.CircleBody()
            self.world.add(self.camera_target)
        
        ## Create the camera.
        if any((self.camera_target, camera_view, camera_view_rect)):
            if camera_view:
                if __debug__: print 'Engine: using pre-made camera view'
            else:
                if camera_view_rect:
                    if __debug__: print 'Engine: making camera view from rect'
                    camera_view = View((self.screen or State.screen).surface, camera_view_rect)
                else:
                    if __debug__: print 'Engine: making camera view from screen'
                    camera_view = self.screen
            if __debug__: print 'Engine: making camera'
            self.camera = Camera(self.camera_target, camera_view)
        else:
            if __debug__: print 'Engine: SKIPPING camera creation: no camera target, view, or view rect'
        
        ## Clock setup. Use pygame.time.get_ticks unless in Windows.
        if sys.platform in('win32','cygwin'):
            if __debug__: print 'Engine: using time.clock for Windows platform'
            time_source = None
        else:
            if __debug__: print 'Engine: using pygame.time.get_ticks for non-Windows platform'
            time_source = lambda:pygame.time.get_ticks()/1000.
        ## Create the clock, specifying callbacks for update() and draw().
        if __debug__: print 'Engine: creating GameClock'
        self.clock = GameClock(
            update_speed, frame_speed,
            update_callback=self.update, frame_callback=self.draw,
            time_source=time_source)
        
        ## Default schedules.
        if __debug__: print 'Engine: scheduling _get_events at priority -2.0'
        self.clock.schedule_update_priority(self._get_events, -2.0)
        if default_schedules:
            if __debug__: print 'Engine: scheduling default items'
            self.schedule_default()
        
        ## Init joysticks.
        if not pygame.joystick.get_init():
            if __debug__: print 'Engine: initializing joysticks'
            self._joysticks = pygame_utils.init_joystick()
        self._get_pygame_events = pygame.event.get
        
        ## Initialize State.
        if set_state:
            if __debug__: print 'Engine: copying my objects to State'
            self.set_state()
    
    def enter(self):
        """Called when the context is entered.
        
        If you override this, make sure you call the super.
        """
        self.set_state()
    
    def resume(self):
        """Called when the context is resumed.
        
        If you override this, make sure you call the super.
        """
        self.set_state()
    
    def set_state(self):
        if self.world_type is not None:
            State.world_type = self.world_type
        if self.screen is not None:
            State.screen = self.screen
        if self.caption is not None:
            pygame.display.set_caption(self.caption)
        if self.map is not None:
            State.map = self.map
        if self.world is not None:
            State.world = self.world
        if self.camera is not None:
            State.camera = self.camera
        if self.camera_target is not None:
            State.camera_target = self.camera_target
        if self.clock is not None:
            State.clock = self.clock
    
    def schedule_default(self):
        """Schedule default items.
        
        Note: These are not tracked. If you intend to manually replace
        State.world or State.camera after constructing the Engine object,
        you'll likely want to unschedule some or all of these and manage the
        schedules yourself. If you replace the objects without unscheduling
        their callbacks, the lost references will result in memory and CPU
        leaks.
        """
        if self.world and isinstance(self.world, model.WorldPymunk):
            self.clock.schedule_update_priority(self.world.step, -1.0)
        if self.camera:
            self.clock.schedule_update_priority(self.camera.update, 1.0)
            self.clock.schedule_frame_priority(self.camera.interpolate, -1.0)
    
    def unschedule_default(self):
        """Unschedule default items.
        """
        if self.world:
            self.clock.unschedule(self.world.step)
        if self.camera:
            self.clock.unschedule(self.camera.update)
            self.clock.unschedule(self.camera.interpolate)
    
    def update(self, dt):
        """Override this method. Called by run() when the clock signals an
        update cycle is ready.
        
        Suggestion:
            move_camera()
            State.camera.update()
            ... custom update the rest of the game ...
        """
        pass
    
    def draw(self, dt):
        """Override this method. Called by run() when the clock signals a
        frame cycle is ready.
        
        Suggestion:
            State.screen.clear()
            State.camera.interpolate()
            ... custom draw the screen ...
            State.screen.flip()
        """
        pass
    
    @property
    def joysticks(self):
        """List of initialized joysticks.
        """
        return list(self._joysticks)
    
    def _get_events(self, dt):
        """Get events and call the handler. Called automatically by run() each
        time the clock indicates an update cycle is ready.
        """
        for e in self._get_pygame_events():
            typ = e.type
            if typ == KEYDOWN:
                self.on_key_down(e.unicode, e.key, e.mod)
            elif typ == KEYUP:
                self.on_key_up(e.key, e.mod)
            elif typ == MOUSEMOTION:
                self.on_mouse_motion(e.pos, e.rel, e.buttons)
            elif typ == MOUSEBUTTONUP:
                self.on_mouse_button_up(e.pos, e.button)
            elif typ == MOUSEBUTTONDOWN:
                self.on_mouse_button_down(e.pos, e.button)
            elif typ == JOYAXISMOTION:
                self.on_joy_axis_motion(e.joy, e.axis, e.value)
            elif typ == JOYBALLMOTION:
                self.on_joy_ball_motion(e.joy, e.ball, e.rel)
            elif typ == JOYHATMOTION:
                self.on_joy_hat_motion(e.joy, e.hat, e.value)
            elif typ == JOYBUTTONUP:
                self.on_joy_button_up(e.joy, e.button)
            elif typ == JOYBUTTONDOWN:
                self.on_joy_button_down(e.joy, e.button)
            elif typ == VIDEORESIZE:
                self.on_video_resize(e.size, e.w, e.h)
            elif typ == VIDEOEXPOSE:
                self.on_video_expose()
            elif typ == USEREVENT:
                    self.on_user_event(e)
            elif typ == QUIT:
                    self.on_quit()
            elif typ == ACTIVEEVENT:
                self.on_active_event(e.gain, e.state)
    
    ## Override an event handler to get specific events.
    def on_active_event(self, gain, state): pass
    def on_joy_axis_motion(self, joy, axis, value): pass
    def on_joy_ball_motion(self, joy, ball, rel): pass
    def on_joy_button_down(self, joy, button): pass
    def on_joy_button_up(self, joy, button): pass
    def on_joy_hat_motion(self, joy, hat, value): pass
    def on_key_down(self, unicode, key, mod): pass
    def on_key_up(self, key, mod): pass
    def on_mouse_button_down(self, pos, button): pass
    def on_mouse_button_up(self, pos, button): pass
    def on_mouse_motion(self, pos, rel, buttons): pass
    def on_quit(self): pass
    def on_user_event(self, e): pass
    def on_video_expose(self): pass
    def on_video_resize(self, size, w, h): pass