Beispiel #1
0
class PhysicallyMountableActor(AbstractMountableActor):
    """An physical actor that you can mount other physical actors to
    
    The other actors are located at a certain position
    relative to the position of this actor. You can
    use this actor to create clusters either visually
    or functionally.
    
    All actors must be under the control of the physics engine.
    
    """        

    my_properties = (
        serialize.L('children', [], 'the child actors that we own'),
        serialize.L('_world', [], 'the world that we belong to'),
        serialize.D('_offsets', {}, 'the offsets to our children'),
    )
    
    def __init__(self, tag, name='', mass=0.0, **kw):
        """Initialize the MountableActor"""
        if not mass:
            raise NoPhysicalConditions('Mass needs to be specified for mountable actor')
        super(PhysicallyMountableActor, self).__init__(tag, name, **kw)
        self.setPhysical(physical.PhysicalBody(mass=mass, update_angle=True))
        self._joints = []

    def init(self):
        """Initialise from serialized form"""
        self._joints = []
        super(PhysicallyMountableActor, self).init()
        
    def mountActor(self, actor, (x, y), original_rotation=False, rotate_with_actor=True):
Beispiel #2
0
class AbstractMountableActor(CompositeActor):
    """An base class for actors that you can mount other actors to
    
    The other actors are located at a certain position
    relative to the position of this actor. You can
    use this actor to create clusters either visually
    or functionally.
    
    """

    my_properties = (
        serialize.L('children', [], 'the child actors that we own'),
        serialize.L('_world', [], 'the world that we belong to'),
        serialize.D('_offsets', {}, 'the offsets to our children'),
    )

    def __init__(self, *args, **kw):
        """Initialize the MountableActor"""
        super(AbstractMountableActor, self).__init__(*args, **kw)
        self._offsets = {}

    def mountActor(self,
                   actor,
                   (x, y),
                   original_rotation=False,
                   rotate_with_actor=True):
Beispiel #3
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)
Beispiel #4
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,))
Beispiel #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)
Beispiel #6
0
class Rectangle(SpatialObject, serialize.Serializable):
    """Represents a rectangle"""
    
    my_properties = (
        serialize.L('rect', (0, 0, 0, 0), 'the spatial extent of the actor'),
    )

    def __init__(self, x=0, y=0, w=0, h=0):
        """Return a new object based on top left, top right, width and height"""
        self.rect = SimpleRect(x, y, w, h)
    
    def init(self):
        """Initialize from serialized"""
        if not hasattr(self, 'rect'):
            self.rect = SimpleRect(0, 0, 0, 0)
        else:
            self.rect = SimpleRect(*self.rect)
        
    @classmethod
    def fromCenter(cls, cx, cy, w, h):
        """Return a new rectangle giving the center x, y and width, height"""
        return cls(cx-w/2, cy-h/2, w, h)
        
    def isInside(self, other):
        """Return True if this object is inside another"""
        return other.rect.contains(self.rect) == 1
    
    def isOverlapping(self, other):
        """Return True if this object overlaps another"""
        return other.rect.colliderect(self.rect) == 1

    def setSpatial(self, x, y, w, h):
        """Set the spatial details of ourself"""
        self.rect = pygame.Rect(x, y, w, h)
    
    def setOrigin(self, x ,y):
        """Set the left and top coords"""
        self.rect.left = x
        self.rect.top = y
        
    def getSpatial(self):
        """Return spatial details"""
        return self.rect

    def setSpatialCentered(self, x, y, w, h):
        """Set the spatial details of ourself"""
        self.setSpatial(x-w/2, y-h/2, w, h)
        
    def getSpatialCentered(self):
        """Return spatial details"""
        x, y, w, h = self.getSpatial()
        return (x+w/2, y+h/2, w, h)
    
    def getRelativeLocation(self, other):
        """Return the relative location of another object"""
        return (other.rect.x - self.rect.x, other.rect.y - self.rect.y)

    def getRelativeLocationCentered(self, other):
        """Return the relative location of another object"""
        l1, l2 = self.getSpatialCentered(), other.getSpatialCentered()
        return (l2[0] - l1[0], l2[1] - l1[1])
    
    def move(self, dx, dy):
        """Move the actor"""
        self.rect.move_ip(dx, dy)
        
    def moveTo(self, x, y):
        """Move the center of this object to the given location"""
        self.rect.x = x-self.rect.width/2
        self.rect.y = y-self.rect.height/2
        
    def resizeBy(self, w, h):
        """Resize the spatial by the given extent"""
        self.rect.inflate_ip(w, h)

    def resizeTo(self, w, h):
        """Resize the spatial by the given extent"""
        self.resizeBy(w-self.width, h-self.height)
        
    def scale(self, factor):
        """Rescale the spatial extent"""
        _, _, w, h = self.rect
        nw, nh = w*factor, h*factor
        self.resizeTo(nw, nh)

    def getArea(self):
        """Return the area of the shape"""
        return self.rect.width * self.rect.height
    
           
    ### Simple access ###
    
    @property
    def x(self): return self.rect.x+self.rect.width/2
    @x.setter
    def x(self, value): 
        self.moveTo(value, self.y)
    @property
    def y(self): return self.rect.y+self.rect.height/2
    @y.setter
    def y(self, value):
        self.moveTo(self.x, value)
    @property
    def width(self): return self.rect.width
    @property
    def height(self): return self.rect.height
Beispiel #7
0
class CompositeActor(Actor):
    """An actor that can have children, which are also actors
    
    World operations on the parent, like adding and removing,
    will also apply to the children.
    
    If the children are removed from the parent then they are
    also removed from the world.
    
    """
    
    # When serializing the children property can be needed (eg for the active and visible
    # properties)
    children = tuple()
        
    my_properties = (
        serialize.L('children', [], 'the child actors that we own'),
        serialize.L('_world', [], 'the world that we belong to'),
    )
    
    def __init__(self, *args, **kw):
        """Initialise the actor"""
        self.children = ActorCollection()
        self._active = True
        self._visible = True
        self._world = None
        super(CompositeActor, self).__init__(*args, **kw)

    ### World events ###
    
    def removedFromWorld(self, world):
        """Called when we are being removed from the world"""
        super(CompositeActor, self).removedFromWorld(world)
        for child in self.getChildren()[:]:
            world.removeActor(child)
        self._world = None
        
    def addedToWorld(self, world):
        """Called when we are being added to the world"""
        super(CompositeActor, self).addedToWorld(world)
        for child in self.getChildren():
            world.addActor(child)
        self._world = world
            
    ### Children ###
            
    def addChild(self, actor):
        """Add a child actor"""
        self.children.append(actor)
        actor.linkEvent(events.E_REMOVED_FROM_WORLD, self._childRemoved)
        #
        # If we are already in the world then add this actor to the world also
        if self._world:
            try:
                self._world.addActor(actor)
            except world.DuplicateActor:
                # Ok if the actor is already there
                pass
        #
        return actor
                   
    def removeChild(self, actor, leave_in_world=False):
        """Remove a child actor"""
        try:
            self.children.remove(actor)
        except ValueError:
            raise InvalidActor('The actor %s was not a child of %s' % (actor.getNiceName(), self.getNiceName()))
        #
        # Remove the child from the world
        if self._world and not leave_in_world:
            self._world.removeActor(actor)

    def removeChildren(self):
        """Remove all the children"""
        for actor in self.getChildren()[:]:
            self.removeChild(actor)
            
    def hasChild(self, actor):
        """Return True if this actor already has this actor as a child"""
        return actor in self.children

    def hasChildren(self):
        """Return True if this actor has children"""
        return len(self.children) != 0
            
    def getChildren(self):
        """Return the list of children"""
        return self.children

    def getChildrenWithTag(self, tag):
        """Return all the children with a certain tag"""
        return [actor for actor in self.getChildren() if actor.tag == tag]
    
    def _childRemoved(self, child, arg):
        """A child was removed from the world"""
        if child in self.children:
            self.children.remove(child)

    # The active attribute should cascade to our children
    @property
    def active(self): return self._active
    @active.setter
    def active(self, value):
        """Set the active"""
        self._active = value
        for child in self.getChildren():
            child.active = value
            
    # The visible attribute should cascade to our children
    @property
    def visible(self): return self._visible
    @visible.setter
    def visible(self, value):
        """Set the visible"""
        self._visible = value
        for child in self.getChildren():
            child.visible = value
Beispiel #8
0
class Zone(geometry.Rectangle, common.Loggable):
    """A zone
    
    A zone is part of a world. It is a container for objects
    and it controls whether objects will take part in world 
    updates.
    
    """
    
    my_properties = (
        serialize.B('active', False, 'whether the zone is active'),
        serialize.L('actors', set(), 'the actors in this zone'),
        serialize.F('physics_stepsize', 10.0, 'the size of physics steps in ms'),
        serialize.L('global_force', (0,0), 'the global force for physics'),
        serialize.F('_rtf', 1.0, 'debugging aid to slow down physics'),
    )
    
    def __init__(self):
        """Initialise the zone"""
        super(Zone, self).__init__()
        self.addLogger()
        self.physics_stepsize = 10.0
        self.global_force = (0,0)
        self.active = False
        self.setSpatial(-1000, -1000, 2000, 2000)
        self.clearActors()
        self._initPhysics()
        self._rtf = 1.0 # A debugging aid to slow down physics

    ### Serializing ###
    
    def init(self):
        """Initialise from serialized state"""
        self.addLogger()
        self.log.info('Initializing zone %s' % self)
        super(Zone, self).init()
        self._initPhysics()
        for actor in self.actors:
            actor.init()
            if actor.getPhysical():
                actor.getPhysical().init()
                self._addPhysicalActor(actor)
        

    ### Zones ###
    
    def updateZone(self, interval, world):
        """Update the objects in the zone"""
        #
        # Iterate through actors - use a list of the actors
        # in case the actor wants to update the list of
        # actors during this iteration
        for actor in list(self.actors):
            if actor.active:
                profiler.PROFILER.start(actor, 'updateActor')
                actor.updateActor(interval, world)
                profiler.PROFILER.end()
        #
        # Do physics if we need to
        if self._physics_objects:
            self.updatePhysics(interval)
    
    def wouldContain(self, actor):
        """Return True if this zone would contain the actor as it is right now
        
        The base Zone implementation uses spatial overlapping as the criteria but you
        can create custom zones that use other criteria to decide which actors should
        be in the zone.
        
        """
        return self.isOverlapping(actor)
    
    def addActor(self, actor):
        """Add an actor to the zone"""
        if actor in self.actors:
            raise DuplicateActor('The actor %s is already in the zone' % actor)
        else:
            self.actors.add(actor)
            if actor.getPhysical():
                self._addPhysicalActor(actor)

    def hasActor(self, actor):
        """Return True if the actor is in this zone"""
        return actor in self.actors
            
    def removeActor(self, actor):
        """Remove an actor from the zone"""
        try:
            self.actors.remove(actor)
        except KeyError:
            raise ActorNotFound('The actor %s was not in the zone' % actor)       
        else:
            if actor in self._physics_objects:
                self._physics_objects.remove(actor)
                p = actor.getPhysical()
                #
                # The try-catch here is probably not required but if the game
                # is playing around with the physics space then it might
                # remove something without alerting the zone so we catch it
                # here.
                try:
                    self.space.remove(p.body)
                    if p.shape:
                        self.space.remove(p.shape)
                except KeyError, err:
                    self.log.error('Actor %s already removed from physics space' % actor.getNiceName())
Beispiel #9
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))
Beispiel #10
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, ))
Beispiel #11
0
class Zone(common.Loggable, geometry.Rectangle):
    """A zone
    
    A zone is part of a world. It is a container for objects
    and it controls whether objects will take part in world 
    updates.
    
    """

    my_properties = (
        serialize.B('active', False, 'whether the zone is active'),
        serialize.L('actors', set(), 'the actors in this zone'),
        serialize.F('physics_stepsize', 10.0,
                    'the size of physics steps in ms'),
        serialize.L('global_force', (0, 0), 'the global force for physics'),
    )

    def __init__(self):
        """Initialise the zone"""
        self.addLogger()
        self.physics_stepsize = 10.0
        self.global_force = (0, 0)
        self.active = False
        self.setSpatial(-1000, -1000, 2000, 2000)
        self.clearActors()
        self._initPhysics()

    ### Serializing ###

    def init(self):
        """Initialise from serialized state"""
        self.addLogger()
        self.log.info('Initializing zone %s' % self)
        super(Zone, self).init()
        self._initPhysics()
        for actor in self.actors:
            actor.init()
            if actor.getPhysical():
                actor.getPhysical().init()
                self._addPhysicalActor(actor)

    ### Zones ###

    def updateZone(self, interval, world):
        """Update the objects in the zone"""
        #
        # Iterate through actors - use a list of the actors
        # in case the actor wants to update the list of
        # actors during this iteration
        for actor in list(self.actors):
            if actor.active:
                actor.updateActor(interval, world)
        #
        # Do physics if we need to
        if self._physics_objects:
            self._updatePhysics(interval)

    def addActor(self, actor):
        """Add an actor to the zone"""
        if actor in self.actors:
            raise DuplicateActor('The actor %s is already in the zone' % actor)
        else:
            self.actors.add(actor)
            if actor.getPhysical():
                self._addPhysicalActor(actor)

    def hasActor(self, actor):
        """Return True if the actor is in this zone"""
        return actor in self.actors

    def removeActor(self, actor):
        """Remove an actor from the zone"""
        try:
            self.actors.remove(actor)
        except KeyError:
            raise ActorNotFound('The actor %s was not in the zone' % actor)
        else:
            if actor in self._physics_objects:
                self._physics_objects.remove(actor)
                p = actor.getPhysical()
                self.space.remove(p.body, p.shape)

    def clearActors(self):
        """Remove all actors"""
        self.actors = set()

    ### Finding ###

    def findActorByName(self, name):
        """Return the actor with the given name"""
        for actor in self.actors:
            if actor.name == name:
                return actor
        else:
            raise ActorNotFound('Could not find actor "%s"' % name)

    def findActorsByTag(self, tag):
        """Return all the actors with a certain tag"""
        return [actor for actor in self.actors if actor.tag == tag]

    def findFirstActorByTag(self, tag):
        """Return the first actor found with the given tag or raise an error"""
        for actor in self.actors:
            if actor.tag == tag:
                return actor
        else:
            raise ActorNotFound('Could not find actor with tag "%s"' % tag)

    def getActors(self):
        """Return all the actors"""
        return self.actors

    ### Physics ###

    def _initPhysics(self):
        """Initialize the physics engine"""
        #
        # Pymunk may not be installed - if so then we skip creating any physics context
        if not common.PYMUNK_OK:
            self.log.info('No pymunk - physics disabled')
            self._physics_objects = []
            return
        #
        # Create a context for the physics
        self.log.info('Initializing physics engine')
        self.space = pymunk.Space()
        self.space.add_collision_handler(2, 2, self._checkCollision, None,
                                         None, None)
        #
        # List of physics objects that we need to update
        self._physics_objects = []
        self._shape_dict = {}

    def _checkCollision(self, space, arbiter):
        """Return True if the collision should occur"""
        s1, s2 = arbiter.shapes[0], arbiter.shapes[1]
        self._collisions.append((s1, s2))
        return True

    def _addPhysicalActor(self, actor):
        """Add an actor with physics to the zone"""
        p = actor.getPhysical()
        p.space = self.space
        self.space.add(p.body, p.shape)
        self._shape_dict[p.shape] = actor
        self._physics_objects.append(actor)
        actor.syncPhysics()

    def _updatePhysics(self, interval):
        """Perform a step of the physics engine"""
        #
        # Globally applied forces
        self.space.gravity = self.global_force
        #
        # Do calculations
        self._collisions = []
        while interval > 0.0:
            togo = min(self.physics_stepsize, interval)
            self.space.step(togo / 1000.0)
            interval -= togo
        #
        # Apply all the collisions
        for shape1, shape2 in self._collisions:
            actor1, actor2 = self._shape_dict[shape1], self._shape_dict[shape2]
            actor1.processEvent(('collision', actor2))
            actor2.processEvent(('collision', actor1))
        #
        # Now update all the tracked objects in world space
        for actor in self._physics_objects:
            p = actor.getPhysical()
            actor.moveTo(*p.shape.body.position, no_sync=True)
            p.velocity = tuple(p.shape.body.velocity)

    def setPhysicsStepsize(self, interval):
        """Set the maximum step size for physics calculations"""
        self.physics_stepsize = interval

    def setGlobalForce(self, force):
        """Set the global force for physics"""
        self.global_force = force
Beispiel #12
0
class PhysicalConditions(serialize.Serializable):
    """Represents physical parameters of an object
    
    This includes the mass, velocity, force applied, acceleration
    and the physical dimensions.
    
    """

    my_properties = (
        serialize.F('mass', 0.0, 'the mass of the object'),
        serialize.L('velocity', (0.0,0.0), 'the velocity of the object'),
        serialize.L('force', (0.0,0.0), 'the force on the object'),
        serialize.F('radius', 0.0, 'the radius of the object'),        
        serialize.F('width', 0.0, 'the width of the object'),        
        serialize.F('height', 0.0, 'the height of the object'),        
        serialize.F('friction', 0.1, 'the friction the object'),        
        serialize.F('elasticity', 1.0, 'the elasticity of the object'),      
        serialize.I('layers', 0, 'the collision layers that we are in'),  
        serialize.I('group', 0, 'the collision group that we are in'),  
        serialize.B('fixed', False, 'whether the object is fixed in place'), 
        serialize.B('update_angle', False, 'whether the rotation of the body should propagate to the actors visual'), 
        serialize.B('visual_size', False, 'whether to set the size based on the visual element of our parent actor'), 
    )
    
    def __init__(self, mass=0.0, radius=0.0, velocity=(0.0, 0.0), force=(0.0, 0.0), width=0.0, height=0.0, fixed=False,
                    friction=0.1, elasticity=1.0, group=0, layers=-1, update_angle=False, visual_size=False):
        """Initialise the conditions"""
        self.body = None
        if not mass and not fixed:
            raise InvalidMass('Mass must be specified unless the object is fixed in place')
        self.mass = mass if not fixed else pymunk.inf
        self.velocity = velocity
        self.force = force
        self.fixed = fixed
        self.friction = friction
        self.elasticity = elasticity
        self.update_angle = update_angle
        self.visual_size = visual_size
        self.group = group
        self.layers = layers
        self.space = None
        if not visual_size:
            self.setGeometry(radius, width, height)

    def init(self):
        """Initialize from serialized form"""
        super(PhysicalConditions, self).init()
        self.setGeometry(self.radius, self.width, self.height)
        self._createPhysicsObject()
                
    def setGeometry(self, radius=None, width=None, height=None):
        """Set the geometry
        
        You must specify either the radius or the width and height
        
        """
        #
        # Reality check
        if radius and (width or height):
            raise InvalidDimensions('Must specify radius or width & height, not both')
        elif not radius and not (width and height):
            raise InvalidDimensions('Must specify width & height')
        #
        if radius:
            self.geometry_type = 'circle'
        else:
            self.geometry_type = 'rectangle'
        self.radius = radius
        self.width = width
        self.height = height
        self._createPhysicsObject()
            
    def _createPhysicsObject(self):
        """Return a new physics object"""
        if self.geometry_type == 'circle':
            inertia = pymunk.moment_for_circle(self.mass, 0, self.radius, (0,0))
        else:
            inertia = pymunk.moment_for_box(self.mass, self.width, self.height)
        #
        body = pymunk.Body(self.mass, inertia)
        body.velocity = self.velocity
        body.force = self.force
        #
        if self.geometry_type == 'circle':
            shape = pymunk.Circle(body, self.radius, (0,0))
        else:
            #shape = pymunk.Poly(body, [(0, 0), (self.width, 0), 
            #                           (self.width, self.height), (0, self.height)])
            w2, h2 = self.width/2, self.height/2
            shape = pymunk.Poly(body, [(-w2,-h2), (+w2, -h2), (+w2, +h2), (-w2, +h2)])
        #
        shape.elasticity = self.elasticity
        shape.collision_type = 2
        shape.group = self.group
        shape.layers = self.layers
        shape.friction = self.friction
        self.shape = shape
        self.body = body

    def updateFrom(self, physical_conditions):
        """Update the properties and our physics object"""
        self.velocity = physical_conditions.velocity
        self.force = physical_conditions.force
        self.body.velocity = self.velocity
        self.body.force = self.force
Beispiel #13
0
class Zone(geometry.Rectangle, common.Loggable):
    """A zone
    
    A zone is part of a world. It is a container for objects
    and it controls whether objects will take part in world 
    updates.
    
    """
    
    my_properties = (
        serialize.B('active', False, 'whether the zone is active'),
        serialize.L('actors', set(), 'the actors in this zone'),
        serialize.F('physics_stepsize', 10.0, 'the size of physics steps in ms'),
        serialize.L('global_force', (0,0), 'the global force for physics'),
        serialize.F('_rtf', 1.0, 'debugging aid to slow down physics'),
    )
    
    def __init__(self):
        """Initialise the zone"""
        super(Zone, self).__init__()
        self.addLogger()
        self.physics_stepsize = 10.0
        self.global_force = (0,0)
        self.active = False
        self.setSpatial(-1000, -1000, 2000, 2000)
        self.clearActors()
        self._initPhysics()
        self._rtf = 1.0 # A debugging aid to slow down physics

    ### Serializing ###
    
    def init(self):
        """Initialise from serialized state"""
        self.addLogger()
        self.log.info('Initializing zone %s' % self)
        super(Zone, self).init()
        self._initPhysics()
        for actor in self.actors:
            actor.init()
            if actor.getPhysical():
                actor.getPhysical().init()
                self._addPhysicalActor(actor)
        

    ### Zones ###
    
    def updateZone(self, interval, world):
        """Update the objects in the zone"""
        #
        # Iterate through actors - use a list of the actors
        # in case the actor wants to update the list of
        # actors during this iteration
        for actor in list(self.actors):
            if actor.active:
                actor.updateActor(interval, world)
        #
        # Do physics if we need to
        if self._physics_objects:
            self.updatePhysics(interval)
    
    def wouldContain(self, actor):
        """Return True if this zone would contain the actor as it is right now
        
        The base Zone implementation uses spatial overlapping as the criteria but you
        can create custom zones that use other criteria to decide which actors should
        be in the zone.
        
        """
        return self.isOverlapping(actor)
    
    def addActor(self, actor):
        """Add an actor to the zone"""
        if actor in self.actors:
            raise DuplicateActor('The actor %s is already in the zone' % actor)
        else:
            self.actors.add(actor)
            if actor.getPhysical():
                self._addPhysicalActor(actor)

    def hasActor(self, actor):
        """Return True if the actor is in this zone"""
        return actor in self.actors
            
    def removeActor(self, actor):
        """Remove an actor from the zone"""
        try:
            self.actors.remove(actor)
        except KeyError:
            raise ActorNotFound('The actor %s was not in the zone' % actor)       
        else:
            if actor in self._physics_objects:
                self._physics_objects.remove(actor)
                p = actor.getPhysical()
                self.space.remove(p.body)
                if p.shape:
                    self.space.remove(p.shape)
                
    def clearActors(self):
        """Remove all actors"""
        self.actors = set()
        
    ### Finding ###
    
    def findActorByName(self, name):
        """Return the actor with the given name"""
        for actor in self.actors:
            if actor.name == name:
                return actor
        else:
            raise ActorNotFound('Could not find actor "%s"' % name) 
    
    def findActorsByTag(self, tag):
        """Return all the actors with a certain tag"""
        return [actor for actor in self.actors if actor.tag == tag]
    
    def findFirstActorByTag(self, tag):
        """Return the first actor found with the given tag or raise an error"""
        for actor in self.actors:
            if actor.tag == tag:
                return actor
        else:
            raise ActorNotFound('Could not find actor with tag "%s"' % tag) 

    def getActors(self):
        """Return all the actors"""
        return self.actors

    ### Physics ###
    
    def _initPhysics(self):
        """Initialize the physics engine"""
        #
        # Pymunk may not be installed - if so then we skip creating any physics context
        if not common.PYMUNK_OK:
            self.log.debug('No pymunk - physics disabled')
            self._physics_objects = []
            return
        #
        # Create a context for the physics
        self.log.debug('Initializing physics engine with %d iterations' % PHYSICS_ITERATIONS)
        self.space = pymunk.Space(PHYSICS_ITERATIONS)
        self.space.add_collision_handler(2, 2, self._checkCollision, None, None, None)
        #
        # List of physics objects that we need to update
        self._physics_objects = []
        self._shape_dict = {}
                
    def _checkCollision(self, space, arbiter):
        """Return True if the collision should occur"""
        s1, s2 = arbiter.shapes[0], arbiter.shapes[1]
        self._collisions.append((s1, s2))
        return True 
    
    def _addPhysicalActor(self, actor):
        """Add an actor with physics to the zone"""
        p = actor.getPhysical()
        p.space = self.space
        if p.shape:
            self.space.add(p.body, p.shape)
            self._shape_dict[p.shape] = actor
        else:
            self.space.add(p.body)
        self._physics_objects.append(actor)
        actor.syncPhysics()
        
    def updatePhysics(self, interval):
        """Perform a step of the physics engine
        
        You do not normally need to call this method as it is called by the
        updateZone method. You may call this to advance the physics simulation
        along without affecting other game elements.
        
        """
        #
        # Globally applied forces
        self.space.gravity = self.global_force
        #
        # Do calculations
        self._collisions = []
        while interval > 0.0:
            togo = min(self.physics_stepsize, interval)
            self.space.step(togo/1000.0*self._rtf) # rtf is a debugging aid to go into slow motion mode
            interval -= togo
        #
        # Apply all the collisions
        for shape1, shape2 in self._collisions:
            actor1, actor2 = self._shape_dict[shape1], self._shape_dict[shape2]
            actor1.processEvent(('collision', actor2))
            actor2.processEvent(('collision', actor1))
        #
        # Now update all the tracked objects in world space
        for actor in self._physics_objects:
            p = actor.getPhysical()
            actor.moveTo(*p.body.position, no_sync=True, override_lock=True)
            p.velocity = tuple(p.body.velocity)
            if p.update_angle:
                actor.setAngle(-math.degrees(p.body.angle), override_lock=True)
            
    def setPhysicsStepsize(self, interval):
        """Set the maximum step size for physics calculations"""
        self.physics_stepsize = interval
        
    def setGlobalForce(self, force):
        """Set the global force for physics"""
        self.global_force = force

    def sleepActor(self, actor):
        """Tell the actor to go to sleep from a physics perspective
        
        The actor will still be visible and will still be updated but it
        will not update its physics. Useful for optimising when an actor
        does not need to interact with the physics simulation for a while.

        """
        actor.getPhysical().body.sleep()
        
    def wakeActor(self, actor):
        """Tell the actor to go to wake up from a physics perspective 
        
        An actor that was put to sleep (via sleepActor) will be woken
        up and take part in the physics simulation again.

        """
        actor.getPhysical().body.activate()
Beispiel #14
0
class CompositeActor(Actor):
    """An actor that can have children, which are also actors
    
    World operations on the parent, like adding and removing,
    will also apply to the children.
    
    If the children are removed from the parent then they are
    also removed from the world.
    
    """
    
    my_properties = (
        serialize.L('children', [], 'the child actors that we own'),
        serialize.L('_world', [], 'the world that we belong to'),
    )
    
    def __init__(self, *args, **kw):
        """Initialise the actor"""
        super(CompositeActor, self).__init__(*args, **kw)
        self.children = []
        self._world = None

    ### World events ###
    
    def removedFromWorld(self, world):
        """Called when we are being removed from the world"""
        super(CompositeActor, self).removedFromWorld(world)
        for child in self.getChildren()[:]:
            world.removeActor(child)
        self._world = None
        
    def addedToWorld(self, world):
        """Called when we are being added to the world"""
        super(CompositeActor, self).addedToWorld(world)
        for child in self.getChildren():
            world.addActor(child)
        self._world = world
            
    ### Children ###
            
    def addChild(self, actor):
        """Add a child actor"""
        self.children.append(actor)
        actor.linkEvent(events.E_REMOVED_FROM_WORLD, self._childRemoved)
        
    def removeChild(self, actor):
        """Remove a child actor"""
        try:
            self.children.remove(actor)
        except ValueError:
            raise InvalidActor('The actor %s was not a child of %s' % (actor.getNiceName(), self.getNiceName()))
        #
        # Remove the child from the world
        if self._world:
            self._world.removeActor(actor)
            
    def getChildren(self):
        """Return the list of children"""
        return self.children
    
    def _childRemoved(self, child, arg):
        """A child was removed from the world"""
        if child in self.children:
            self.children.remove(child)