Пример #1
0
class RenderingLayer(common.Loggable, serialize.Serializable):
    """A layer on which to render things
    
    This is the abstract version of the layer. Create
    subclasses of this to do useful things.
    
    """

    my_properties = (
        serialize.S('name', '', 'the name of the layer'),
        serialize.I('order', 0, 'the order to render (0=low)'),
        serialize.B('active', True, 'whether this layer is active'),
    )

    def __init__(self, name, order):
        """Initialise the Layer"""
        super(RenderingLayer, self).__init__()
        self.name = name
        self.order = order
        self.surface = None
        self.active = True

    def setSurface(self, surface):
        """Set our surface"""
        self.surface = surface

    def getSurface(self):
        """Return the surface"""
        return self.surface

    def initSurface(self, renderer):
        """Create the surface that we need to draw on"""
        raise NotImplementedError

    def getNiceName(self):
        """Return the nice name for this layer"""
        return '<Layer %d: %s - order %d>' % (id(self), self.name, self.order)

    ### Serializing ###

    def init(self):
        """Initialise from serialized state"""

    ### Rendering ###

    def clearSurface(self):
        """Clear our surface"""
        raise NotImplementedError

    def render(self, surface):
        """Render to a surface"""
        raise NotImplementedError
Пример #2
0
class GeneralStore(serialize.Serializable):
    """Stores things"""
    
    my_properties = (
        serialize.S('base_path', '', 'the base location to find files'),
        serialize.L('raw_items', [], 'the items we have registered'),
    )
    
    def __init__(self):
        """Initialize the store"""
        self.items = {}
        self.raw_items = []
        self.base_path = ''

    def init(self):
        """Initialise from serialized form"""
        self.items = {}
        old_items, self.raw_items = self.raw_items, []
        for item in old_items:
            self.registerItem(*item)
        
    def setPath(self, path):
        """Set our base path to locate images"""
        if not os.path.isdir(path):
            raise BadPath('The path %s is not a directory' % path)
        self.base_path = path
        
    def _resolveFilename(self, name):
        """Return the name to a file"""
        if os.path.isfile(name):
            return name
        else:
            return os.path.join(self.base_path, name)
        
    def registerItem(self, name, *args, **kw):
        """Register an item"""
        #
        # Make sure we only register once
        if name in self.items:
            raise DuplicateItem('The item named "%s" is already registered' % name)
        #
        return self._registerItem(name, *args, **kw)
        
    def getItems(self):
        """Return all the items"""
        return self.items.values()

    def getItemDefinitions(self):
        """Return all the item definitions"""
        return self.raw_items
    
    def clearItems(self):
        """Clear all the items"""
        self.items = {}
        self.raw_items = []
        
    def removeItem(self, name):
        """Remove the named item"""
        try:
            del(self.items[name])
        except KeyError:
            raise UnknownItem('The item "%s" was not in the collection' % name)
        self.raw_items = [item for item in self.raw_items if item[0] != name]
        
    def getNames(self):
        """Return the names of all the items"""
        return self.items.keys()

    def getItem(self, name):
        """Return an item"""
        try:
            return self.items[name]
        except KeyError:
            raise UnknownItem('The item called "%s" could not be found' % name)
Пример #3
0
class Engine(common.Loggable, serialize.Serializable, common.EventAware):
    """The main Serge engine
    
    The engine manages a set of worlds and allows
    a single :doc:`world`, the current world, to be automatically
    updated on a certain time frequency.
    
    """
    
    my_properties = (
        serialize.L('_worlds', [], 'the worlds in this engine'),
        serialize.O('renderer', None, 'the renderer for this engine'),
        serialize.O('sprites', None, 'the sprite registry'),
        serialize.S('_current_world_name', '', 'the name of the current world'),
        serialize.L('_recent_worlds', [], 'the list of worlds recently visited'),
        serialize.B('fullscreen', False, 'whether to display in full screen or not'),
    )
    
    def __init__(self, width=640, height=480, title='Serge', backcolour=(0,0,0), icon=None, fullscreen=False):
        """Initialise the engine
        
        :param width: width of the screen
        :param height: height of the screen
        
        """
        self.title = title
        self.fullscreen = fullscreen
        self.addLogger()
        self.initEvents()
        self.log.info('Starting serge engine (v%s)' % common.version)
        SetCurrentEngine(self)
        super(Engine, self).__init__()
        self.clearWorlds()
        self.renderer = render.Renderer(width, height, title, backcolour, icon, fullscreen)
        self.sprites = visual.Register
        self._stop_requested = False
        self._current_world_name = ''
        self._builder = None
        self._keyboard = input.Keyboard()
        self._mouse = input.Mouse(self)
        self._stats = EngineStats()
        self._recent_worlds = []
                    
    def init(self):
        """Initialise ourself"""
        self.addLogger()
        self.log.info('Initializing serge engine (v%s)' % common.version)
        SetCurrentEngine(self)
        #
        # Prepare all the worlds
        for world in self._worlds.values():
            world.init()
        self._current_world = None
        self._snapshots_enabled = True
        self._snapshot_count = 0
        #
        # Recover the sprite registry from our own
        self.sprites.init()
        visual.Register = self.sprites
        self.setCurrentWorldByName(self._current_world_name)
        #
        self.renderer.init()
        #
        self._builder = None
        self._keyboard = input.Keyboard()
        self._mouse = input.Mouse(self)
            
    def addWorld(self, world):
        """Add a world to the engine
        
        :param world: the world instance to add
        
        """
        if world.name in self._worlds:
            raise DuplicateWorld('A world named "%s" already exists' % world.name)
        if world in self._worlds.values():
            raise DuplicateWorld('This world (named "%s") already exists' % world.name)
        self._worlds[world.name] = world
        world.setEngine(self)
        
    def removeWorld(self, world):
        """Remove a world from the engine
        
        :param world: the world instance to remove

        """
        self.removeWorldNamed(world.name)
        
    def removeWorldNamed(self, name):
        """Remove a world with a given name
        
        :param name: the name of the world to remove

        """
        try:
            del(self._worlds[name])
        except KeyError:
            raise WorldNotFound('No world named "%s" in the worlds collection' % name)

    def clearWorlds(self):
        """Clear all the worlds"""
        self._worlds = {}
        self._current_world = None
        
    def getWorld(self, name):
        """Return the named world
        
        :param name: the name of the world to return

        """
        try:
            return self._worlds[name]
        except KeyError:
            raise WorldNotFound('No world named "%s" in the worlds collection' % name)

    def getWorlds(self):
        """Return all the worlds"""
        return self._worlds.values()

    def getCurrentWorld(self):
        """Return the currently selected world"""
        if self._current_world:
            return self._current_world
        else:
            raise NoCurrentWorld('There is no current world')
        
    def setCurrentWorld(self, world):
        """Set the current world
        
        :param world: the world to set as the current world

        """
        self.setCurrentWorldByName(world.name)
        
    def setCurrentWorldByName(self, name):
        """Set the current world to the one with the given name
        
        :param name: the name of the world to set as the current world

        """
        self.log.info('Setting current world to %s' % name)
        if self._current_world_name:
            self._recent_worlds.append(self._current_world_name)
        new_world = self.getWorld(name)
        #
        # Send activation and deactivation callbacks to worlds to allow them to do
        # any housekeeping
        if new_world != self._current_world:
            if self._current_world:
                self._current_world.deactivateWorld()
            new_world.activateWorld()
        #
        self._current_world = new_world
        self._current_world_name = name
        return new_world

    def goBackToPreviousWorld(self, obj=None, arg=None):
        """Return to the world we were in before this one
        
        The arguments are never used and are just here to allow you to use
        this method as an event callback.
        
        """
        try:
            name = self._recent_worlds.pop()
        except IndexError:
            raise WorldNotFound('There are no previous worlds')
        self.setCurrentWorldByName(name)
        # We will have pushed the current world onto the stack, which we don't want
        # so take it off again
        self._recent_worlds.pop()
        
    def updateWorld(self, interval):
        """Update the current world"""
        if self._current_world:
            self._current_world.updateWorld(interval)
        else:
            raise NoCurrentWorld('Cannot update when there is no current world')
        
    def run(self, fps, endat=None):
        """Run the updates at the specified frames per second until the optional endtime
        
        :param fps: the target frames per second (integer)
        :param endat: a time to stop the engine at (long), eg time.time()+60 to run for a minute
        
        """
        self.log.info('Engine starting (requested fps=%d)' % fps)
        clock = pygame.time.Clock()
        self._stop_requested = False
        while True:
            #
            # Watch for ending conditions
            if self._stop_requested or (endat and time.time() >= endat):
                break
            #
            # Main render activity
            try:
                #
                # Pause
                clock.tick(fps)
                #
                # Do the update for our actors
                interval = clock.get_time()
                if self._current_world:
                    self.updateWorld(interval)
                #
                # Do builder work if needed
                if self._builder:
                    self._builder.updateBuilder(interval)
                #
                # Events that may have happened
                self._handleEvents()
                # Inputs
                self._mouse.update(interval)
                self._keyboard.update(interval)
                pygame.event.clear()
                # Sound
                sound.Music.update(interval)
                sound.Sounds.update(interval)                
                if self._current_world:
                    self.processEvents()
                #
                # Get ready to render
                self._stats.beforeRender()
                self.renderer.preRender()
                #
                # Render the active world
                if self._current_world:
                    self._current_world.renderTo(self.renderer, interval)
                #
                # Render the builder if needed
                if self._builder:
                    self._builder.renderTo(self.renderer, interval)
                #
                # And render all of our layers
                self.renderer.render()
                self.processEvent((events.E_AFTER_RENDER, self))
                self._stats.afterRender()
                #
                # Show the screen
                pygame.display.flip()
                self._stats.recordFrame()
                #
            except NotImplementedError, err:
                self.log.error('Failed in main loop: %s' % err)
        #
        self.log.info('Engine stopping')
        self.processEvent((events.E_AFTER_STOP, self))
        self.log.info('Engine info: %s' % (self._stats,))
Пример #4
0
class RenderingLayer(common.Loggable, serialize.Serializable, common.EventAware):
    """A layer on which to render things
    
    This is the abstract version of the layer. Create
    subclasses of this to do useful things.
    
    """
    
    my_properties = (
        serialize.S('name', '', 'the name of the layer'),
        serialize.I('order', 0, 'the order to render (0=low)'),
        serialize.B('active', True, 'whether this layer is active'),
        serialize.B('static', False, 'whether this layer is static with respect to the camera'),
    )
    
    def __init__(self, name, order):
        """Initialise the Layer"""
        super(RenderingLayer, self).__init__()
        self.initEvents()
        self.name = name
        self.order = order
        self.surface = None
        self.active = True
        self.static = False

    def setSurface(self, surface):
        """Set our surface"""
        self.surface = surface

    def getSurface(self):
        """Return the surface"""
        return self.surface  

    def initSurface(self, renderer):
        """Create the surface that we need to draw on"""
        raise NotImplementedError

    def getNiceName(self):
        """Return the nice name for this layer"""
        return '<Layer %d: %s - order %d>' % (id(self), self.name, self.order)
    
    def setStatic(self, static):
        """Determine whether this layer is static with respect to camera movements or not"""
        self.static = static
        
    ### Serializing ###
    
    def init(self):
        """Initialise from serialized state"""
        self.initEvents()
        
    ### Rendering ###
          
    def clearSurface(self):
        """Clear our surface"""
        raise NotImplementedError
    
    def preRender(self):
        """Called before the layer has anything rendered to"""
        self.processEvent((events.E_BEFORE_RENDER, self))
        
    def render(self, surface):
        """Render to a surface"""
        raise NotImplementedError
        
    def postRender(self):
        """Called after the layer has has had everything rendered on it"""
        self.processEvent((events.E_AFTER_RENDER, self))
Пример #5
0
class Renderer(common.Loggable, serialize.Serializable, common.EventAware):
    """The main rendering component"""
    
    my_properties = (
        serialize.L('layers', [], 'the layers we render to'),
        serialize.I('width', 640, 'the width of the screen'),
        serialize.I('height', 480, 'the height of the screen'),
        serialize.S('title', 'Serge', 'the title of the main window'),
        serialize.L('backcolour', (0,0,0), 'the background colour'),
        serialize.O('camera', None, 'the camera for this renderer'),
        serialize.O('icon', None, 'the icon for the main window'),
        serialize.B('fullscreen', False, 'whether to display in full screen or not'),
    )
    
    def __init__(self, width=640, height=480, title='Serge', backcolour=(0,0,0), icon=None, fullscreen=False):
        """Initialise the Renderer"""
        self.addLogger()
        self.initEvents()
        self.width = width
        self.height = height
        self.title = title
        self.layers = []
        self.backcolour = backcolour
        self.fullscreen = fullscreen
        self.camera = camera.Camera()
        self.camera.setSpatial(0, 0, self.width, self.height)
        self.icon = icon
        self.init()
            
    ### Serializing ###
    
    def init(self):
        """Initialise from serialized state"""
        self.addLogger()
        self.initEvents()
        self._sort_needed = False
        pygame.display.set_caption(self.title)
        # 
        # Tried the following with flags but no impact pygame.FULLSCREEN|pygame.HWSURFACE|pygame.DOUBLEBUF
        flags = pygame.FULLSCREEN if self.fullscreen else 0
        self.surface = pygame.display.set_mode((self.width, self.height), flags )
        for layer in self.layers:
            layer.setSurface(pygame.Surface((self.width, self.height), pygame.SRCALPHA, 32))
            layer.init()
        self.camera.init()
        self.camera.resizeTo(self.width, self.height)
        if self.icon:
            pygame.display.set_icon(visual.Register.getItem(self.icon).raw_image)
                
    ### Layers ###
    
    def addLayer(self, layer):
        """Add a layer to the rendering"""
        self.log.info('Adding layer "%s" at %d' % (layer.name, layer.order))
        if layer in self.layers:
            raise DuplicateLayer('The layer %s is already in the renderer' % layer)
        else:
            self.layers.append(layer)
        self._sort_needed = True
        self.resetSurfaces()
        return layer

    def getLayer(self, name):
        """Return the named layer"""
        for layer in self.layers:
            if layer.name == name:
                return layer
        else:
            raise UnknownLayer('No layer with name "%s" was found' % (name,))

    def getLayerBefore(self, layer):
        """Return the layer before the specified one in terms of rendering order"""
        for test_layer in reversed(self.getLayers()):
            if test_layer.order < layer.order:
                return test_layer
        else:
            raise NoLayer('There is no layer before %s' % layer.getNiceName())
        
    def resetSurfaces(self):
        """Recreate the surfaces for our layers
        
        When layers are added we sometimes need to reset the layers,
        for instance, virtual layers need to be shifted around so
        that they have the right order.
        
        """
        self._sortLayers()
        for layer in self.getLayers():
            layer.initSurface(self)

    def getLayers(self):
        """Return all the layers"""
        return self.layers
        
    def removeLayer(self, layer):
        """Remove the layer from the rendering"""
        try:
            self.layers.remove(layer)
        except ValueError:
            raise UnknownLayer('The layer %s was not found' % layer.getNiceName())

    def removeLayerNamed(self, name):
        """Remove the layer with the specific name"""
        layer = self.getLayer(name)
        self.removeLayer(layer)
        
    def clearLayers(self):
        """Clear all the layers"""
        self.layers = []
        
    def _sortLayers(self):
        """Sort the layers into the right order"""
        self.layers.sort(lambda l1, l2 : cmp(l1.order, l2.order))
        self._sort_needed = False

    def orderActors(self, actors):
        """Return the list of actors sorted by who should be processed first to correctly render
        
        The actors are checked to see which layer they reside on and then
        this is used to order the returned list.
        
        """
        #
        # Make a lookup table to quickly find layers
        layers = dict([(layer.name, layer.order) for layer in self.getLayers()])
        actor_list = [(layers.get(actor.getLayerName(), 0), actor) for actor in actors]
        actor_list.sort()
        #
        return [actor for _, actor in actor_list]

    ### Rendering ###

    def clearSurface(self):
        """Clear the surface"""
        self.surface.fill(self.backcolour)

    def preRender(self):
        """Prepare for new rendering"""
        self.clearSurface()
        for layer in self.getLayers():
            if layer.active:
                layer.clearSurface()
                layer.preRender()
                
    def render(self):
        """Render all the layers"""
        #
        # Post rendering events
        for layer in self.layers:
            if layer.active:
                layer.postRender()
        #
        # Put layers in the right order
        if self._sort_needed:
            self._sortLayers()
        #
        # Render all layers
        for layer in self.layers:
            if layer.active:
                layer.render(self.surface)
        #
        self.processEvent((events.E_AFTER_RENDER, self))            

    def getSurface(self):
        """Return the overall surface"""
        return self.surface  
    
    ### Camera stuff ###
    
    def setCamera(self, camera):
        """Set our camera"""
        self.camera = camera    
        
    def getCamera(self):
        """Return our camera"""
        return self.camera  

    def getScreenSize(self):
        """Returns the screen size"""
        return (self.width, self.height)
Пример #6
0
class Actor(common.Loggable, geometry.Rectangle, common.EventAware):
    """Represents an actor"""
    
    my_properties = (
        serialize.S('tag', 'actor', 'the actor\'s tag'),
        serialize.S('name', '', 'the actor\'s name'),
        serialize.B('active', True, 'whether the actor is active'),
        serialize.B('visible', True, 'whether the actor is visible'),
        serialize.S('sprite', '', 'the name of our sprite'),
        serialize.S('layer', '', 'the name of the layer we render to'),
        serialize.I('rendering_order', 0, 'the order to use for rendering to the screen (lower is earlier)'),
        serialize.O('physical_conditions', '', 'the physical conditions for this object'),
        serialize.F('angle', 0.0, 'the angle for the actor'),
        serialize.O('lock', None, 'a lock object you can place to prevent an actor moving'),
    )
    
    def __init__(self, tag, name=''):
        """Initialise the actor"""
        self.addLogger()
        self.initEvents()
        super(Actor, self).__init__()
        # Whether we respond to updates or not
        self.active = True
        self.visible = True   
        # Class based tag to locate the actor by
        self.tag = tag
        # Unique name to locate by
        self.name = name
        # Our sprite
        self.sprite = ''
        self._visual = None
        # The layer we render to
        self.layer = ''
        # The order to render to the screen in (lower is earlier)
        self.rendering_order = 0
        # Our zoom factor
        self.zoom = 1.0
        # Physics parameters - None means no physics
        self.physical_conditions = None
        # Angle
        self.angle = 0.0
        # Properties to lock an actor so it cannot be moved
        self.lock = None
        
    def init(self):
        """Initialize from serialized form"""
        self.addLogger()
        self.initEvents()
        self.log.info('Initializing actor %s:%s:%s' % (self, self.tag, self.name))
        super(Actor, self).init()
        if self.sprite:
            self.setSpriteName(self.sprite)
        else:
            self._visual = None
        self.setLayerName(self.layer)
        self.zoom = 1.0

    def getNiceName(self):
        """Return a nice name for this actor"""
        if self.name:
            name_part = '%s (%s)' % (self.name, self.tag)
        else:
            name_part = self.tag
        return '%s [%s] <%s>' % (self.__class__.__name__, name_part, hex(id(self)))
        
    def setSpriteName(self, name):
        """Set the sprite for this actor"""
        if name != self.sprite:
            self.visual = visual.Register.getItem(name).getCopy()
            #
            # Make sure to re-apply the zoom
            if self.zoom != 1.0:
                self.visual.scaleBy(self.zoom)
            self.sprite = name
        
    @property
    def visual(self): return self._visual
    @visual.setter
    def visual(self, value):
        """Set the visual item for this actor"""
        self._visual = value
        self._resetVisual()
        
    def _resetVisual(self):
        """Reset the visual item on the center point"""
        #
        # Adjust our location so that we are positioned and sized appropriately
        cx, cy, _, _ = self.getSpatialCentered()
        self.setSpatialCentered(cx, cy, self._visual.width, self._visual.height)
        #
        # Here is a hack - sometimes the visual width changes and we want to update our width
        # so we let the visual know about us so it can update our width. This is almost 
        # certainly the wrong thing to do, but we have some tests in there so hopefully
        # the right thing becomes obvious later!
        self._visual._actor_parent = self
        
    def getSpriteName(self):
        """Return our sprite"""
        return self.sprite
        
    def setLayerName(self, name):
        """Set the layer that we render to"""
        self.layer = name
    
    def getLayerName(self):
        """Return our layer name"""
        return self.layer

    def setRenderingOrder(self, order):
        """Sets the order to render to the screen

        You can set this to adjust when objects are rendered even within
        a layer. A lower number means that an actor will be rendered
        earlier - which means it will be behind others.

        The default is 0, so you should set this to higher than 1000
        if you want an actor to appear in front of other objects
        that have not been explicitly set.

        """
        self.rendering_order = order

    def getRenderingOrder(self):
        """Return the rendering order"""
        return self.rendering_order

    def renderTo(self, renderer, interval):
        """Render this actor to the given renderer"""
        if self._visual and self.layer:       
            layer = renderer.getLayer(self.layer)
            camera = renderer.camera
            if layer.static:
                coords = self.getOrigin()
            elif camera.canSee(self):
                coords = camera.getRelativeLocation(self)
            else: 
                return # Cannot see me
            self._visual.renderTo(interval, layer.getSurface(), coords)
    
    def updateActor(self, interval, world):
        """Update the actor status"""

    def removedFromWorld(self, world):
        """Called when we are being removed from the world"""
        self.processEvent((events.E_REMOVED_FROM_WORLD, self))

    def addedToWorld(self, world):
        """Called when we are being added to the world"""
        self.processEvent((events.E_ADDED_TO_WORLD, self))
        
    def setZoom(self, zoom):
        """Zoom in on this actor"""
        if self._visual:
            self._visual.scaleBy(zoom/self.zoom)
            self.setSpatialCentered(self.x, self.y, self._visual.width, self._visual.height)
        self.zoom = zoom

    def setAngle(self, angle, sync_physical=False, override_lock=False):
        """Set the angle for the visual"""
        if self.lock and not override_lock:
            raise PositionLocked('Cannot rotate: %s' % self.lock.reason)
        if self._visual:
            self._visual.setAngle(angle)
            self._resetVisual()
        if sync_physical and self.physical_conditions:
            self.physical_conditions.body.angle = math.radians(-angle)
        self.angle = angle
        
    def getAngle(self):
        """Return the angle for the actor"""
        return self.angle
    

    ### Physics ###
    
    def setPhysical(self, physical_conditions):
        """Set the physical conditions"""
        #
        # Watch for if this object already has a shape
        if self.physical_conditions and self.physical_conditions.body:
            self.physical_conditions.updateFrom(physical_conditions)
        else:
            #
            # Check if we should be using the size of the visual element
            if physical_conditions.visual_size:
                if self.visual is None:
                    raise physical.InvalidDimensions(
                        'No visual element set for actor %s but visual_size requested' % self.getNiceName())
                else:
                    if physical_conditions.visual_size == geometry.CIRCLE:
                        # Circle
                        physical_conditions.setGeometry(radius=self.visual.radius)
                    elif physical_conditions.visual_size == geometry.RECTANGLE:
                        # Rectangle
                        physical_conditions.setGeometry(width=self.visual.width, height=self.visual.height)
                    else:
                        raise physical.InvalidDimensions('Visual size setting (%s) is not recognized' % physical_conditions.visual_size)
            #
            self.physical_conditions = physical_conditions
        
    def getPhysical(self):
        """Return the physical conditions"""
        return self.physical_conditions

    def syncPhysics(self, spatial_only=False):
        """Sync physics when the actors physical properties have been changed"""
        if self.physical_conditions:
            #self.log.debug('Syncing physics for %s to %s, %s' % (self.getNiceName(), self.x, self.y))
            self.physical_conditions.body.position = self.x, self.y
            if not spatial_only:
                self.physical_conditions.body.velocity = self.physical_conditions.velocity

    # Remap x, y properties to allow syncing with the physics engine

    def move(self, x, y):
        """Move by a certain amount"""
        super(Actor, self).move(x, y)
        self.syncPhysics(spatial_only=True)
        

    def moveTo(self, x, y, no_sync=False, override_lock=False):
        """Move the center of this actor to the given location, unless it is locked
        
        You can override the lock by passing True to override lock.
        
        """
        if self.lock and not override_lock:
            raise PositionLocked('The actor is locked in place: %s' % self.lock.reason)
        else:
            super(Actor, self).moveTo(x, y, override_lock=override_lock)
            if not no_sync:
                self.syncPhysics(spatial_only=True)
Пример #7
0
class World(common.Loggable, serialize.Serializable, common.EventAware):
    """The main world object
    
    The :doc:`engine` will control main worlds. Each world has a number
    of :doc:`zone` which contain :doc:`actor`.
    
    """

    my_properties = (
        serialize.S('name', '', 'the name of this world'),
        serialize.L('zones', set(), 'the zones in this world'),
        serialize.L('unzoned_actors', set(),
                    'the actors not in any zone in this world'),
    )

    def __init__(self, name):
        """Initialise the World"""
        self.addLogger()
        self.initEvents()
        self.name = name
        self.engine = None
        self.zones = set()
        self.unzoned_actors = set(
        )  # Actors get put here if then end up in no zone
        self.event_handlers = {}
        self.init()

    ### Serializing ###

    def init(self):
        """Initialise from serialized state"""
        self.addLogger()
        self.initEvents()
        self.log.info('Initializing world %s' % self.name)
        super(World, self).__init__()
        self.engine = None
        #
        # This list is used to order the processing of actors in rendering. The
        # flag is used to tell us when we need to resort them
        self._sorted_actors = []
        self._actors_need_resorting = False
        self._scheduled_deletions = set()
        #
        # Now process actors
        for zone in self.zones:
            zone.init()
        for actor in self.unzoned_actors:
            actor.init()

    ### Zones ###

    def addZone(self, zone):
        """Add a zone to the world"""
        if zone in self.zones:
            raise DuplicateZone('The zone %s is already in the world' % zone)
        else:
            self.zones.add(zone)
        self._actors_need_resorting = True

    def clearZones(self):
        """Remove all the zones"""
        self.zones = set()

    ### Main ###

    def updateWorld(self, interval):
        """Update the objects in the world"""
        for zone in self.zones:
            if zone.active:
                zone.updateZone(interval, self)
        #
        # Process any scheduled actor deletions
        while self._scheduled_deletions:
            try:
                self.removeActor(self._scheduled_deletions.pop())
            except UnknownActor:
                # Ok, the actor must have been removed directly
                pass

    def setEngine(self, engine):
        """Set the engine that we are owned by"""
        self.engine = engine

    def getEngine(self):
        """Return the engine that we are owned by"""
        return self.engine

    def findActorsByTag(self, tag):
        """Return all the actors in all zones based on the tag"""
        results = actor.ActorCollection()
        for z in self.zones:
            results.extend(z.findActorsByTag(tag))
        return results

    def findActorByName(self, name):
        """Return the actor with the give name in all zones"""
        for z in self.zones:
            try:
                return z.findActorByName(name)
            except zone.ActorNotFound:
                pass
        else:
            raise zone.ActorNotFound(
                'Unable to find actor named "%s" in any zone' % name)

    def findActorsAt(self, x, y):
        """Return the actors at a certain location"""
        actors = actor.ActorCollection()
        test = geometry.Point(x, y)
        for the_actor in self.getActors():
            if test.isInside(the_actor):
                actors.append(the_actor)
        return actors

    def getActors(self):
        """Return all the actors"""
        actors = actor.ActorCollection(self.unzoned_actors)
        for z in self.zones:
            actors.extend(z.getActors())
        return actors

    def rezoneActors(self):
        """Move actors to the right zone based on their spatial location"""
        #
        # Start with a list of actors to find homes for based on any that
        # were not in any zones at all
        moved = self.unzoned_actors
        self.unzoned_actors = set()
        #
        # Find all the actors that are no longer in the right zone
        # and remove them from their current zone
        for z in self.zones:
            for actor in z.actors.copy():
                if not actor.isOverlapping(z):
                    z.removeActor(actor)
                    moved.add(actor)
        #
        # Now find the place for the moved actors
        for actor in moved:
            self.addActor(actor)

    def clearActors(self):
        """Clear all the actors"""
        self.clearActorsExceptTags([])

    def clearActorsExceptTags(self, tags):
        """Clear all actors except the ones with a tag in the list of tags"""
        for actor in self.getActors():
            if actor.tag not in tags:
                try:
                    self.removeActor(actor)
                except UnknownActor:
                    # Can be called if a composite actor removes their own children
                    pass
        for actor in list(self.unzoned_actors):
            if actor.tag not in tags:
                self.unzoned_actors.remove(actor)

    def clearActorsWithTags(self, tags):
        """Clear all actors with a tag in the list of tags"""
        for actor in self.getActors():
            if actor.tag in tags:
                try:
                    self.removeActor(actor)
                except UnknownActor:
                    # Can be called if a composite actor removes their own children
                    pass
        for actor in list(self.unzoned_actors):
            if actor.tag not in tags:
                self.unzoned_actors.remove(actor)

    def addActor(self, actor):
        """Add an actor to the world"""
        #
        self.log.debug('Adding %s to world %s' %
                       (actor.getNiceName(), self.name))
        #
        # Make sure the actor isn't already here
        if self.hasActor(actor):
            raise DuplicateActor('The actor %s is already in the world' %
                                 actor.getNiceName())
        #
        # Try to put the actor in the right zone
        for z in self.zones:
            if z.wouldContain(actor):
                z.addActor(actor)
                break
        else:
            # The actor is not in any zones, store for later
            self.unzoned_actors.add(actor)
        #
        # Tell the actor about it
        actor.addedToWorld(self)
        #
        self._actors_need_resorting = True
        #
        return actor

    def removeActor(self, actor):
        """Remove the actor from the world"""
        self.log.debug('Removing "%s" actor (%s)' %
                       (actor.tag, actor.getNiceName()))
        #
        self._actors_need_resorting = True
        #
        # Try to remove from zones
        for z in self.zones:
            if z.hasActor(actor):
                z.removeActor(actor)
                break
        else:
            #
            # We didn't find it in the zone - maybe in the unzoned
            if actor in self.unzoned_actors:
                self.unzoned_actors.remove(actor)
            else:
                raise UnknownActor('The actor %s was not found in the world' %
                                   actor)
        #
        # Tell the actor about it
        actor.removedFromWorld(self)

    def scheduleActorRemoval(self, actor):
        """Remove an actor at the end of the next update for the world
        
        This method can be used to safely remove an actor from the world
        during the execution of the world update. It can sometimes be
        useful to do this when inside logic that is iterating over actors
        or inside the updateWorld event loop.
        
        """
        self._scheduled_deletions.add(actor)

    def hasActor(self, actor):
        """Return True if this actor is in the world"""
        #
        # Try to remove from zones
        for z in self.zones:
            if z.hasActor(actor):
                return True
        #
        # We didn't find it in the zone - maybe in the un-zoned
        return actor in self.unzoned_actors

    def requestResortActors(self):
        """Request that actors are resorted the next time we render

        Call this if you have adjusted the rendering order of actors

        """
        self._actors_need_resorting = True

    def renderTo(self, renderer, interval):
        """Render all of our actors in active zones"""
        #
        # Watch out in case we need to reorder our actors
        if self._actors_need_resorting:
            self.log.debug('Sorting actors now')
            self._sorted_actors = renderer.orderActors(self.getActors())
            self._actors_need_resorting = False
        #
        camera = renderer.getCamera()
        self.processEvent((events.E_BEFORE_RENDER, self))
        #
        # Render all of the actors
        for actor in self._sorted_actors:
            if actor.active and actor.visible:
                profiler.PROFILER.start(actor, 'renderActor')
                try:
                    actor.renderTo(renderer, interval)
                except Exception, err:
                    self.log.error('Failed rendering "%s" actor "%s": %s' %
                                   (actor.tag, actor, err))
                    raise
                profiler.PROFILER.end()
        #
        self.processEvent((events.E_AFTER_RENDER, self))
Пример #8
0
class Engine(common.Loggable, serialize.Serializable):
    """The main Serge engine
    
    The engine essentially manages a set of world and allows
    a single world, the current world, to be automatically
    updated on a certain time frequency.
    
    """

    my_properties = (
        serialize.L('_worlds', [], 'the worlds in this engine'),
        serialize.O('renderer', None, 'the renderer for this engine'),
        serialize.O('sprites', None, 'the sprite registry'),
        serialize.S('_current_world_name', '',
                    'the name of the current world'),
    )

    def __init__(self,
                 width=640,
                 height=480,
                 title='Serge',
                 backcolour=(0, 0, 0),
                 icon=None):
        """Initialise the engine"""
        self.addLogger()
        self.log.info('Starting serge engine (v%s)' % common.version)
        SetCurrentEngine(self)
        super(Engine, self).__init__()
        self.clearWorlds()
        self.renderer = render.Renderer(width, height, title, backcolour, icon)
        self.sprites = visual.Register
        self._stop_requested = False
        self._current_world_name = ''
        self._builder = None
        self._keyboard = input.Keyboard()
        self._mouse = input.Mouse(self)
        self._stats = EngineStats()

    def init(self):
        """Initialise ourself"""
        self.addLogger()
        self.log.info('Initializing serge engine (v%s)' % common.version)
        SetCurrentEngine(self)
        #
        # Prepare all the worlds
        for world in self._worlds.values():
            world.init()
        self._current_world = None
        self._snapshots_enabled = True
        self._snapshot_count = 0
        #
        # Recover the sprite registry from our own
        self.sprites.init()
        visual.Register = self.sprites
        self.setCurrentWorldByName(self._current_world_name)
        #
        self.renderer.init()
        #
        self._builder = None
        self._keyboard = input.Keyboard()
        self._mouse = input.Mouse(self)

    def addWorld(self, world):
        """Add a world to the engine"""
        if world.name in self._worlds:
            raise DuplicateWorld('A world named "%s" already exists' %
                                 world.name)
        if world in self._worlds.values():
            raise DuplicateWorld('This world (named "%s") already exists' %
                                 world.name)
        self._worlds[world.name] = world
        world.setEngine(self)

    def removeWorld(self, world):
        """Remove a world"""
        self.removeWorldNamed(world.name)

    def removeWorldNamed(self, name):
        """Remove a world with a given name"""
        try:
            del (self._worlds[name])
        except KeyError:
            raise WorldNotFound(
                'No world named "%s" in the worlds collection' % name)

    def clearWorlds(self):
        """Clear all the worlds"""
        self._worlds = {}
        self._current_world = None

    def getWorld(self, name):
        """Return the named world"""
        try:
            return self._worlds[name]
        except KeyError:
            raise WorldNotFound(
                'No world named "%s" in the worlds collection' % name)

    def getWorlds(self):
        """Return all the worlds"""
        return self._worlds.values()

    def getCurrentWorld(self):
        """Return the current world"""
        if self._current_world:
            return self._current_world
        else:
            raise NoCurrentWorld('There is no current world')

    def setCurrentWorld(self, world):
        """Set the current world"""
        self.setCurrentWorldByName(world.name)

    def setCurrentWorldByName(self, name):
        """Set the current world to the one with the given name"""
        new_world = self.getWorld(name)
        #
        # Send activation and deactivation callbacks to worlds to allow them to do
        # any housekeeping
        if new_world != self._current_world:
            if self._current_world:
                self._current_world.deactivateWorld()
            new_world.activateWorld()
        #
        self._current_world = new_world
        self._current_world_name = name
        return new_world

    def updateWorld(self, interval):
        """Update the current world"""
        if self._current_world:
            self._current_world.updateWorld(interval)
        else:
            raise NoCurrentWorld(
                'Cannot update when there is no current world')

    def run(self, fps, endat=None):
        """Run the updates at the specified frames per second until the optional endtime"""
        clock = pygame.time.Clock()
        self._stop_requested = False
        while True:
            #
            # Watch for ending conditions
            if self._stop_requested or (endat and time.time() >= endat):
                break
            #
            # Main render activity
            try:
                #
                # Pause
                clock.tick(fps)
                #
                # Do the update for our actors
                interval = clock.get_time()
                if self._current_world:
                    self.updateWorld(interval)
                #
                # Do builder work if needed
                if self._builder:
                    self._builder.updateBuilder(interval)
                #
                # Events that may have happened
                self._handleEvents()
                self._mouse.update(interval)
                self._keyboard.update(interval)
                if self._current_world:
                    self.processEvents()
                #
                # Get ready to render
                self._stats.beforeRender()
                self.renderer.preRender()
                #
                # Render the active world
                if self._current_world:
                    self._current_world.renderTo(self.renderer, interval)
                #
                # Render the builder if needed
                if self._builder:
                    self._builder.renderTo(self.renderer, interval)
                #
                # And render all of our layers
                self.renderer.render()
                self._stats.afterRender()
                #
                # Show the screen
                pygame.display.flip()
                self._stats.recordFrame()
                #
            except NotImplementedError, err:
                self.log.error('Failed in main loop: %s' % err)
        #
        self.log.info('Engine stopping')
        self.log.info('Engine info: %s' % (self._stats, ))
Пример #9
0
class Actor(common.Loggable, geometry.Rectangle, common.EventAware):
    """Represents an actor"""
    
    my_properties = (
        serialize.S('tag', 'actor', 'the actor\'s tag'),
        serialize.S('name', '', 'the actor\'s name'),
        serialize.B('active', True, 'whether the actor is active'),
        serialize.S('sprite', '', 'the name of our sprite'),
        serialize.S('layer', '', 'the name of the layer we render to'),
        serialize.O('physical_conditions', '', 'the physical conditions for this object'),
        serialize.F('angle', 0.0, 'the angle for the actor'),
    )
    
    def __init__(self, tag, name=''):
        """Initialise the actor"""
        self.addLogger()
        self.initEvents()
        super(Actor, self).__init__()
        # Whether we respond to updates or not
        self.active = True        
        # Class based tag to locate the actor by
        self.tag = tag
        # Unique name to locate by
        self.name = name
        # Our sprite
        self.sprite = ''
        self._visual = None
        # The layer we render to
        self.layer = ''    
        # Our zoom factor
        self.zoom = 1.0
        # Physics parameters - None means no physics
        self.physical_conditions = None
        # Angle
        self.angle = 0.0
        
    def init(self):
        """Initialize from serialized form"""
        self.addLogger()
        self.initEvents()
        self.log.info('Initializing actor %s:%s:%s' % (self, self.tag, self.name))
        super(Actor, self).init()
        if self.sprite:
            self.setSpriteName(self.sprite)
        else:
            self._visual = None
        self.setLayerName(self.layer)
        self.zoom = 1.0

    def getNiceName(self):
        """Return a nice name for this actor"""
        if self.name:
            name_part = '%s (%s)' % (self.name, self.tag)
        else:
            name_part = self.tag
        return '%s [%s] <%s>' % (self.__class__.__name__, name_part, id(self))
        
    def setSpriteName(self, name):
        """Set the sprite for this actor"""
        self.visual = visual.Register.getItem(name).getCopy()
        self.sprite = name
        
    @property
    def visual(self): return self._visual
    @visual.setter
    def visual(self, value):
        """Set the visual item for this actor"""
        self._visual = value
        self._resetVisual()
        
    def _resetVisual(self):
        """Reset the visual item on the center point"""
        #
        # Adjust our location so that we are positioned and sized appropriately
        cx, cy, _, _ = self.getSpatialCentered()
        self.setSpatialCentered(cx, cy, self._visual.width, self._visual.height)
        #
        # Here is a hack - sometimes the visual width changes and we want to update our width
        # so we let the visual know about us so it can update our width. This is almost 
        # certainly the wrong thing to do, but we have some tests in there so hopefully
        # the right thing becomes obvious later!
        self._visual._actor_parent = self
        
    def getSpriteName(self):
        """Return our sprite"""
        return self.sprite
        
    def setAsText(self, text_object):
        """Set some text as our visual"""
        self.visual = text_object

    def setText(self, text):
        """Set the actual text"""
        self._visual.setText(text)
        
    def setLayerName(self, name):
        """Set the layer that we render to"""
        self.layer = name
    
    def getLayerName(self):
        """Return our layer name"""
        return self.layer
    
    def renderTo(self, renderer, interval):
        """Render ourself to the given renderer"""
        if self._visual:
            coords = renderer.camera.getRelativeLocation(self)
            if self.layer:
                self._visual.renderTo(interval, renderer.getLayer(self.layer).getSurface(), coords)
    
    def updateActor(self, interval, world):
        """Update the actor status"""

    def removedFromWorld(self, world):
        """Called when we are being removed from the world"""
        self.processEvent((events.E_REMOVED_FROM_WORLD, self))

    def addedToWorld(self, world):
        """Called when we are being added to the world"""
        self.processEvent((events.E_ADDED_TO_WORLD, self))
        
    def setZoom(self, zoom):
        """Zoom in on this actor"""
        if self._visual:
            self._visual.scaleBy(zoom/self.zoom)
        self.zoom = zoom

    def setAngle(self, angle, sync_physical=False):
        """Set the angle for the visual"""
        if self._visual:
            self._visual.setAngle(angle)
            self._resetVisual()
        if sync_physical and self.physical_conditions:
            self.physical_conditions.body.angle = math.radians(-angle)
        self.angle = angle
        
    def getAngle(self):
        """Return the angle for the actor"""
        return self.angle
    

    ### Physics ###
    
    def setPhysical(self, physical_conditions):
        """Set the physical conditions"""
        #
        # Watch for if this object already has a shape
        if self.physical_conditions and self.physical_conditions.body:
            self.physical_conditions.updateFrom(physical_conditions)
        else:
            self.physical_conditions = physical_conditions
        
    def getPhysical(self):
        """Return the physical conditions"""
        return self.physical_conditions

    def syncPhysics(self, spatial_only=False):
        """Sync physics when the actors physical properties have been changed"""
        if self.physical_conditions:
            #self.log.debug('Syncing physics for %s to %s, %s' % (self.getNiceName(), self.x, self.y))
            self.physical_conditions.shape.body.position = self.x, self.y
            if not spatial_only:
                self.physical_conditions.shape.body.velocity = self.physical_conditions.velocity

    # Remap x, y properties to allow syncing with the physics engine

    def move(self, x, y):
        """Move by a certain amount"""
        super(Actor, self).move(x, y)
        self.syncPhysics(spatial_only=True)
        
    def moveTo(self, x, y, no_sync=False):
        """Move to a certain place"""
        super(Actor, self).moveTo(x, y)
        if not no_sync:
            self.syncPhysics(spatial_only=True)