Esempio n. 1
0
class Camera(common.Loggable, geometry.Rectangle):
    """Represents a camera"""

    my_parameters = (serialize.F('zoom', 1.0, 'the camera zoom'), )

    def __init__(self):
        """Initialise the Camera"""
        super(Camera, self).__init__()
        self.target = None
        self.zoom = 1.0

    def init(self):
        """Initialise from serialized"""
        self.zoom = 1.0

    def setZoom(self, zoom, x, y):
        """Set the new zoom centered on the given x and y"""
        self.scale(zoom / self.zoom)
        self.zoom = zoom

    def canSeeActors(self, actors):
        """Return the actors that we can see from a list of actors"""
        return [actor for actor in actors if self.canSee(actor)]

    def canSee(self, actor):
        """Return True if we can see the actor"""
        return self.isOverlapping(actor)

    def setTarget(self, target):
        """Set the target for the camera to head towards"""
        self.target = target

    def getTarget(self):
        """Return the camera's target location"""
        return self.target

    def update(self, interval):
        """Update the location of the camera"""
        #
        # If we have a target then move towards it
        if self.target:
            dx, dy = self.target.getRelativeLocationCentered(self)
            mx = my = 100.0 * interval / 1000.0
            if abs(dx) > mx:
                dx = math.copysign(mx, dx)
            if abs(dy) > my:
                dy = math.copysign(my, dy)
            self.move(-dx, -dy)

    def getRelativeLocation(self, other):
        """Return the relative location of one from another"""
        x, y = super(Camera, self).getRelativeLocation(other)
        return x * self.zoom, y * self.zoom
Esempio n. 2
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)
Esempio n. 3
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())
Esempio n. 4
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
Esempio n. 5
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
Esempio n. 6
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()
Esempio n. 7
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)