class Screen(object): """ Abstract class that coordinates visualization. Must be overridden in each backend. """ __instance = None is_canvas = False background = colorproperty('background') draw_circle = delegate_to('camera') draw_aabb = delegate_to('camera') draw_poly = delegate_to('camera') draw_segment = delegate_to('camera') draw_path = delegate_to('camera') draw_ray = delegate_to('camera') draw_line = delegate_to('camera') draw_image = delegate_to('camera') @property def shape(self): return self.width, self.height def __new__(cls, *args, **kwds): if cls.__instance is not None: raise TypeError('cannot create two instances of singleton object') return object.__new__(cls) def __init__(self, shape=(800, 600), pos=(0, 0), zoom=1, background=None): self.width, self.height = shape self.pos = Vec2(*pos) self.zoom = zoom self.background = background self.camera = Camera(self) self.visible = False def init(self): """ Initialize game screen. """ def show(self): """ Show initialized game window. """ self.visible = True def draw(self, obj): obj.draw(self.camera)
class Body(physics.Body): def __init__(self, *args, **kwargs): # Visualization parameters image = {'image': kwargs.pop('image', None)} for k in list(kwargs): if k.startswith('image_'): image[k] = kwargs.pop(k) self.color = kwargs.pop('color', black) self.linecolor = kwargs.pop('linecolor', None) self.linewidth = kwargs.pop('linewidth', 1) self.visible = kwargs.pop('visible', True) self.world = kwargs.pop('world', None) # Init physics object and visualization super().__init__(*args, **kwargs) self.__init_image(**image) # Set world if self.world is not None: self.world.add(self) def __init_image(self, image, image_offset=(0, 0), image_reference=None, **kwargs): self._image = None self._drawshape = None if image is not None: # Get all image parameters img_kwargs = {} for k, v in kwargs.items(): if k.startswith('image_'): img_kwargs[k[6:]] = v if isinstance(image, str): image = Image(image, self.pos, **img_kwargs) else: raise NotImplementedError self._image = image offset = asvector(image_offset) if image_reference in [ 'pos_ne', 'pos_nw', 'pos_se', 'pos_sw', 'pos_left', 'pos_right', 'pos_up', 'pos_down' ]: pos_ref = getattr(self, image_reference) pos_img_ref = getattr(image, image_reference) offset += pos_ref - pos_img_ref elif image_reference not in ['middle', None]: raise ValueError('invalid image reference: %r' % image_reference) image.offset = offset else: self._drawshape = self._init_drawshape(color=self.color or black, linecolor=self.linecolor, linewidth=self.linewidth) @lazy def _input(self): return conf.get_input() color = colorproperty('color') linecolor = colorproperty('linecolor') @property def image(self): img = self._image if img is None: return None img.pos = self.pos + img.offset return img @image.setter def image(self, value): if value is None: self._image = None return if isinstance(value, str): value = Image(value, self.pos) self._image = value @property def drawable(self): return self.image or self.drawshape @property def drawshape(self): if self._drawshape is None: return None self._drawshape.pos = self.pos return self._drawshape def show(self): """ Makes object visible. """ self.visible = True def hide(self): """ Makes object invisible. It keeps interacting with the wold, but it is not shown on the screen. """ self.visible = False def draw(self, screen): """ Draw object in a canvas-like screen. """ img = self.image if img is not None: img.pos = self.pos + img.offset return img.draw(screen) else: return self.draw_shape(screen) def draw_shape(self, screen): """ Draw a shape identical to the object's bounding box. """ raise NotImplementedError('must be implemented on subclass.') def destroy(self): super().destroy() if self.world is not None: self.world.remove(self) self.world = None def _init_drawshape(self, color=None, linecolor=None, linewidth=1): bbox = self.bb cls = getattr(draw, type(bbox).__name__) return cls(*bbox, color=color, linecolor=linecolor, linewidth=linewidth)
class World(Listener, collections.MutableSequence): """ Combines physical simulation with display. """ # Simulation properties background = colorproperty('background', 'white') gravity = delegate_to('_simulation') damping = delegate_to('_simulation') adamping = delegate_to('_simulation') time = delegate_to('_simulation', readonly=True) # Special properties @lazy def add(self): return ObjectFactory(self) @lazy def track(self): return Tracker(self) @lazy def _mainloop(self): return conf.get_mainloop() @lazy def _input(self): return conf.get_input() _last_instances = [] def __init__(self, background=None, gravity=None, damping=0, adamping=0, restitution=1, friction=0, bounds=None, max_speed=None, simulation=None): self.background = background self._render_tree = RenderTree() self._objects = [] if simulation: self._simulation = simulation else: self._simulation = Simulation(gravity=gravity, damping=damping, adamping=adamping, restitution=restitution, friction=friction, max_speed=max_speed, bounds=bounds) self.is_paused = False # Populate world with user-customizable init self.init() # Saves instance self._last_instances.append(weakref.ref(self)) # Connect signals self.autoconnect() def __len__(self): return len(self._objects) def __iter__(self): return iter(self._objects) def __getitem__(self, i): return self._objects[i] def __delitem__(self, i): self.remove(self._objects[i]) def __setitem__(self, key, value): raise IndexError('cannot replace objects in world') def _add(self, obj, layer=0): """ Adds object to the world. """ if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) else: self._render_tree.add(obj, layer) if isinstance(obj, Body): self._simulation.add(obj) obj.world = self self._objects.append(obj) def insert(self, idx, obj): raise IndexError('cannot insert objects at specific positions. ' 'Please user world.add()') def append(self, obj, layer=0): """ Alias to World.add() """ self.add(obj, layer) def remove(self, obj): """ Remove object from the world. """ if getattr(obj, 'world', None) is self: if obj in self._render_tree: self._render_tree.remove(obj) self._simulation.remove(obj) obj.world = None else: self._render_tree.remove(obj) self._objects.remove(obj) def init(self): """ Executed after initialization. Should be overridden by sub-classes in order to populate the world with default objects during its creation. The default implementation does nothing. """ def pause(self): """ Pause physics simulation. """ self.is_paused = True def resume(self): """ Resume paused physics simulation. """ self.is_paused = False def toggle_pause(self): """ Toggles paused state. """ self.is_paused = not self.is_paused def update(self, dt): """ Main update routine. """ if not self.is_paused: self._simulation.update(dt) def run(self, timeout=None, **kwds): """ Runs simulation until the given timeout expires. Args: timeout (float): Maximum duration of simulation (in seconds). Leave None to run the simulation indefinitely. """ conf.init() conf.show_screen() self._mainloop.run(self, timeout=timeout, **kwds) def start(self, **kwds): """ Non-blocking version of World.run(). Starts simulation in a separate thread. This must be supported by the backend (pygame, for instance, does). """ if hasattr(self, '_thread'): try: self._thread.join(0) except TimeoutError: pass self._thread = threading.Thread(target=self.run, kwargs=kwds) self._thread.start() def stop(self): """ Stops simulation. """ # Forces thread to stop try: self._thread.join(0) del self._thread except (AttributeError, TimeoutError): pass self._mainloop.stop() def render_tree(self): """ Return the render tree. """ return self._render_tree
class Screen(object): """ Abstract class that coordinates visualization. Must be overridden in each backend. """ __instance = None is_canvas = False background = colorproperty('background') draw_circle = delegate_to('camera') draw_aabb = delegate_to('camera') draw_poly = delegate_to('camera') draw_segment = delegate_to('camera') draw_path = delegate_to('camera') draw_ray = delegate_to('camera') draw_line = delegate_to('camera') draw_image = delegate_to('camera') @property def shape(self): return self.width, self.height def __new__(cls, *args, **kwds): if cls.__instance is not None: raise TypeError('cannot create two instances of singleton object') return object.__new__(cls) def __init__(self, shape=(800, 600), pos=(0, 0), zoom=1, background=None): self.width, self.height = shape self.pos = Vec2(*pos) self.zoom = zoom self.background = background self.camera = Camera(self) self.visible = False self.background_image = None self.background_color = white def init(self): """ Initialize game screen. """ def show(self): """ Show initialized game window. """ self.visible = True def set_background_image(self, image): """ Configures a background image from its asset name. """ if image is None: self.background_image = None else: from FGAme import asset if isinstance(image, str): image = asset.Asset(image, 'images', ['.png', '.jpeg', '.jpg']) self.background_image = self.prepare_image(image) def prepare_image(self, asset): """ Prepares a useful image object from asset. """ raise NotImplementedError def draw(self, obj): obj.draw(self.camera)