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,))
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)
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, ))
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)
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)