Exemple #1
0
    def update(self):
        # Don't do anything if stopped
        if not self.started:
            return

        LayeredUpdates.update(self)

        for u in self.p0_units:
            u.update()

        for u in self.p1_units:
            u.update()
    def update(self):
        """
        Update everything in the group.
        """
        LayeredUpdates.update(self)

        # Update units
        base_unit.BaseUnit.active_units.update()

        # The unit is finished moving, so go back to select
        if self.mode == Modes.Moving:
            if (not self.sel_unit) or (not self.sel_unit.is_moving):
                self.change_mode(Modes.Select)

        # Update the reticle effect
        self._reticle.update()

        # Update effects
        self._effects.update()
 def update(self):
     """
     Update everything in the group.
     """
     LayeredUpdates.update(self)
     
     # Update units
     base_unit.BaseUnit.active_units.update()
     
     # The unit is finished moving, so go back to select
     if self.mode == Modes.Moving:
         if (not self.sel_unit) or (not self.sel_unit.is_moving):
             self.change_mode(Modes.Select)
             
     # Update the reticle effect
     self._reticle.update()
     
     # Update effects
     self._effects.update()
Exemple #4
0
class PlatformerScene(Scene):
    def __init__(self, game):
        super().__init__(game)
        self.player = None
        self.active = True

        self.geometry = list()
        self.space = pymunk.Space()
        self.space.gravity = (0, 1000)
        self.sprites = LayeredUpdates()
        self.event_handler = event_handling.EventQueueHandler()
        self.background = resources.gfx("background.png", convert=True)
        self.load()
        pygame.mixer.music.load(resources.music_path("zirkus.ogg"))
        pygame.mixer.music.play(-1)

    def add_static(self, vertices, rect):
        body = pymunk.Body(body_type=pymunk.Body.STATIC)
        body.position = rect.x, rect.y
        shape = pymunk.Poly(body, vertices)
        shape.friction = 1.0
        shape.elasticity = 1.0
        self.space.add(body, shape)

    def load(self):
        def box_vertices(x, y, w, h):
            lt = x, y
            rt = x + w, y
            rb = x + w, y + h
            lb = x, y + h
            return lt, rt, rb, lb

        filename = path_join("data", "maps", "untitled.tmx")
        tmxdata = pytmx.util_pygame.load_pygame(filename)
        for obj in tmxdata.objects:
            if obj.type == map_fixed:
                rect = Rect(obj.x, obj.y, obj.width, obj.height)
                vertices = box_vertices(0, 0, obj.width, obj.height)
                self.add_static(vertices, rect)

            elif obj.type == map_yarn_spawn:
                ball = sprite.Ball(Rect((obj.x, obj.y), (32, 32)))
                model = BasicModel()
                model.sprites = [ball]
                model.pymunk_objects = ball.pymunk_shapes
                self.add_model(model)
                self.player = model

            elif obj.type == map_player_spawn:
                self.player = unicyclecat.build(self.space, self.sprites)
                self.player.position = obj.x, obj.y

        self.fsm = SimpleFSM(control, "idle")

    def add_model(self, model):
        self.sprites.add(*model.sprites)
        self.space.add(model.pymunk_objects)

    def remove_model(self, model):
        self.sprites.remove(*model.sprites)
        self.space.remove(model.pymunk_objects)

    def render(self):
        surface = self._game.screen
        surface.blit(self.background, (0, 0))
        self.sprites.draw(surface)
        return [surface.get_rect()]

    def tick(self, dt):
        step_amount = (1 / 30.) / 30
        for i in range(30):
            self.space.step(step_amount)
        self.sprites.update(dt)

    def event(self, pg_event):
        events = self.event_handler.process_event(pg_event)
        position = self.player.position

        for event in events:
            try:
                cmd, arg = self.fsm((event.button, event.held))
            except ValueError as e:
                continue

            if cmd == "move":
                resources.sfx("cat_wheel.ogg", False, True)
                resources.sfx("cat_wheel.ogg", True)
                self.player.accelerate(arg)

            if cmd == "idle":
                self.player.brake()

            elif cmd == "jump":
                resources.sfx("cat_jump.ogg", True)
                self.player.main_body.apply_impulse_at_world_point((0, -600),
                                                                   position)
def graph_loop(mass_lower_limit=0.0,
               mass_upper_limit=0.0,
               radius_lower_limit=0.0,
               radius_upper_limit=0.0,
               is_gas_drwaf=False):
    m_lo_l = mass_lower_limit
    m_hi_l = mass_upper_limit
    r_lo_l = radius_lower_limit
    r_hi_l = radius_upper_limit

    fondo = display.set_mode((witdh, height), SCALED)
    rect = Rect(60, 2, 529, 476)
    lineas = LayeredUpdates()

    linea_h = Linea(rect, rect.x, rect.centery, rect.w, 1, lineas)
    linea_v = Linea(rect, rect.centerx, rect.y, 1, rect.h, lineas)
    punto = Punto(rect, rect.centerx, rect.centery, lineas)

    data = {}

    if any([
            mass_lower_limit < 0, mass_upper_limit < 0, radius_lower_limit < 0,
            radius_upper_limit < 0
    ]):
        raise ValueError()

    lim_mass_a = int(find_and_interpolate(m_lo_l, mass_keys,
                                          exes)) if m_lo_l else 0
    lim_mass_b = int(find_and_interpolate(m_hi_l, mass_keys,
                                          exes)) if m_hi_l else 0
    lim_radius_a = int(find_and_interpolate(r_lo_l, radius_keys,
                                            yes)) if r_lo_l else 0
    lim_radius_b = int(find_and_interpolate(r_hi_l, radius_keys,
                                            yes)) if r_hi_l else 0

    move_x, move_y = True, True
    lockx, locky = False, False

    mass_value = 0
    radius_value = 0

    mass_color = negro
    radius_color = negro

    mouse.set_pos(rect.center)
    event.clear()

    done = False
    composition_text_comp = None
    while not done:
        for e in event.get():
            if e.type == QUIT:
                py_quit()
                if __name__ == '__main__':
                    sys.exit()
            elif e.type == MOUSEBUTTONDOWN:
                if e.button == 1:
                    if (not lockx) or (not locky):
                        if (not lockx) and (not move_x):
                            lockx = True

                        elif (not locky) and (not move_y):
                            locky = True

                        elif not punto.disabled:
                            data['mass'] = round(mass_value, 3)
                            data['radius'] = round(radius_value, 3)
                            data['gravity'] = round(
                                mass_value / (radius_value**2), 3)
                            data['density'] = round(
                                mass_value / (radius_value**3), 3)
                            done = True
                        else:
                            data = {}
                            done = True

                elif e.button == 3:
                    if lockx:
                        lockx = False
                        move_x = not lockx

                    if locky:
                        locky = False
                        move_y = not locky

            elif e.type == MOUSEMOTION:
                px, py = e.pos
                if move_y:
                    linea_h.move_y(py)
                    punto.move_y(py)

                if move_x:
                    linea_v.move_x(px)
                    punto.move_x(px)

                point_x, point_y = punto.rect.center
                if rect.collidepoint(point_x, point_y) and (move_x or move_y):
                    if mascara.get_at((point_x, point_y)):
                        punto.select()
                        for name in _lineas:
                            if [point_x, point_y] in _lineas[name]:
                                composition_text_comp = name
                                break
                    else:
                        punto.deselect()

            elif e.type == KEYDOWN:
                if e.key == K_ESCAPE:
                    py_quit()
                    sys.exit()

                if e.key == K_LSHIFT:
                    move_x = False

                elif e.key == K_LCTRL:
                    move_y = False

                elif e.key == K_SPACE:
                    data['mass'] = round(mass_value, 3)
                    data['radius'] = round(radius_value, 3)
                    data['gravity'] = round(mass_value / (radius_value**2), 3)
                    data['density'] = round(mass_value / (radius_value**3), 3)
                    done = True

            elif e.type == KEYUP:
                if e.key == K_LSHIFT:
                    if not lockx:
                        move_x = True

                elif e.key == K_LCTRL:
                    if not locky:
                        move_y = True

        px, py = punto.rect.center
        alto, bajo = 0, 0
        if rect.collidepoint((px, py)):
            for _y in reversed(range(0, py)):
                if mascara.get_at((px, _y)):
                    alto = _y
                    break
            for _y in range(py, 476):
                if mascara.get_at((px, _y)):
                    bajo = _y
                    break

            a, b = 0, 0
            # creo que esto se puede escribir con oneliners.
            for name in _lineas:
                if [px, alto] in _lineas[name]:
                    a = name
                    break
            for name in _lineas:
                if [px, bajo] in _lineas[name]:
                    b = name
                    break
            if a and b:
                c = composiciones
                silicates = interpolate(py, alto, bajo, c[a]['silicates'],
                                        c[b]['silicates'])
                hydrogen = interpolate(py, alto, bajo, c[a]['hydrogen'],
                                       c[b]['hydrogen'])
                helium = interpolate(py, alto, bajo, c[a]['helium'],
                                     c[b]['helium'])
                iron = interpolate(py, alto, bajo, c[a]['iron'], c[b]['iron'])
                water_ice = interpolate(py, alto, bajo, c[a]['water ice'],
                                        c[b]['water ice'])
                values = [
                    i for i in [silicates, hydrogen, helium, iron, water_ice]
                    if i != 0
                ]

                # sólo mostramos los valores mayores a 0%
                keys, compo = [], []
                if silicates:
                    compo.append(str(round(silicates, 2)) + '% silicates')
                    keys.append('silicates')
                if hydrogen:
                    compo.append(str(round(hydrogen, 2)) + '% hydrogen')
                    keys.append('hydrogen')
                if helium:
                    compo.append(str(round(helium, 2)) + '% helium')
                    keys.append('helium')
                if iron:
                    compo.append(str(round(iron, 2)) + '% iron')
                    keys.append('iron')
                if water_ice:
                    compo.append(str(round(water_ice, 2)) + '% water ice')
                    keys.append('water ice')

                composition_text_comp = ', '.join(compo)
                data['composition'] = dict(zip(keys, values))

                if hydrogen or helium:
                    data['clase'] = 'Gas Dwarf'
                    data['albedo'] = 30
                else:
                    data['clase'] = 'Terrestial Planet'
                    data['albedo'] = 25
            else:
                data = {}

        mass_value = find_and_interpolate(linea_v.rect.x, exes, mass_keys)
        radius_value = find_and_interpolate_flipped(linea_h.rect.y, yes,
                                                    radius_keys)

        block = Surface(rect.size, SRCALPHA)
        block_mask = mask.from_surface(block)
        if any([lim_mass_b, lim_mass_a, lim_radius_a, lim_radius_b]):
            block_rect = block.get_rect(topleft=rect.topleft)
            alpha = 150
            if lim_mass_a:
                block.fill([0] * 3 + [alpha],
                           (0, rect.y - 2, lim_mass_a - rect.x, rect.h))
            if lim_mass_b:
                block.fill([0] * 3 + [alpha],
                           (lim_mass_b - rect.x, rect.y - 2, rect.w, rect.h))
            if lim_radius_a:
                block.fill([0] * 3 + [alpha],
                           (0, lim_radius_a, rect.w, rect.h - lim_radius_a))
            if lim_radius_b:
                block.fill([0] * 3 + [alpha],
                           (0, rect.y - 2, rect.w, lim_radius_b))
            if is_gas_drwaf:
                block.blit(gas_drawf, (0, 0))

            block_mask = mask.from_surface(block)
            point_x, point_y = punto.rect.center
            if block_rect.collidepoint((point_x, point_y)):
                if block_mask.get_at((point_x - rect.x, point_y - rect.y)):
                    punto.disable()
                    radius_color = rojo
                    mass_color = rojo
                else:
                    punto.enable()
                    mass_color = negro
                    radius_color = negro

        mass_text = 'Mass:' + str(round(mass_value, 3))
        radius_text = 'Radius:' + str(round(radius_value, 3))
        gravity_text = 'Density:' + str(
            round(mass_value / (radius_value**3), 3))
        density_text = 'Gravity:' + str(
            round(mass_value / (radius_value**2), 3))

        if not done:
            fondo.fill(blanco)
            fondo.blit(graph, (0, 0))
            if block_mask.count() != 0:
                fondo.blit(block, rect)

            if punto.disabled:
                fondo.blit(texto3, rectT3)
            else:
                fondo.blit(fuente1.render(mass_text, True, mass_color),
                           (5, rect.bottom + 43))
                fondo.blit(fuente1.render(radius_text, True, radius_color),
                           (140, rect.bottom + 43))
                fondo.blit(fuente1.render(density_text, True, negro),
                           (130 * 2 - 5, rect.bottom + 43))
                fondo.blit(fuente1.render(gravity_text, True, negro),
                           (140 * 3, rect.bottom + 43))
                if composition_text_comp is not None:
                    composition_text = 'Composition:' + composition_text_comp
                    fondo.blit(
                        fuente1.render(composition_text, True, negro, blanco),
                        (5, rect.bottom + 64))

            fondo.blit(texto1, rectT1)
            fondo.blit(texto2, rectT2)
            punto.update()
            lineas.update()
            lineas.draw(fondo)
            display.update()

    display.quit()
    return data
class DDR(Microgame):

    def __init__(self):
        Microgame.__init__(self)
        self.rect=Rect(0,0, locals.WIDTH, locals.HEIGHT)
        self.arrow1=arrow(self,'left',50,50,0,0)
        self.arrow2=arrow(self,'down',150,50,0,0)
        self.arrow3=arrow(self,'up',250, 50,0,0)
        self.arrow4=arrow(self,'right',350,50,0,0)
        self.background=Background()
        self.sprites=LayeredUpdates([self.background,self.arrow1,self.arrow2,self.arrow3,self.arrow4])
        self.timesincearrow=0
        self.dancer=pyganim.PygAnimation([('games/DDR/DDRGIFS/TWERK/twerk1.png',0.1),
                                        ('games/DDR/DDRGIFS/TWERK/twerk2.png',0.1),
                                        ('games/DDR/DDRGIFS/TWERK/twerk3.png',0.1),
                                        ('games/DDR/DDRGIFS/TWERK/twerk4.png',0.1),
                                        ('games/DDR/DDRGIFS/TWERK/twerk5.png',0.1),
                                        ('games/DDR/DDRGIFS/TWERK/twerk6.png',0.1)])
        
        self.anim=pyganim.PygAnimation([('games/DDR/DDRGIFS/MUSIC_ANIMATION/music1.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music2.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music3.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music4.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music5.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music6.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music7.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music8.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music9.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music10.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music11.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music12.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music13.png',0.1),
                                        ('games/DDR/DDRGIFS/MUSIC_ANIMATION/music14.png',0.1)])

        self.disco=pyganim.PygAnimation([('games/DDR/DDRGIFS/DISCOBALL/disco3.png',0.1),
                                         ('games/DDR/DDRGIFS/DISCOBALL/disco4.png',0.1)])
    
        self.dancer.play()
        self.anim.play()
        self.disco.play()

    def time_elapsed(self):
        self.timesincearrow=pygame.time.get_ticks()
        
    def start(self):
        self.time_elapsed()
        music.load('games/DDR/geekin.ogg')
        music.play(2,0.0)

    def stop(self):
        music.stop()

    
        
    def update(self):
        self.sprites.update()
        time=pygame.time.get_ticks()
        if time-self.timesincearrow>350:
            num=randint(0,3)
            if num==0:
                self.sprites.add(moving_arrow(self,'left',50,768,0,-10))
            elif num==1:
                self.sprites.add(moving_arrow(self,'down',150,768,0,-10))
            elif num==2:
                self.sprites.add(moving_arrow(self,'up',250,768,0,-10))
            elif num==3:
                self.sprites.add(moving_arrow(self,'right',350,768,0,-10))
                
            self.time_elapsed()
            
        for event in pygame.event.get():
            if event.type==KEYDOWN or event.type==KEYUP:
                if event.key==K_q:
                    self.lose()
        

    def render(self,surface):
        surface.fill(Color(0,0,0))
        self.sprites.draw(surface)
        self.dancer.blit(surface,(800,100))
        self.anim.blit(surface,(550,500))
        self.disco.blit(surface,(600,50))
Exemple #7
0
class GraphView(Minigame):
    GAME_NAME = "GraphView"

    def __init__(self, graph_yaml=None, graph_tmx=None):
        self.visitor = None
        self.scroll_group = None
        self.graph_yaml = graph_yaml
        self.graph_tmx = graph_tmx
        self.visitor_cursor = None
        self.vertex_group = VertexLookupGroup()
        self.hud_group = None
        self.hud_button = None
        self.pointer = None
        self.sprites = LayeredUpdates()
        self._animations = Group()

    def animate(self, *args, **kwargs):
        ani = Animation(*args, **kwargs)
        self._animations.add(ani)
        return ani

    def initialize(self, context):

        screen = pygame.display.get_surface()
        adventure_graph = build_graph_from_yaml_data(
            load_yaml_data(get_data_asset(self.graph_yaml)))
        self.visitor = Visitor.visit_graph(adventure_graph, context)

        tmx_data = load_pygame(resources.get_map_asset(self.graph_tmx))
        map_data = pyscroll.TiledMapData(tmx_data)
        map_layer_size = screen.get_width(), int(screen.get_height() * .80)
        map_layer_rect = Rect((0, 0), map_layer_size)
        map_layer = pyscroll.BufferedRenderer(map_data, map_layer_size)

        self.scroll_group = PyscrollGroup(map_layer=map_layer)

        self.pointer = PointerSprite(self.vertex_group, self.scroll_group)
        # self.pointer.selected_vertex_id = "START"
        self.sprites.add(self.pointer, layer=100)

        sw, sh = screen.get_size()
        hud_rect = Rect(20, sh * .76, sw * .80, sh * .2)
        border_image = load_image('border-default.png').convert_alpha()
        self.hud_group = HUDGroup(hud_rect, border_image)
        info = VertexInfoSprite(self.visitor)
        info.rect = Rect(12, 12, hud_rect.width - 250, hud_rect.height - 24)
        self.hud_group.add(info)
        self.hud_group.open()

        self.hud_button = HUDButton(self.hud_group, 850, 70)
        self.hud_group.add(self.hud_button)

        edges = list()
        for vertex in self.visitor.graph.vertex_index.values():
            vertex_sprite = VertexSprite(vertex)
            self.vertex_group.add(vertex_sprite)
            self.scroll_group.add(vertex_sprite)
            for edge in vertex.edges:
                edges.append(edge)

        edge_sprites = dict()
        for edge in edges:
            key = [edge.from_vertex.vertex_id, edge.to_vertex.vertex_id]
            key.sort()
            key = tuple(key)
            if key not in edge_sprites:
                edge_sprite = EdgeSprite(edge, self.scroll_group)
                from_vertex_sprite = self.vertex_group[
                    edge.from_vertex.vertex_id]
                to_vertex_sprite = self.vertex_group[edge.to_vertex.vertex_id]
                from_vertex_sprite.edge_sprites.append(edge_sprite)
                to_vertex_sprite.edge_sprites.append(edge_sprite)
                edge_sprites[key] = edge_sprite

        self.visitor_cursor = VisitorCursor(self.visitor, self.pointer,
                                            self.vertex_group,
                                            self.scroll_group)

        if context.get('show_clippie', False):
            c = Clippie(self.sprites, self._animations, context)
            c.rect.topleft = 1100, 550
            self.sprites.add(c)
            self.visitor_cursor.clippie = c

        clock = pygame.time.Clock()

        pygame.display.flip()
        pygame.mouse.set_visible(False)

        while True:
            delta = clock.tick(60)
            events = pygame.event.get()
            self.hud_group.update(delta, events)
            for event in events:
                if event.type == pygame.QUIT:
                    sys.exit(0)
                elif event.type == pygame.KEYDOWN or self.hud_button.handle_click:
                    vertex_id = self.visitor_cursor.current_vertex_sprite.vertex_id
                    if self.hud_button.handle_click or pygame.K_SPACE == event.key and vertex_id == self.pointer.selected_vertex_id:
                        self.hud_button.handle_click = False
                        if self.visitor.current_vertex.can_activate(
                                self.visitor.context):
                            activation_dict = self.visitor.activate_current_vertex(
                            )
                            if activation_dict[
                                    "command"] == "launch-mini-game":
                                r = RunMinigameActivation(
                                    activation_dict["command"],
                                    activation_dict["activation-keyword-args"])

                                logger.debug(
                                    "Stating game: {} with arguments {}".
                                    format(r.mini_game_name,
                                           r.mini_game_keyword_args))
                                unhandled_actions = self.minigame_manager.run_minigame(
                                    r.mini_game_name, self.visitor.context,
                                    r.post_run_actions,
                                    **r.mini_game_keyword_args
                                    if r.mini_game_keyword_args is not None
                                    else {})

                                if self.has_exit_action(unhandled_actions):
                                    return

                        else:
                            vertex = self.visitor.current_vertex
                            failing = vertex.activation_pre_requisites.get_failing_pre_requisites(
                                self.visitor.context)
                            self.visitor.context[
                                'gamestate.dialog_text'] = failing[0].hint
                            self.visitor_cursor.animations.add(
                                Task(self.visitor_cursor.clear_hint, 5000))

            self.scroll_group.update(delta, events)
            # self.hud_group.update(delta, events)
            self.sprites.update(delta, events)
            self._animations.update(delta)
            self._update_edge_colors()

            x_offset = (
                (self.visitor_cursor.rect.x - self.scroll_group.view.x) -
                self.pointer.rect.x) / 2
            self.scroll_group.center(
                (self.visitor_cursor.rect.x - x_offset, 0))

            self.scroll_group.draw(screen, map_layer_rect)

            screen.fill((200, 200, 200), (0, sh * .75, 1280, 200))

            self.hud_group.draw(screen)
            self.sprites.draw(screen)

            screen.blit(self.pointer.image, self.pointer.rect)
            pygame.display.flip()

    def has_exit_action(self, unhandle_actions):
        for u in unhandle_actions:
            if u.ACTION_NAME == ExitGameAction.ACTION_NAME:
                return True
        return False

    def run(self, context):
        pass

    def _update_edge_colors(self):
        for key, vertex_sprite in self.vertex_group.lookup.items():
            for e in vertex_sprite.edge_sprites:
                if e.edge.can_traverse(self.visitor.context):
                    e.color = pygame.Color("GREEN")
Exemple #8
0
class PlatformerScene(Scene):
    """
    Platformer Scene class.
    """
    def __init__(self, game):
        super().__init__(game)
        self.player = None
        self.active = True
        self.fsm = None
        self.space = pymunk.Space()
        self.space.gravity = (0, 1000)
        self.sprites = LayeredUpdates()
        self.event_handler = event_handling.EventQueueHandler()
        self.event_handler.print_controls()
        self.background = resources.gfx("background.png", convert=True)
        self.load()
        pygame.mixer.music.load(resources.music_path("zirkus.ogg"))
        pygame.mixer.music.play(-1)

    def add_static(self, vertices, rect):
        """
        Add static object to scene.

        :param vertices:
        :param rect:
        """
        body = pymunk.Body(body_type=pymunk.Body.STATIC)
        body.position = rect.x, rect.y
        shape = pymunk.Poly(body, vertices)
        shape.friction = 1.0
        shape.elasticity = 1.0
        self.space.add(body, shape)

    def load(self):
        """
        Load a scene in TMX format.
        """
        def box_vertices(box_x, box_y, width, height):
            top_left = box_x, box_y
            top_right = box_x + width, box_y
            bottom_right = box_x + width, box_y + height
            bottom_left = box_x, box_y + height
            return top_left, top_right, bottom_right, bottom_left

        filename = path_join("data", "maps", "untitled.tmx")
        tmxdata = pytmx.util_pygame.load_pygame(filename)
        for obj in tmxdata.objects:
            if obj.type == MAP_FIXED:
                rect = Rect(obj.x, obj.y, obj.width, obj.height)
                vertices = box_vertices(0, 0, obj.width, obj.height)
                self.add_static(vertices, rect)

            elif obj.type == MAP_YARN_SPAWN:
                ball = sprite.Ball(Rect((obj.x, obj.y), (32, 32)))
                model = BasicModel()
                model.sprites = [ball]
                model.pymunk_objects = ball.pymunk_shapes
                self.add_model(model)
                self.player = model

            elif obj.type == MAP_PLAYER_SPAWN:
                self.player = unicyclecat.build(self.space, self.sprites)
                self.player.position = obj.x, obj.y

        self.fsm = SimpleFSM(CONTROL, "idle")

    def add_model(self, model):
        """
        Add a model.

        :param model: Model to add.
        """
        self.sprites.add(*model.sprites)
        self.space.add(model.pymunk_objects)

    def remove_model(self, model):
        """
        Remove a model.

        :param model: Model to remove.
        """
        self.sprites.remove(*model.sprites)
        self.space.remove(model.pymunk_objects)

    def render(self):
        """
        Render scene to the screen surface.

        """
        surface = self._game.screen
        surface.blit(self.background, (0, 0))
        self.sprites.draw(surface)
        return [surface.get_rect()]

    def tick(self, time_delta):
        """
        Tick the physics and game update loops.
        """
        step_amount = (1 / 30.0) / 30
        for _ in range(30):
            self.space.step(step_amount)
        self.sprites.update(time_delta=time_delta)

    def event(self, event):
        """
        Process an event.

        :param event: The event to process
        """
        events = self.event_handler.process_event(event)
        position = self.player.position

        for evt in events:
            try:
                cmd, arg = self.fsm((evt.button, evt.held))
            except ValueError:
                continue

            if cmd == "move":
                resources.sfx("cat_wheel.ogg", False, True)
                resources.sfx("cat_wheel.ogg", True)
                self.player.accelerate(arg)

            if cmd == "idle":
                self.player.brake()

            elif cmd == "jump":
                resources.sfx("cat_jump.ogg", True)
                self.player.main_body.apply_impulse_at_world_point((0, -600),
                                                                   position)