コード例 #1
0
    def pos(self):
        # Let character keep in the middle of screen
        pos = Game().role.pos - Vector2(RESOLUTION) / 2

        # 使窗口在地图内
        win = Rect(pos, RESOLUTION)
        win.clamp_ip(self.surf.get_rect())

        # 变换为窗口坐标系
        return -Vector2(win.topleft)
コード例 #2
0
class Camera(object):
    def __init__(self, target, bounds, size):
        self.bounds = bounds
        self.rect = Rect((0, 0), size)

    def update(self, target):
        self.rect.center = target.center
        self.rect.clamp_ip(self.bounds)

    def draw_background(self, surf, bg):
        surf.blit(bg, (-self.rect.x, -self.rect.y))

    def draw_sprite(self, surf, sprite):
        if self.rect.colliderect(sprite.rect):
            surf.blit(sprite.image, rel_rect(sprite.rect, self.rect))
コード例 #3
0
ファイル: camera.py プロジェクト: AKilgore/CS112-Spring2012
class Camera(object):

    def __init__(self, target, bounds, size):
        self.bounds = bounds
        self.rect = Rect((0,0), size)

    def update(self, target):
        self.rect.center = target.center
        self.rect.clamp_ip(self.bounds)

    def draw_background(self, surf, bg):
        surf.blit(bg, (-self.rect.x, -self.rect.y))

    def draw_sprite(self, surf, sprite):
        if self.rect.colliderect(sprite.rect):
            surf.blit(sprite.image, rel_rect(sprite.rect, self.rect))
コード例 #4
0
class Viewport:

    def __init__(self):
        self.viewport = Rect((0, 0), SCREEN_SIZE)
        self.locked = False
        self.destination = None
        self.speed = 0
        self.on_arrival = None

    def center(self, center, inside):
        if not self.locked:
            self.viewport.center = center
            self.viewport.clamp_ip(inside)

    def get(self):
        return self.viewport

    def lock(self):
        self.locked = True

    def unlock(self):
        self.locked = False

    def move_to(self, destination, speed=100, on_arrival=None):
        self.destination = destination
        self.speed = speed
        self.on_arrival = on_arrival

    def move(self, time_passed):
        if self.destination:
            location = Vector(*self.viewport.topleft)
            vec_to_destination = self.destination - location

            distance_to_destination = vec_to_destination.get_magnitude()
            vec_to_destination.normalize()
            travel_distance = min(distance_to_destination, time_passed * self.speed)
            vec_to_destination.x *= travel_distance
            vec_to_destination.y *= travel_distance
            move = location + vec_to_destination
            self.viewport.topleft = move.x, move.y
            if self.destination == move:
                self.destination = None
                self.on_arrival()
                self.on_arrival = None
コード例 #5
0
    def collision_check(self):
        ''' Method for checking if the entity has run into a tree or something
            and move it back a pixel if it has
        '''
        if self.wall_collides:
            # Move the entity inside of the window (border collision)
            entity_rect = Rect(self.x, self.y, self.width,self.height)
            window_rect = Rect(0, 0, g.width * c.TILE_SIZE, g.height * c.TILE_SIZE)
            if not window_rect.contains(entity_rect):
                entity_rect.clamp_ip(window_rect)
                self.x = entity_rect.left
                self.y = entity_rect.top

        if self.collides:
            # Make sure collision rectangles are up to date
            self.update_collision_rects()
            # Get the tile the entity is standing on
            tile_pos = self.get_tile()
            checked_tiles = []
            # Loop through a 3x3 tile square around the entity, to not check the entire map
            for i in range(tile_pos[0] - 1, tile_pos[0] + 2):
                for j in range(tile_pos[1] - 1, tile_pos[1] + 2):
                    try:
                        if c.IMAGES[g.map[i][j].type].collides:
                            checked_tiles.append(g.map[i][j].rect())
                    except IndexError:
                        # That index was apparently outside of the map
                        pass
                    except:
                        raise
                    
            # Check if each of the zones collides with any of the tiles
            if self.col_left.collidelist(checked_tiles) != -1:
                self.x += 1
            if self.col_right.collidelist(checked_tiles) != -1:
                self.x -= 1
            if self.col_bottom.collidelist(checked_tiles) != -1:
                self.y -= 1
            if self.col_top.collidelist(checked_tiles) != -1:
                self.y += 1
コード例 #6
0
ファイル: entities.py プロジェクト: ZeeQyu/TileGame
    def collision_check(self):
        """ Method for checking if the entity has run into a tree or something
            and move it back a pixel if it has
        """
        if self.wall_collides:
            # Move the entity inside of the window (border collision)
            entity_rect = Rect(self.x, self.y, self.width,self.height)
            window_rect = Rect(0, 0, g.width * c.TILE_SIZE, g.height * c.TILE_SIZE)
            if not window_rect.contains(entity_rect):
                entity_rect.clamp_ip(window_rect)
                self.x = entity_rect.left
                self.y = entity_rect.top

        collided = False
        if self.collides:
            # Make sure collision rectangles are up to date
            self.update_collision_rects()
            # Get the tile the entity is standing on
            tile_pos = self.get_tile()
            checked_tiles = []
            # Loop through a 3x3 tile square around the entity, to not check the entire map
            for i in range(tile_pos[0] - 1, tile_pos[0] + 2):
                for j in range(tile_pos[1] - 1, tile_pos[1] + 2):
                    try:
                        if c.IMAGES[g.map[i][j].type].collides:
                            checked_tiles.append(g.map[i][j].rect())
                    except IndexError:
                        # That index was apparently outside of the map
                        pass

            # Check if each of the zones collides with any of the tiles
            # If so, move it in the appropriate direction, specified in update_collision_rects() as the keys
            for rect in self.rects:
                if self.rects[rect].collidelist(checked_tiles) != -1:
                    self.x += rect[0]
                    self.y += rect[1]
                    collided = True

        self.collided = collided
コード例 #7
0
ファイル: camera.py プロジェクト: sgoodspeed/samKeenanGame
class Camera(object):

    def __init__(self, target, bounds, size):
        self.bounds = bounds
        self.rect = Rect((0,0), size)

    def update(self, target):
        self.rect.center = target.center
        self.rect.clamp_ip(self.bounds)

    def draw_background(self, surf, bg):
        surf.blit(bg, (-self.rect.x, -self.rect.y))

    def draw_background_alpha(self, surf, bg, alpha):
        new_bg = bg.copy()
        new_bg.set_alpha(alpha)
        surf.blit(new_bg, (-self.rect.x, -self.rect.y))

    def draw_sprite(self, surf, sprite):
        if self.rect.colliderect(sprite.rect):
            surf.blit(sprite.image, rel_rect(sprite.rect, self.rect))
            
    def draw_sprite_group(self, surf, group):
        for sprite in group:
            if self.rect.colliderect(sprite.rect):
                surf.blit(sprite.image, rel_rect(sprite.rect, self.rect))
    
    def draw_sprite_group_alpha(self, surf, group, alpha):
        for sprite in group:
            if self.rect.colliderect(sprite.rect):
                new_sprite = sprite.image.copy()
                new_sprite.set_alpha(alpha)
                surf.blit(new_sprite, rel_rect(sprite.rect, self.rect))

    def draw_sprite_alpha(self, surf, sprite, alpha):
        if self.rect.colliderect(sprite.rect):
            new_sprite = sprite.image.copy()
            new_sprite.set_alpha(alpha)
            surf.blit(new_sprite, rel_rect(sprite.rect, self.rect))
コード例 #8
0
ファイル: rect_test.py プロジェクト: CTPUG/pygame_cffi
 def test_clamp_ip( self ):
     r = Rect(10, 10, 10, 10)
     c = Rect(19, 12, 5, 5)
     c.clamp_ip(r)
     self.assertEqual(c.right, r.right)
     self.assertEqual(c.top, 12)
     c = Rect(1, 2, 3, 4)
     c.clamp_ip(r)
     self.assertEqual(c.topleft, r.topleft)
     c = Rect(5, 500, 22, 33)
     c.clamp_ip(r)
     self.assertEqual(c.center, r.center)
コード例 #9
0
ファイル: rect_test.py プロジェクト: annie60/Xilarius
 def test_clamp_ip(self):
     r = Rect(10, 10, 10, 10)
     c = Rect(19, 12, 5, 5)
     c.clamp_ip(r)
     self.assertEqual(c.right, r.right)
     self.assertEqual(c.top, 12)
     c = Rect(1, 2, 3, 4)
     c.clamp_ip(r)
     self.assertEqual(c.topleft, r.topleft)
     c = Rect(5, 500, 22, 33)
     c.clamp_ip(r)
     self.assertEqual(c.center, r.center)
コード例 #10
0
class Menu(object):
    """popup_menu.Menu
    Menu(pos, name, items) : return menu

    pos -> (x,y); topleft coordinates of the menu.
    name -> str; the name of the menu.
    items -> list; a list containing strings for menu items labels.

    This class is not intended to be used directly. Use PopupMenu or
    NonBlockingPopupMenu instead, unless designing your own subclass.
    """
    def __init__(self, pos, name, items, gameController):

        self.Game = gameController
        screen = pygame.display.get_surface()
        screen_rect = screen.get_rect()
        self.name = name
        self.items = []
        self.menu_item = None
        # Make the frame rect
        x, y = pos
        self.rect = Rect(x, y, 0, 0)
        self.rect.width += margin * 2
        self.rect.height += margin * 2
        # Make the title image and rect, and grow the frame rect
        TitleFont = pygame.font.SysFont(None, 20)
        TitleFont.bold = True
        self.title_image = TitleFont.render(name, True, text_color)
        self.title_rect = self.title_image.get_rect(topleft=(x + margin,
                                                             y + margin))
        self.rect.width = margin * 2 + self.title_rect.width
        self.rect.height = margin + self.title_rect.height
        # Make the item highlight rect
        self.hi_rect = Rect(0, 0, 0, 0)

        # Make menu items
        n = 0
        for item in items:
            menu_item = MenuItem(item, n)
            self.items.append(menu_item)
            self.rect.width = max(self.rect.width,
                                  menu_item.rect.width + margin * 2)
            self.rect.height += menu_item.rect.height + margin
            n += 1
        self.rect.height += margin

        # Position menu fully within view
        if not screen_rect.contains(self.rect):
            savex, savey = self.rect.topleft
            self.rect.clamp_ip(screen_rect)
            self.title_rect.top = self.rect.top + margin
            self.title_rect.left = self.rect.left + margin

        # Position menu items within menu frame
        y = self.title_rect.bottom + margin
        for item in self.items:
            item.rect.x = self.rect.x + margin
            item.rect.y = y
            y = item.rect.bottom + margin
            item.rect.width = self.rect.width - margin * 2

        # Calculate highlight rect's left-alignment and size
        self.hi_rect.left = menu_item.rect.left
        self.hi_rect.width = self.rect.width - margin * 2
        self.hi_rect.height = menu_item.rect.height

        # Create the menu frame and highlight frame images
        self.bg_image = pygame.transform.scale(HUDConf.PLAYER_ICON_SLOT,
                                               self.rect.size)
        self.hi_image = pygame.surface.Surface(self.hi_rect.size)
        # self.bg_image.fill(bg_color)
        self.hi_image.fill(hi_color)
        # Draw menu border
        rect = self.bg_image.get_rect()
        # pygame.draw.rect(self.bg_image, glint_color, rect, 1)
        t, l, b, r = rect.top, rect.left, rect.bottom, rect.right
        # pygame.draw.line(self.bg_image, shadow_color, (l, b - 1), (r, b - 1), 1)
        # pygame.draw.line(self.bg_image, shadow_color, (r - 1, t), (r - 1, b), 1)
        # Draw title divider in menu frame
        left = margin
        right = self.rect.width - margin * 2
        y = self.title_rect.height + 1
        # pygame.draw.line(self.bg_image, shadow_color, (left, y), (right, y))

    def draw(self):
        # Draw the menu on the main display.

        self.Game.screen.blit(self.bg_image, self.rect)
        self.Game.screen.blit(self.title_image, self.title_rect)
        for item in self.items:
            if item is self.menu_item:
                self.hi_rect.top = item.rect.top
                self.Game.screen.blit(self.hi_image, self.hi_rect)
            self.Game.screen.blit(item.image, item.rect)

    def check_collision(self, mouse_pos):
        # Set self.menu_item if the mouse is hovering over one.
        self.menu_item = None
        if self.rect.collidepoint(mouse_pos):
            for item in self.items:
                if item.rect.collidepoint(mouse_pos):
                    self.menu_item = item
                    break
コード例 #11
0
ファイル: orthographic.py プロジェクト: ri0t/pyscroll
class BufferedRenderer(object):
    """ Renderer that support scrolling, zooming, layers, and animated tiles

    The buffered renderer must be used with a data class to get tile, shape,
    and animation information.  See the data class api in pyscroll.data, or
    use the built-in pytmx support for loading maps created with Tiled.
    """
    def __init__(self, data, size, clamp_camera=True, colorkey=None, alpha=False,
                 time_source=time.time, scaling_function=pygame.transform.scale):

        # default options
        self.map_rect = None                       # pygame rect of entire map
        self.data = data                           # reference to data source
        self.clamp_camera = clamp_camera           # if clamped, cannot scroll past map edge
        self.time_source = time_source             # determines how tile animations are processed
        self.scaling_function = scaling_function   # what function to use when zooming
        self.default_shape_texture_gid = 1         # [experimental] texture to draw shapes with
        self.default_shape_color = 0, 255, 0       # [experimental] color to fill polygons with

        # internal private defaults
        self._alpha = False
        if colorkey and alpha:
            print('cannot select both colorkey and alpha.  choose one.')
            raise ValueError
        elif colorkey:
            self._clear_color = colorkey
        else:
            self._clear_color = None

        # private attributes
        self._size = None             # size that the camera/viewport is on screen, kinda
        self._redraw_cutoff = None    # size of dirty tile edge that will trigger full redraw
        self._x_offset = None         # offsets are used to scroll map in sub-tile increments
        self._y_offset = None
        self._buffer = None           # complete rendering of tilemap
        self._tile_view = None        # this rect represents each tile on the buffer
        self._half_width = None       # 'half x' attributes are used to reduce division ops.
        self._half_height = None
        self._tile_queue = None       # tiles queued to be draw onto buffer
        self._animation_queue = None  # heap queue of animation token.  schedules tile changes
        self._animation_map = None    # map of GID to other GIDs in an animation
        self._last_time = None        # used for scheduling animations
        self._layer_quadtree = None   # used to draw tiles that overlap optional surfaces
        self._zoom_buffer = None      # used to speed up zoom operations
        self._zoom_level = 1.0        # negative numbers make map smaller, positive: bigger

        # this represents the viewable pixels, aka 'camera'
        self.view_rect = Rect(0, 0, 0, 0)

        self.reload_animations()
        self.set_size(size)

    def _update_time(self):
        self._last_time = time.time() * 1000

    def reload_animations(self):
        """ Reload animation information
        """
        self._update_time()
        self._animation_map = dict()
        self._animation_queue = list()

        for gid, frame_data in self.data.get_animations():
            frames = list()
            for frame_gid, frame_duration in frame_data:
                image = self.data.get_tile_image_by_gid(frame_gid)
                frames.append(AnimationFrame(image, frame_duration))

            ani = AnimationToken(gid, frames)
            ani.next += self._last_time
            self._animation_map[ani.gid] = ani.frames[ani.index].image
            heappush(self._animation_queue, ani)

    def _process_animation_queue(self):
        self._update_time()
        requires_redraw = False

        # test if the next scheduled tile change is ready
        while self._animation_queue[0].next <= self._last_time:
            requires_redraw = True
            token = heappop(self._animation_queue)

            # advance the animation index, looping by default
            if token.index == len(token.frames) - 1:
                token.index = 0
            else:
                token.index += 1

            next_frame = token.frames[token.index]
            token.next = next_frame.duration + self._last_time
            self._animation_map[token.gid] = next_frame.image
            heappush(self._animation_queue, token)

        if requires_redraw:
            # TODO: record the tiles that changed and update only affected tiles
            self.redraw_tiles()
            pass

    def _calculate_zoom_buffer_size(self, value):
        if value <= 0:
            print('zoom level cannot be zero or less')
            raise ValueError
        value = 1.0 / value
        return [int(round(i * value)) for i in self._size]

    @property
    def zoom(self):
        """ Zoom the map in or out.

        Increase this number to make map appear to come closer to camera.
        Decrease this number to make map appear to move away from camera.

        Default value is 1.0
        This value cannot be negative or 0.0

        :return: float
        """
        return self._zoom_level

    @zoom.setter
    def zoom(self, value):
        self._zoom_level = value
        buffer_size = self._calculate_zoom_buffer_size(value)
        self._initialize_buffers(buffer_size)

    def set_size(self, size):
        """ Set the size of the map in pixels

        This is an expensive operation, do only when absolutely needed.

        :param size: (width, height) pixel size of camera/view of the group
        """
        self._size = size
        buffer_size = self._calculate_zoom_buffer_size(self._zoom_level)
        self._initialize_buffers(buffer_size)

    def _create_buffers(self, view_size, buffer_size):
        """ Create the buffers, taking in account pixel alpha or colorkey

        :param view_size: pixel size of the view
        :param buffer_size: pixel size of the buffer
        """
        requires_zoom_buffer = not view_size == buffer_size
        self._zoom_buffer = None

        if self._clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.RLEACCEL)
                self._zoom_buffer.set_colorkey(self._clear_color)
            self._buffer = Surface(buffer_size, flags=pygame.RLEACCEL)
            self._buffer.set_colorkey(self._clear_color)
            self._buffer.fill(self._clear_color)
        elif self._alpha:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.SRCALPHA)
            self._buffer = Surface(buffer_size, flags=pygame.SRCALPHA)
        else:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size)
            self._buffer = Surface(buffer_size)

    def _initialize_buffers(self, size):
        """ Create the buffers to cache tile drawing

        :param size: (int, int): size of the draw area
        :return: None
        """
        tw, th = self.data.tile_size
        mw, mh = self.data.map_size
        buffer_tile_width = int(math.ceil(size[0] / tw) + 2)
        buffer_tile_height = int(math.ceil(size[1] / th) + 2)
        buffer_pixel_size = buffer_tile_width * tw, buffer_tile_height * th

        self.map_rect = Rect(0, 0, mw * tw, mh * th)
        self.view_rect.size = size
        self._tile_view = Rect(0, 0, buffer_tile_width, buffer_tile_height)
        self._redraw_cutoff = min(buffer_tile_width, buffer_tile_height)
        self._create_buffers(size, buffer_pixel_size)
        self._half_width = size[0] // 2
        self._half_height = size[1] // 2
        self._x_offset = 0
        self._y_offset = 0

        def make_rect(x, y):
            return Rect((x * tw, y * th), (tw, th))

        rects = [make_rect(*i) for i in product(range(buffer_tile_width),
                                                range(buffer_tile_height))]

        # TODO: figure out what depth -actually- does
        self._layer_quadtree = quadtree.FastQuadTree(rects, 4)

        self.redraw_tiles()

    def scroll(self, vector):
        """ scroll the background in pixels

        :param vector: (int, int)
        """
        self.center((vector[0] + self.view_rect.centerx,
                     vector[1] + self.view_rect.centery))

    def center(self, coords):
        """ center the map on a pixel

        float numbers will be rounded.

        :param coords: (number, number)
        """
        x, y = [round(i, 0) for i in coords]
        self.view_rect.center = x, y

        if self.clamp_camera:
            self.view_rect.clamp_ip(self.map_rect)
            x, y = self.view_rect.center

        # calc the new position in tiles and offset
        tw, th = self.data.tile_size
        left, self._x_offset = divmod(x - self._half_width, tw)
        top, self._y_offset = divmod(y - self._half_height, th)

        # adjust the view if the view has changed without a redraw
        dx = int(left - self._tile_view.left)
        dy = int(top - self._tile_view.top)
        view_change = max(abs(dx), abs(dy))

        if view_change <= self._redraw_cutoff:
            self._buffer.scroll(-dx * tw, -dy * th)
            self._tile_view.move_ip((dx, dy))
            self._queue_edge_tiles(dx, dy)
            self._flush_tile_queue()

        elif view_change > self._redraw_cutoff:
            logger.info('scrolling too quickly.  redraw forced')
            self._tile_view.move_ip((dx, dy))
            self.redraw_tiles()

    def _queue_edge_tiles(self, dx, dy):
        """ Queue edge tiles and clear edge areas on buffer if needed

        :param dx: Edge along X axis to enqueue
        :param dy: Edge along Y axis to enqueue
        :return: None
        """
        v = self._tile_view
        fill = partial(self._buffer.fill, self._clear_color)
        tw, th = self.data.tile_size
        self._tile_queue = iter([])

        def append(rect):
            self._tile_queue = chain(self._tile_queue, self.data.get_tile_images_by_rect(rect))
            if self._clear_color:
                fill(((rect[0] - self._tile_view.left) * tw,
                      (rect[1] - self._tile_view.top) * th,
                      rect[2] * tw, rect[3] * th))

        if dx > 0:    # right side
            append((v.right - dx, v.top, dx, v.height))

        elif dx < 0:  # left side
            append((v.left, v.top, -dx, v.height))

        if dy > 0:    # bottom side
            append((v.left, v.bottom - dy, v.width, dy))

        elif dy < 0:  # top side
            append((v.left, v.top, v.width, -dy))

    def draw(self, surface, rect, surfaces=None):
        """ Draw the map onto a surface

        pass a rect that defines the draw area for:
            drawing to an area smaller that the whole window/screen

        surfaces may optionally be passed that will be blitted onto the surface.
        this must be a list of tuples containing a layer number, image, and
        rect in screen coordinates.  surfaces will be drawn in order passed,
        and will be correctly drawn with tiles from a higher layer overlapping
        the surface.

        surfaces list should be in the following format:
        [ (layer, surface, rect), ... ]

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace into tiles
        """
        if self._zoom_level == 1.0:
            self._render_map(surface, rect, surfaces)
        else:
            self._render_map(self._zoom_buffer, self._zoom_buffer.get_rect(), surfaces)
            self.scaling_function(self._zoom_buffer, rect.size, surface)

    def _render_map(self, surface, rect, surfaces):
        """ Render the map and optional surfaces to destination surface

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace into tiles
        """
        if self._animation_queue:
            self._process_animation_queue()

        if rect.width > self.map_rect.width:
            x = (rect.width - self.map_rect.width) // 4
            print(x)
            self._x_offset += x

        if rect.height > self.map_rect.height:
            pass

        # need to set clipping otherwise the map will draw outside its area
        with surface_clipping_context(surface, rect):
            # draw the entire map to the surface taking in account the scrolling offset
            surface.blit(self._buffer, (-self._x_offset - rect.left,
                                        -self._y_offset - rect.top))
            if surfaces:
                self._draw_surfaces(surface, rect, surfaces)

    def _draw_surfaces(self, surface, rect, surfaces):
        """ Draw surfaces onto map, then redraw tiles that cover them

        :param surface: destination
        :param rect: clipping area
        :param surfaces: sequence of surfaces to blit
        """
        surface_blit = surface.blit
        left, top = self._tile_view.topleft
        ox = self._x_offset - rect.left
        oy = self._y_offset - rect.top
        hit = self._layer_quadtree.hit
        get_tile = self.data.get_tile_image
        tile_layers = tuple(self.data.visible_tile_layers)
        dirty = [(surface_blit(i[0], i[1]), i[2]) for i in surfaces]

        for dirty_rect, layer in dirty:
            for r in hit(dirty_rect.move(ox, oy)):
                x, y, tw, th = r
                for l in [i for i in tile_layers if gt(i, layer)]:
                    tile = get_tile((x // tw + left, y // th + top, l))
                    if tile:
                        surface_blit(tile, (x - ox, y - oy))

    def _draw_objects(self):
        """ Totally unoptimized drawing of objects to the map [probably broken]
        """
        tw, th = self.data.tile_size
        buff = self._buffer
        blit = buff.blit
        map_gid = self.data.tmx.map_gid
        default_color = self.default_shape_color
        get_image_by_gid = self.data.get_tile_image_by_gid
        _draw_textured_poly = pygame.gfxdraw.textured_polygon
        _draw_poly = pygame.draw.polygon
        _draw_lines = pygame.draw.lines

        ox = self._tile_view.left * tw
        oy = self._tile_view.top * th

        def draw_textured_poly(texture, points):
            try:
                _draw_textured_poly(buff, points, texture, tw, th)
            except pygame.error:
                pass

        def draw_poly(color, points, width=0):
            _draw_poly(buff, color, points, width)

        def draw_lines(color, points, width=2):
            _draw_lines(buff, color, False, points, width)

        def to_buffer(pt):
            return pt[0] - ox, pt[1] - oy

        for layer in self.data.visible_object_layers:
            for o in (o for o in layer if o.visible):
                texture_gid = getattr(o, "texture", None)
                color = getattr(o, "color", default_color)

                # BUG: this is not going to be completely accurate, because it
                # does not take into account times where texture is flipped.
                if texture_gid:
                    texture_gid = map_gid(texture_gid)[0][0]
                    texture = get_image_by_gid(int(texture_gid))

                if hasattr(o, 'points'):
                    points = [to_buffer(i) for i in o.points]
                    if o.closed:
                        if texture_gid:
                            draw_textured_poly(texture, points)
                        else:
                            draw_poly(color, points)
                    else:
                        draw_lines(color, points)

                elif o.gid:
                    tile = get_image_by_gid(o.gid)
                    if tile:
                        pt = to_buffer((o.x, o.y))
                        blit(tile, pt)

                else:
                    x, y = to_buffer((o.x, o.y))
                    points = ((x, y), (x + o.width, y),
                              (x + o.width, y + o.height), (x, y + o.height))
                    if texture_gid:
                        draw_textured_poly(texture, points)
                    else:
                        draw_poly(color, points)

    def _flush_tile_queue(self):
        """ Blit the queued tiles and block until the tile queue is empty
        """
        tw, th = self.data.tile_size
        ltw = self._tile_view.left * tw
        tth = self._tile_view.top * th
        blit = self._buffer.blit
        map_get = self._animation_map.get

        for x, y, l, tile, gid in self._tile_queue:
            blit(map_get(gid, tile), (x * tw - ltw, y * th - tth))

    def redraw_tiles(self):
        """ redraw the visible portion of the buffer -- it is slow.
        """
        if self._clear_color:
            self._buffer.fill(self._clear_color)
        elif self._alpha:
            self._buffer.fill(0)

        self._tile_queue = self.data.get_tile_images_by_rect(self._tile_view)
        self._flush_tile_queue()

    def get_center_offset(self):
        """ Return x, y pair that will change world coords to screen coords
        :return: int, int
        """
        return (-self.view_rect.centerx + self._half_width,
                -self.view_rect.centery + self._half_height)
コード例 #12
0
class Menu(object):
    """popup_menu.Menu
    Menu(pos, name, items) : return menu

    pos -> (x,y); topleft coordinates of the menu.
    name -> str; the name of the menu.
    items -> list; a list containing strings for menu items labels.

    This class is not intended to be used directly. Use PopupMenu or
    NonBlockingPopupMenu instead, unless designing your own subclass.
    """
    
    def __init__(self, pos, name, items):
        screen = pygame.display.get_surface()
        screen_rect = screen.get_rect()
        self.name = name
        self.items = []
        self.menu_item = None
        # Make the frame rect
        x,y = pos
        self.rect = Rect(x,y,0,0)
        self.rect.width += margin * 2
        self.rect.height += margin * 2
        # Make the title image and rect, and grow the frame rect
        self.title_image = font.render(name, True, text_color)
        self.title_rect = self.title_image.get_rect(topleft=(x+margin,y+margin))
        self.rect.width = margin*2 + self.title_rect.width
        self.rect.height = margin + self.title_rect.height
        # Make the item highlight rect
        self.hi_rect = Rect(0,0,0,0)

        # Make menu items
        n = 0
        for item in items:
            menu_item = MenuItem(item, n)
            self.items.append(menu_item)
            self.rect.width = max(self.rect.width, menu_item.rect.width+margin*2)
            self.rect.height += menu_item.rect.height + margin
            n += 1
        self.rect.height += margin
        
        # Position menu fully within view
        if not screen_rect.contains(self.rect):
            savex,savey = self.rect.topleft
            self.rect.clamp_ip(screen_rect)
            self.title_rect.top = self.rect.top + margin
            self.title_rect.left = self.rect.left + margin
        
        # Position menu items within menu frame
        y = self.title_rect.bottom + margin
        for item in self.items:
            item.rect.x = self.rect.x + margin
            item.rect.y = y
            y = item.rect.bottom + margin
            item.rect.width = self.rect.width - margin*2
        
        # Calculate highlight rect's left-alignment and size
        self.hi_rect.left = menu_item.rect.left
        self.hi_rect.width = self.rect.width - margin*2
        self.hi_rect.height = menu_item.rect.height

        # Create the menu frame and highlight frame images
        self.bg_image = pygame.surface.Surface(self.rect.size)
        self.hi_image = pygame.surface.Surface(self.hi_rect.size)
        self.bg_image.fill(bg_color)
        self.hi_image.fill(hi_color)
        # Draw menu border
        rect = self.bg_image.get_rect()
        pygame.draw.rect(self.bg_image, glint_color, rect, 1)
        t,l,b,r = rect.top,rect.left,rect.bottom,rect.right
        pygame.draw.line(self.bg_image, shadow_color, (l,b-1), (r,b-1), 1)
        pygame.draw.line(self.bg_image, shadow_color, (r-1,t), (r-1,b), 1)
        # Draw title divider in menu frame
        left = margin
        right = self.rect.width - margin*2
        y = self.title_rect.height + 1
        pygame.draw.line(self.bg_image, shadow_color, (left,y), (right,y))

    def draw(self):
        # Draw the menu on the main display.
        screen = pygame.display.get_surface()
        screen.blit(self.bg_image, self.rect)
        screen.blit(self.title_image, self.title_rect)
        for item in self.items:
            if item is self.menu_item:
                self.hi_rect.top = item.rect.top
                screen.blit(self.hi_image, self.hi_rect)
            screen.blit(item.image, item.rect)

    def check_collision(self, mouse_pos):
        # Set self.menu_item if the mouse is hovering over one.
        self.menu_item = None
        if self.rect.collidepoint(mouse_pos):
            for item in self.items:
                if item.rect.collidepoint(mouse_pos):
                    self.menu_item = item
                    break
コード例 #13
0
                scale /= 1.1
            else:
                redraw = False
        
    if redraw:
        if scale < screen_size[0]/env_size[0]:
            scale = screen_size[0]/env_size[0]
        if scale > 16000/env_size[0]:
            scale = 16000/env_size[0]
        size = (int(env_size[0]*scale), int(env_size[1]*scale))
        background = Surface(size)
        pygame.draw.rect(background, (255,255,255), Rect((0,0), size))
        tree.draw(background, pygame.font.SysFont("Calibri", int(11*scale)), size, scale)
        
        mouse_pose = pygame.mouse.get_pos()
        background_mouse_pose = (mouse_pose[0]-background_dimensions.left, mouse_pose[1]-background_dimensions.top)
        current_scale = scale/prev_scale
        background_dimensions.move_ip(int(-background_mouse_pose[0]*(current_scale-1)), int(-background_mouse_pose[1]*(current_scale-1)))
        background_dimensions.size = size
        
        x = background_dimensions.width-2000
        y = background_dimensions.height-750
        background_dimensions.clamp_ip(Rect(-x, -y, 2000+2*x, 750+2*y))
        
        screen.blit(background, background_dimensions)
        pygame.display.update()
        prev_scale = scale
        
pygame.quit()

コード例 #14
0
ファイル: orthographic.py プロジェクト: induane/harrenrpg
class BufferedRenderer:
    """
    Renderer that support scrolling, zooming, layers, and animated tiles

    The buffered renderer must be used with a data class to get tile, shape,
    and animation information.  See the data class api in pyscroll.data, or
    use the built-in pytmx support for loading maps created with Tiled.

    NOTE: colorkey and alpha transparency is quite slow
    """

    _rgba_clear_color = 0, 0, 0, 0
    _rgb_clear_color = 0, 0, 0

    def __init__(self,
                 data,
                 size,
                 clamp_camera=True,
                 colorkey=None,
                 alpha=False,
                 time_source=time.time,
                 scaling_function=transform.scale,
                 tall_sprites=0,
                 **kwargs):

        bg_fill = kwargs.get("background_color")
        if bg_fill:
            self._rgb_clear_color = bg_fill
            self._rgba_clear_color = bg_fill

        # default options
        self.data = data  # reference to data source
        self.clamp_camera = clamp_camera  # if true, cannot scroll past map edge
        self.time_source = time_source  # determines how tile animations are processed
        self.scaling_function = scaling_function  # what function to use when scaling the zoom buffer
        self.tall_sprites = tall_sprites  # correctly render tall sprites
        self.map_rect = None  # pygame rect of entire map

        # Tall Spritesthat's
        # this value, if greater than 0, is the number of pixels from the bottom of
        # tall sprites which is compared against the bottom of a tile on the same
        # layer of the sprite.  In other words, if set, it prevents tiles from being
        # drawn over sprites which are taller than the tile height.  The value that
        # is how far apart the sprites have to be before drawing the tile over.
        # Reasonable values are about 10% of the tile height
        # This feature only works for the first layer over the tall sprite, all
        # other layers will be drawn over the tall sprite.

        # internal private defaults
        if colorkey and alpha:
            LOG.error("cannot select both colorkey and alpha.  choose one.")
            raise ValueError
        elif colorkey:
            self._clear_color = colorkey
        elif alpha:
            self._clear_color = self._rgba_clear_color
        else:
            self._clear_color = None

        # private attributes
        self._anchored_view = True  # if true, map is fixed to upper left corner
        self._previous_blit = None  # rect of the previous map blit when map edges are visible
        self._size = None  # actual pixel size of the view, as it occupies the screen
        self._redraw_cutoff = None  # size of dirty tile edge that will trigger full redraw
        self._x_offset = None  # offsets are used to scroll map in sub-tile increments
        self._y_offset = None
        self._buffer = None  # complete rendering of tilemap
        self._tile_view = None  # this rect represents each tile on the buffer
        self._half_width = None  # 'half x' attributes are used to reduce division ops.
        self._half_height = None
        self._tile_queue = None  # tiles queued to be draw onto buffer
        self._animation_queue = None  # heap queue of animation token;  schedules tile changes
        self._layer_quadtree = None  # used to draw tiles that overlap optional surfaces
        self._zoom_buffer = None  # used to speed up zoom operations
        self._zoom_level = 1.0  # negative numbers make map smaller, positive: bigger
        self._real_ratio_x = 1.0  # zooming slightly changes aspect ratio; this compensates
        self._real_ratio_y = 1.0  # zooming slightly changes aspect ratio; this compensates
        self.view_rect = Rect(0, 0, 0,
                              0)  # this represents the viewable map pixels

        if hasattr(Surface, "blits"):
            self._flush_tile_queue = self._flush_tile_queue_blits

        self.set_size(size)

    def scroll(self, vector):
        """scroll the background in pixels

        :param vector: (int, int)
        """
        self.center((vector[0] + self.view_rect.centerx,
                     vector[1] + self.view_rect.centery))

    def center(self, coords):
        """center the map on a pixel

        float numbers will be rounded.

        :param coords: (number, number)
        """
        x, y = round(coords[0]), round(coords[1])
        self.view_rect.center = x, y

        mw, mh = self.data.map_size
        tw, th = self.data.tile_size
        vw, vh = self._tile_view.size

        # prevent camera from exposing edges of the map
        if self.clamp_camera:
            self._anchored_view = True
            self.view_rect.clamp_ip(self.map_rect)
            x, y = self.view_rect.center

        # calc the new position in tiles and pixel offset
        left, self._x_offset = divmod(x - self._half_width, tw)
        top, self._y_offset = divmod(y - self._half_height, th)
        right = left + vw
        bottom = top + vh

        if not self.clamp_camera:
            # not anchored, so the rendered map is being offset by values larger
            # than the tile size.  this occurs when the edges of the map are inside
            # the screen.  a situation like is shows a background under the map.
            self._anchored_view = True
            dx = int(left - self._tile_view.left)
            dy = int(top - self._tile_view.top)

            if mw < vw or left < 0:
                left = 0
                self._x_offset = x - self._half_width
                self._anchored_view = False

            elif right > mw:
                left = mw - vw
                self._x_offset += dx * tw
                self._anchored_view = False

            if mh < vh or top < 0:
                top = 0
                self._y_offset = y - self._half_height
                self._anchored_view = False

            elif bottom > mh:
                top = mh - vh
                self._y_offset += dy * th
                self._anchored_view = False

        # adjust the view if the view has changed without a redraw
        dx = int(left - self._tile_view.left)
        dy = int(top - self._tile_view.top)
        view_change = max(abs(dx), abs(dy))

        if view_change and (view_change <= self._redraw_cutoff):
            self._buffer.scroll(-dx * tw, -dy * th)
            self._tile_view.move_ip(dx, dy)
            self._queue_edge_tiles(dx, dy)
            self._flush_tile_queue(self._buffer)

        elif view_change > self._redraw_cutoff:
            LOG.info("scrolling too quickly.  redraw forced")
            self._tile_view.move_ip(dx, dy)
            self.redraw_tiles(self._buffer)

    def draw(self, surface, rect, surfaces=None):
        """Draw the map onto a surface

        pass a rect that defines the draw area for:
            drawing to an area smaller that the whole window/screen

        surfaces may optionally be passed that will be blitted onto the surface.
        this must be a sequence of tuples containing a layer number, image, and
        rect in screen coordinates.  surfaces will be drawn in order passed,
        and will be correctly drawn with tiles from a higher layer overlapping
        the surface.

        surfaces list should be in the following format:
        [ (layer, surface, rect), ... ]

        or this:
        [ (layer, surface, rect, blendmode_flags), ... ]

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        :return rect: area that was drawn over
        """
        if self._zoom_level == 1.0:
            self._render_map(surface, rect, surfaces)
        else:
            self._render_map(self._zoom_buffer, self._zoom_buffer.get_rect(),
                             surfaces)
            self.scaling_function(self._zoom_buffer, rect.size, surface)
        return self._previous_blit.copy()

    @property
    def zoom(self):
        """Zoom the map in or out.

        Increase this number to make map appear to come closer to camera.
        Decrease this number to make map appear to move away from camera.

        Default value is 1.0
        This value cannot be negative or 0.0

        :return: float
        """
        return self._zoom_level

    @zoom.setter
    def zoom(self, value):
        zoom_buffer_size = self._calculate_zoom_buffer_size(self._size, value)
        self._zoom_level = value
        self._initialize_buffers(zoom_buffer_size)

        zoom_buffer_size = self._zoom_buffer.get_size()
        self._real_ratio_x = float(self._size[0]) / zoom_buffer_size[0]
        self._real_ratio_y = float(self._size[1]) / zoom_buffer_size[1]

    def set_size(self, size):
        """Set the size of the map in pixels

        This is an expensive operation, do only when absolutely needed.

        :param size: (width, height) pixel size of camera/view of the group
        """
        buffer_size = self._calculate_zoom_buffer_size(size, self._zoom_level)
        self._size = size
        self._initialize_buffers(buffer_size)

    def redraw_tiles(self, surface):
        """Redraw the visible portion of the buffer -- this is slow."""
        # TODO/BUG: Redraw animated tiles correctly.  They are getting reset here
        LOG.debug("pyscroll buffer redraw")
        self._clear_surface(self._buffer)
        self._tile_queue = self.data.get_tile_images_by_rect(self._tile_view)
        self._flush_tile_queue(surface)

    def get_center_offset(self):
        """Return x, y pair that will change world coords to screen coords
        :return: int, int
        """
        return (-self.view_rect.centerx + self._half_width,
                -self.view_rect.centery + self._half_height)

    def translate_point(self, point):
        """
        Translate world coordinates and return screen coordinates.

        Respects zoom level. Will be returned as tuple.

        :rtype: tuple
        """
        mx, my = self.get_center_offset()
        if self._zoom_level == 1.0:
            return point[0] + mx, point[1] + my
        else:
            return int(round((point[0] + mx)) * self._real_ratio_x), int(
                round((point[1] + my) * self._real_ratio_y))

    def translate_rect(self, rect):
        """Translate rect position and size to screen coordinates.  Respects zoom level.

        :rtype: Rect
        """
        mx, my = self.get_center_offset()
        rx = self._real_ratio_x
        ry = self._real_ratio_y
        x, y, w, h = rect
        if self._zoom_level == 1.0:
            return Rect(x + mx, y + my, w, h)
        else:
            return Rect(round((x + mx) * rx), round((y + my) * ry),
                        round(w * rx), round(h * ry))

    def translate_points(self, points):
        """Translate coordinates and return screen coordinates

        Will be returned in order passed as tuples.

        :return: list
        """
        retval = []
        append = retval.append
        sx, sy = self.get_center_offset()
        if self._zoom_level == 1.0:
            for c in points:
                append((c[0] + sx, c[1] + sy))
        else:
            rx = self._real_ratio_x
            ry = self._real_ratio_y
            for c in points:
                append((int(round(
                    (c[0] + sx) * rx)), int(round((c[1] + sy) * ry))))
        return retval

    def translate_rects(self, rects):
        """Translate rect position and size to screen coordinates.  Respects zoom level.

        Will be returned in order passed as Rects.

        :return: list
        """
        retval = []
        append = retval.append
        sx, sy = self.get_center_offset()
        if self._zoom_level == 1.0:
            for r in rects:
                x, y, w, h = r
                append(Rect(x + sx, y + sy, w, h))
        else:
            rx = self._real_ratio_x
            ry = self._real_ratio_y
            for r in rects:
                x, y, w, h = r
                append(
                    Rect(round((x + sx) * rx), round((y + sy) * ry),
                         round(w * rx), round(h * ry)))
        return retval

    def _render_map(self, surface, rect, surfaces):
        """Render the map and optional surfaces to destination surface

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        """
        self._tile_queue = self.data.process_animation_queue(self._tile_view)
        self._tile_queue and self._flush_tile_queue(self._buffer)

        # TODO: could maybe optimize to remove just the edges, ideally by drawing lines
        # if not self.anchored_view:
        #     surface.fill(self._clear_color, self._previous_blit)
        if not self._anchored_view:
            self._clear_surface(surface, self._previous_blit)

        offset = -self._x_offset + rect.left, -self._y_offset + rect.top

        with surface_clipping_context(surface, rect):
            self._previous_blit = surface.blit(self._buffer, offset)
            if surfaces:
                surfaces_offset = -offset[0], -offset[1]
                self._draw_surfaces(surface, surfaces_offset, surfaces)

    def _clear_surface(self, surface, rect=None):
        """Clear the buffer, taking in account colorkey or alpha

        :return:
        """
        clear_color = self._rgb_clear_color if self._clear_color is None else self._clear_color
        surface.fill(clear_color, rect)

    def _draw_surfaces(self, surface, offset, surfaces):
        """Draw surfaces onto buffer, then redraw tiles that cover them

        :param surface: destination
        :param offset: offset to compensate for buffer alignment
        :param surfaces: sequence of surfaces to blit
        """
        surface_blit = surface.blit
        ox, oy = offset
        left, top = self._tile_view.topleft
        hit = self._layer_quadtree.hit
        get_tile = self.data.get_tile_image
        tile_layers = tuple(self.data.visible_tile_layers)
        dirty = []
        dirty_append = dirty.append

        # TODO: check to avoid sorting overhead
        # sort layers, then the y value
        def sprite_sort(i):
            return i[2], i[1][1] + i[0].get_height()

        surfaces.sort(key=sprite_sort)

        layer_getter = itemgetter(2)
        for layer, group in groupby(surfaces, layer_getter):
            del dirty[:]

            for i in group:
                try:
                    flags = i[3]
                except IndexError:
                    dirty_append(surface_blit(i[0], i[1]))
                else:
                    dirty_append(surface_blit(i[0], i[1], None, flags))

            # TODO: make set of covered tiles, in the case where a cluster
            # of sprite surfaces causes excessive over tile overdrawing
            for dirty_rect in dirty:
                for r in hit(dirty_rect.move(ox, oy)):
                    x, y, tw, th = r
                    for l in [i for i in tile_layers if gt(i, layer)]:

                        if self.tall_sprites and l == layer + 1:
                            if y - oy + th <= dirty_rect.bottom - self.tall_sprites:
                                continue

                        tile = get_tile(x // tw + left, y // th + top, l)
                        tile and surface_blit(tile, (x - ox, y - oy))

    def _queue_edge_tiles(self, dx, dy):
        """Queue edge tiles and clear edge areas on buffer if needed

        :param dx: Edge along X axis to enqueue
        :param dy: Edge along Y axis to enqueue
        :return: None
        """
        v = self._tile_view
        tw, th = self.data.tile_size
        self._tile_queue = iter([])

        def append(rect):
            self._tile_queue = chain(self._tile_queue,
                                     self.data.get_tile_images_by_rect(rect))
            # TODO: optimize so fill is only used when map is smaller than buffer
            self._clear_surface(
                self._buffer,
                ((rect[0] - v.left) * tw,
                 (rect[1] - v.top) * th, rect[2] * tw, rect[3] * th))

        if dx > 0:  # right side
            append((v.right - 1, v.top, dx, v.height))

        elif dx < 0:  # left side
            append((v.left, v.top, -dx, v.height))

        if dy > 0:  # bottom side
            append((v.left, v.bottom - 1, v.width, dy))

        elif dy < 0:  # top side
            append((v.left, v.top, v.width, -dy))

    @staticmethod
    def _calculate_zoom_buffer_size(size, value):
        if value <= 0:
            LOG.error("zoom level cannot be zero or less")
            raise ValueError
        value = 1.0 / value
        return int(size[0] * value), int(size[1] * value)

    def _create_buffers(self, view_size, buffer_size):
        """Create the buffers, taking in account pixel alpha or colorkey

        :param view_size: pixel size of the view
        :param buffer_size: pixel size of the buffer
        """
        requires_zoom_buffer = not view_size == buffer_size
        self._zoom_buffer = None

        if self._clear_color is None:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size)
            self._buffer = Surface(buffer_size)
        elif self._clear_color == self._rgba_clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=SRCALPHA)
            self._buffer = Surface(buffer_size, flags=SRCALPHA)
            self.data.convert_surfaces(self._buffer, True)
        elif self._clear_color is not self._rgb_clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=RLEACCEL)
                self._zoom_buffer.set_colorkey(self._clear_color)
            self._buffer = Surface(buffer_size, flags=RLEACCEL)
            self._buffer.set_colorkey(self._clear_color)
            self._buffer.fill(self._clear_color)

    def _initialize_buffers(self, view_size):
        """Create the buffers to cache tile drawing

        :param view_size: (int, int): size of the draw area
        :return: None
        """
        def make_rect(x, y):
            return Rect((x * tw, y * th), (tw, th))

        tw, th = self.data.tile_size
        mw, mh = self.data.map_size
        buffer_tile_width = int(math.ceil(view_size[0] / tw) + 1)
        buffer_tile_height = int(math.ceil(view_size[1] / th) + 1)
        buffer_pixel_size = buffer_tile_width * tw, buffer_tile_height * th

        self.map_rect = Rect(0, 0, mw * tw, mh * th)
        self.view_rect.size = view_size
        self._previous_blit = Rect(self.view_rect)
        self._tile_view = Rect(0, 0, buffer_tile_width, buffer_tile_height)
        self._redraw_cutoff = 1  # TODO: optimize this value
        self._create_buffers(view_size, buffer_pixel_size)
        self._half_width = view_size[0] // 2
        self._half_height = view_size[1] // 2
        self._x_offset = 0
        self._y_offset = 0

        rects = [
            make_rect(*i) for i in product(range(buffer_tile_width),
                                           range(buffer_tile_height))
        ]

        # TODO: figure out what depth -actually- does
        # values <= 8 tend to reduce performance
        self._layer_quadtree = FastQuadTree(rects, 4)

        self.redraw_tiles(self._buffer)

    def _flush_tile_queue(self, surface):
        """Blit the queued tiles and block until the tile queue is empty"""
        tw, th = self.data.tile_size
        ltw = self._tile_view.left * tw
        tth = self._tile_view.top * th
        surface_blit = surface.blit

        self.data.prepare_tiles(self._tile_view)

        for x, y, l, image in self._tile_queue:
            surface_blit(image, (x * tw - ltw, y * th - tth))

    def _flush_tile_queue_blits(self, surface):
        """Blit the queued tiles and block until the tile queue is empty

        for pygame 1.9.4 +
        """
        tw, th = self.data.tile_size
        ltw = self._tile_view.left * tw
        tth = self._tile_view.top * th

        self.data.prepare_tiles(self._tile_view)

        blit_list = [(image, (x * tw - ltw, y * th - tth))
                     for x, y, l, image in self._tile_queue]
        surface.blits(blit_list)
コード例 #15
0
class Player(pygame.sprite.DirtySprite):
    """Class representing individual players' avatar, their attributes, and movement
       :param direction_facing: the direction the player is facing (0 for left and 1 for right)
       :param sword_height: the height at which the player is holding the sword (0 means no sword, 3 is high, 2 is med, 1 is low sword)
       :param is_ghost: True if the player has respawned as a ghost and False if the player is not currently a ghost
       :param is_locked_on: True if camera is following this player, False otherwise
       :param sprite: the image name for the player's current motion
       :param image_dict: a dictionary of image file names for each motion
    """
    def __init__(self, x_pos, y_pos, direction_facing, sword_height, is_ghost,
                 is_locked_on, image_dict, win_direction):
        pygame.sprite.DirtySprite.__init__(self)
        self._player_state = {
            "running": False,
            "jumping": False,
            "ducking": False,
            "thrusting": False,
            "on_right_wall": False,
            "on_left_wall": False,
            "on_ground": False,
            "locked_on": False,
            "ghost": False,
            "ghost_counter": -1,
            "sword_height": 2,
            "direction_facing": direction_facing,
            "air_time": 0,
            "x_velocity": 0,
            "y_velocity": 0,
            "count_until_turn_around": 0,
            "sword": True,
            "sword_moving": True,
            "run_counter": 0,
            "ignore_gravity": False,
            "player_won": False,
            "win_direction": win_direction  #-1 left, 1 right
        }
        self._image_dict = image_dict
        self.image = pygame.image.load(self.getSprite()).convert_alpha()
        self.rect = Rect(x_pos + image_shift_amount_x,
                         y_pos - image_shift_amount_y, 73, 140)

    def getXPos(self):
        return self.rect.x

    def getYPos(self):
        return self.rect.y

    def moveLeft(self):
        if (self.getPlayerState("ghost_counter") > 150
                or self.getPlayerState("ghost_counter") == -1):
            self.setPlayerState("x_velocity",
                                self.getPlayerState("x_velocity") - 1.25)
            if abs(self.getPlayerState("x_velocity")) > 5:
                self._player_state["count_until_turn_around"] += 1
            else:
                self._player_state["count_until_turn_around"] -= 4
            if self._player_state["count_until_turn_around"] > 15:
                self.setDirection("left")
                self._player_state["count_until_turn_around"] = 20
                self.setPlayerState("running", True)
            elif self._player_state["count_until_turn_around"] < 0:
                self._player_state["count_until_turn_around"] = 0
            if self.getPlayerState("x_velocity") < -12:
                self.setPlayerState("x_velocity", -12)
            if self.getPlayerState(
                    "ducking") and self.getPlayerState("x_velocity") < -3:
                self.setPlayerState("x_velocity", -3)
        else:
            self.setPlayerState("x_velocity", 0)

    def moveRight(self):
        if (self.getPlayerState("ghost_counter") > 150
                or self.getPlayerState("ghost_counter") == -1):
            self.setPlayerState("x_velocity",
                                self.getPlayerState("x_velocity") + 1.25)
            if abs(self.getPlayerState("x_velocity")) > 5:
                self._player_state["count_until_turn_around"] += 1
            else:
                self._player_state["count_until_turn_around"] -= 4
            if self._player_state["count_until_turn_around"] > 15:
                self.setDirection("right")
                self._player_state["count_until_turn_around"] = 20
                self.setPlayerState("running", True)
            elif self._player_state["count_until_turn_around"] < 0:
                self._player_state["count_until_turn_around"] = 0

            if self.getPlayerState("x_velocity") > 12:
                self.setPlayerState("x_velocity", 12)
            if self.getPlayerState(
                    "ducking") and self.getPlayerState("x_velocity") > 3:
                self.setPlayerState("x_velocity", 3)
        else:
            self.setPlayerState("x_velocity", 0)

    def standingStill(self):
        if self.getPlayerState("x_velocity") > 0:
            self.setPlayerState("x_velocity",
                                self.getPlayerState("x_velocity") / 5)
        elif self.getPlayerState("x_velocity") < 0:
            self.setPlayerState("x_velocity",
                                self.getPlayerState("x_velocity") / 5)
        if abs(self.getPlayerState("x_velocity")) < .1:
            self.setPlayerState("x_velocity", 0)
        if abs(self.getPlayerState("x_velocity")) < 1:
            self._player_state["count_until_turn_around"] -= 4
        if self._player_state["count_until_turn_around"] > 15:
            self.setDirection("right")
        elif self._player_state["count_until_turn_around"] < 0:
            self._player_state["count_until_turn_around"] = 0

    def calculateGravity(self, time):
        if self.getPlayerState("air_time") == 0:
            self.setPlayerState("air_time", time)
        self.setPlayerState(
            "y_velocity",
            self.getPlayerState("y_velocity") + 5 *
            (time - self.getPlayerState("air_time")))
        if self.getPlayerState("y_velocity") > 50:
            self.setPlayerState("y_velocity", 50)
        if self.getPlayerState("on_ground") or self.getPlayerState(
                "ignore_gravity"):
            self.setPlayerState("y_velocity", 0)
        #if self.getPlayerState("ignore_gravity"):
        #    self.setPlayerState("y_velocity", 0)

    def jump(self, time):
        self.setPlayerState("y_velocity", -14)
        self.setPlayerState("air_time", time)
        self.setPlayerState("on_ground", False)

    def update(self):
        #print(self.getSprite())
        self.image = pygame.image.load(self.getSprite())
        self.dirty = 1  # force redraw from image, since we moved the sprite rect

    def getPlayerState(self, type):
        return self._player_state[type]

    def setPlayerState(self, type, value):
        self._player_state[type] = value

    def setDirection(self, direction):
        if (self.getPlayerState("ghost_counter") > 150
                or self.getPlayerState("ghost_counter") == -1):
            if direction == "left":
                self._player_state["direction_facing"] = 1
            else:
                self._player_state["direction_facing"] = 0

    def getDirection(self):
        if self._player_state["direction_facing"] == 1:
            return "left"
        else:
            return "right"

    def adjustSwordHeight(self, adjustment):
        self._player_state["sword_height"] += adjustment
        if self._player_state["sword_height"] < 1:
            self._player_state["sword_height"] = 1
        elif self._player_state["sword_height"] > 3:
            self._player_state["sword_height"] = 3

    def respawn(self, startx, starty):
        if self._player_state["win_direction"] == -1:
            self.rect.x = startx + 314
            self.rect.y = starty - 150
        else:
            self.rect.x = startx - 386
            self.rect.y = starty - 150

    def getSprite(self):
        #print(self._player_state["ghost_counter"])
        if self._player_state["ghost"]:
            front = "ghost_"
        else:
            front = ""
        if self._player_state["direction_facing"] == 1:
            append = "_l"
        else:
            append = "_r"
        if not self._player_state["sword"]:
            append = append + "_nosword"
        if self._player_state["ghost_counter"] >= 0 and self._player_state[
                "ghost_counter"] < 50:
            return self._image_dict[front + "dead" + append + "_1"]
        elif self._player_state["ghost_counter"] >= 50 and self._player_state[
                "ghost_counter"] < 100:
            return self._image_dict[front + "dead" + append + "_2"]
        elif self._player_state["ghost_counter"] >= 100 and self._player_state[
                "ghost_counter"] < 150:
            return self._image_dict[front + "dead" + append + "_3"]

        else:
            if self._player_state["ghost_counter"] == 151:
                self._player_state["ghost"] = True
            if self._player_state["running"]:
                self._player_state[
                    "run_counter"] = self._player_state["run_counter"] + 1
                if self._player_state["run_counter"] == 22:
                    self._player_state["run_counter"] = 1
                if self._player_state["run_counter"] > 14:
                    run = "_1"
                elif self._player_state["run_counter"] > 7:
                    run = "_2"
                else:
                    run = "_3"
                return self._image_dict[front + "run" + append + run]
            if self._player_state["jumping"]:
                return self._image_dict[front + "jump" + append]
            if self._player_state["ducking"]:
                return self._image_dict[front + "duck" + append]
            if self._player_state["sword_height"] == 1:
                append = "_low" + append
            elif self._player_state["sword_height"] == 2:
                append = "_med" + append
            else:
                append = "_high" + append
            if self._player_state["thrusting"]:
                return self._image_dict[front + "thrust" + append]
            else:
                return self._image_dict[front + "sword" + append]

    def getImageDict(self):
        return self._image_dict

    def setImageDict(self, image_dict):
        self._image_dict = image_dict

    def getCollisionRect(self):
        return Rect(self.rect.x + image_shift_amount_x,
                    self.rect.y - image_shift_amount_y, 73, 140)

    def debug(self):
        print("x_vel: ", end="")
        print(self.getPlayerState("x_velocity"), end=" ")
        print("y_vel: ", end="")
        print(self.getPlayerState("y_velocity"), end=" ")
        print("x_cord: ", end="")
        print(self.rect.x, end=" ")
        print("y_cord: ", end="")
        print(self.rect.y)
        print("count: ", end="")
        print(self.getPlayerState("count_until_turn_around"))

    def move(self, entities,
             camera):  #TODO Check for being stabbed in this method

        self.setPlayerState("on_ground", False)
        self.setPlayerState("on_right_wall", False)
        self.setPlayerState("on_left_wall", False)

        if self.getPlayerState("ducking"):
            self.setPlayerState("running", False)

        ghost_multiplier = 1
        if self.getPlayerState("ghost"):
            ghost_multiplier = 1.5

        collision_list = self.test_collision_X(
            entities,
            camera)  # Test all entities on the map for collision with player
        self.rect.x += (self.getPlayerState("x_velocity")) * ghost_multiplier
        for objects in collision_list:
            if self.getPlayerState("x_velocity") == 0 and (
                    self._player_state["ghost_counter"] < 0
                    or self._player_state["ghost_counter"] > 150):
                self._player_state["ghost_counter"] = 0
            if self.getPlayerState("x_velocity") < 0:  # Moving left
                self.rect.left = objects.right - image_shift_amount_x
                self.setPlayerState("on_left_wall", True)
            elif self.getPlayerState("x_velocity") > 0:  # Moving right
                self.rect.right = objects.left - image_shift_amount_x
                self.setPlayerState("on_right_wall", True)
        # Lock player to look at other player when standing still or moving short time.
        # Flip player if they have been moving a certain amount of time.

        collision_list = self.test_collision_Y(entities, camera)
        self.rect.y += (self.getPlayerState("y_velocity"))
        for objects in collision_list:
            if self.getPlayerState("y_velocity") < 0:  # Moving up
                self.rect.top = objects.bottom - image_shift_amount_y
            elif self.getPlayerState("y_velocity") > 0:  # Moving down
                self.rect.bottom = objects.top + image_shift_amount_y
                self.setPlayerState("on_ground", True)

    #screen border keeps player on screens
        if self._player_state["ghost_counter"] < 0 or self._player_state[
                "ghost_counter"] >= 150:  # if player is not dying
            screen_rect = camera.getScreenRect()
            offset = camera.getOffset()
            screen_rect = pygame.Rect(screen_rect.left + offset[0], 0, 1000,
                                      600)

            if not screen_rect.contains(self.rect):
                if (self._player_state["on_right_wall"]
                        or self._player_state["on_left_wall"]):
                    self._player_state["ghost_counter"] = 0
                else:
                    self.rect.clamp_ip(
                        screen_rect)  # makes player stay on screen

        self.update()  # updates players position

    def test_collision_Y(self, entities, camera):
        collision_list = []
        self.rect.y += (self.getPlayerState("y_velocity"))
        length = len(entities)
        for objects in entities:
            if self.getCollisionRect().colliderect(objects.getRect()):
                if objects.getEffects()["Kill"] and (
                        self.getPlayerState("ghost_counter") == -1
                        or self.getPlayerState("ghost_counter") > 151):
                    if self.getPlayerState(
                            "win_direction") == camera.getTarget(
                            ).getPlayerState("win_direction"):
                        camera.setActive(False)
                    self.setPlayerState("ghost_counter", 0)
                    self.setPlayerState("sword", True)
                elif objects.getEffects()["P2Flag"]:
                    if self.getPlayerState(
                            "win_direction"
                    ) == -1 and not self.getPlayerState("ghost"):
                        self.setPlayerState("player_won", True)
                elif objects.getEffects()["P1Flag"]:
                    if self.getPlayerState(
                            "win_direction"
                    ) == 1 and not self.getPlayerState("ghost"):
                        self.setPlayerState("player_won", True)
                if length <= 2:
                    pass
                else:
                    collision_list.append(objects.getRect())
            length = length - 1
        self.rect.y -= (self.getPlayerState("y_velocity"))
        return collision_list

    def test_collision_X(self, entities, camera):
        collision_list = []
        self.rect.x += (self.getPlayerState("x_velocity"))
        length = len(entities)
        for objects in entities:
            if self.getCollisionRect().colliderect(objects.getRect()):
                if objects.getEffects()["Kill"]:
                    if self.getPlayerState(
                            "win_direction") == camera.getTarget(
                            ).getPlayerState("win_direction"):
                        camera.setActive(False)
                    self.setPlayerState("ghost_counter", 0)
                    self.setPlayerState("sword", True)
                elif objects.getEffects()["P2Flag"]:
                    if self.getPlayerState(
                            "win_direction"
                    ) == -1 and not self.getPlayerState("ghost"):
                        self.setPlayerState("player_won", True)
                elif objects.getEffects()["P1Flag"]:
                    if self.getPlayerState(
                            "win_direction"
                    ) == 1 and not self.getPlayerState("ghost"):
                        self.setPlayerState("player_won", True)
                else:
                    collision_list.append(objects.getRect())
            length = length - 1
        self.rect.x -= (self.getPlayerState("x_velocity"))
        return collision_list

    def duck(self):
        global hit_box_height
        hit_box_height /= 2

    def standUp(self):
        global hit_box_height
        hit_box_height *= 2

    sprite = property(getSprite)
    image_dict = property(getImageDict, setImageDict)
    duck = property(duck)
    stand_up = property(standUp)
コード例 #16
0
class BufferedRenderer(object):
    """ Renderer that support scrolling, zooming, layers, and animated tiles

    The buffered renderer must be used with a data class to get tile, shape,
    and animation information.  See the data class api in pyscroll.data, or
    use the built-in pytmx support for loading maps created with Tiled.
    """
    _alpha_clear_color = 0, 0, 0, 0

    def __init__(self,
                 data,
                 size,
                 clamp_camera=True,
                 colorkey=None,
                 alpha=False,
                 time_source=time.time,
                 scaling_function=pygame.transform.scale):

        # default options
        self.data = data  # reference to data source
        self.clamp_camera = clamp_camera  # if true, cannot scroll past map edge
        self.anchored_view = True  # if true, map will be fixed to upper left corner
        self.map_rect = None  # pygame rect of entire map
        self.time_source = time_source  # determines how tile animations are processed
        self.scaling_function = scaling_function  # what function to use when scaling the zoom buffer

        # internal private defaults
        if colorkey and alpha:
            print('cannot select both colorkey and alpha.  choose one.')
            raise ValueError
        elif colorkey:
            self._clear_color = colorkey
        elif alpha:
            self._clear_color = self._alpha_clear_color
        else:
            self._clear_color = None

        # private attributes
        self._size = None  # size that the camera/viewport is on screen, kinda
        self._redraw_cutoff = None  # size of dirty tile edge that will trigger full redraw
        self._x_offset = None  # offsets are used to scroll map in sub-tile increments
        self._y_offset = None
        self._buffer = None  # complete rendering of tilemap
        self._tile_view = None  # this rect represents each tile on the buffer
        self._half_width = None  # 'half x' attributes are used to reduce division ops.
        self._half_height = None
        self._tile_queue = None  # tiles queued to be draw onto buffer
        self._animation_queue = None  # heap queue of animation token;  schedules tile changes
        self._last_time = None  # used for scheduling animations
        self._layer_quadtree = None  # used to draw tiles that overlap optional surfaces
        self._zoom_buffer = None  # used to speed up zoom operations
        self._zoom_level = 1.0  # negative numbers make map smaller, positive: bigger

        # used to speed up animated tile redraws by keeping track of animated tiles
        # so they can be updated individually
        self._animation_tiles = defaultdict(set)

        # this represents the viewable pixels, aka 'camera'
        self.view_rect = Rect(0, 0, 0, 0)

        self.reload_animations()
        self.set_size(size)

    def scroll(self, vector):
        """ scroll the background in pixels

        :param vector: (int, int)
        """
        self.center((vector[0] + self.view_rect.centerx,
                     vector[1] + self.view_rect.centery))

    def center(self, coords):
        """ center the map on a pixel

        float numbers will be rounded.

        :param coords: (number, number)
        """
        x, y = [round(i, 0) for i in coords]
        self.view_rect.center = x, y

        mw, mh = self.data.map_size
        tw, th = self.data.tile_size

        self.anchored_view = ((self._tile_view.width < mw)
                              or (self._tile_view.height < mh))

        if self.anchored_view and self.clamp_camera:
            self.view_rect.clamp_ip(self.map_rect)

        x, y = self.view_rect.center

        if not self.anchored_view:
            # calculate offset and do not scroll the map layer
            # this is used to handle maps smaller than screen
            self._x_offset = x - self._half_width
            self._y_offset = y - self._half_height

        else:
            # calc the new position in tiles and offset
            left, self._x_offset = divmod(x - self._half_width, tw)
            top, self._y_offset = divmod(y - self._half_height, th)

            # adjust the view if the view has changed without a redraw
            dx = int(left - self._tile_view.left)
            dy = int(top - self._tile_view.top)
            view_change = max(abs(dx), abs(dy))

            if view_change and (view_change <= self._redraw_cutoff):
                self._buffer.scroll(-dx * tw, -dy * th)
                self._tile_view.move_ip(dx, dy)
                self._queue_edge_tiles(dx, dy)
                self._flush_tile_queue(self._buffer)

            elif view_change > self._redraw_cutoff:
                logger.info('scrolling too quickly.  redraw forced')
                self._tile_view.move_ip(dx, dy)
                self.redraw_tiles(self._buffer)

    def draw(self, surface, rect, surfaces=None):
        """ Draw the map onto a surface

        pass a rect that defines the draw area for:
            drawing to an area smaller that the whole window/screen

        surfaces may optionally be passed that will be blitted onto the surface.
        this must be a sequence of tuples containing a layer number, image, and
        rect in screen coordinates.  surfaces will be drawn in order passed,
        and will be correctly drawn with tiles from a higher layer overlapping
        the surface.

        surfaces list should be in the following format:
        [ (layer, surface, rect), ... ]

        or this:
        [ (layer, surface, rect, blendmode_flags), ... ]

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        """
        if self._zoom_level == 1.0:
            self._render_map(surface, rect, surfaces)
        else:
            self._render_map(self._zoom_buffer, self._zoom_buffer.get_rect(),
                             surfaces)
            self.scaling_function(self._zoom_buffer, rect.size, surface)

    @property
    def zoom(self):
        """ Zoom the map in or out.

        Increase this number to make map appear to come closer to camera.
        Decrease this number to make map appear to move away from camera.

        Default value is 1.0
        This value cannot be negative or 0.0

        :return: float
        """
        return self._zoom_level

    @zoom.setter
    def zoom(self, value):
        buffer_size = self._calculate_zoom_buffer_size(self._size, value)
        self._zoom_level = value
        self._initialize_buffers(buffer_size)

    def set_size(self, size):
        """ Set the size of the map in pixels

        This is an expensive operation, do only when absolutely needed.

        :param size: (width, height) pixel size of camera/view of the group
        """
        buffer_size = self._calculate_zoom_buffer_size(size, self._zoom_level)
        self._size = size
        self._initialize_buffers(buffer_size)

    def redraw_tiles(self, surface):
        """ redraw the visible portion of the buffer -- it is slow.
        """
        logger.warn('pyscroll buffer redraw')
        if self._clear_color:
            surface.fill(self._clear_color)

        self._tile_queue = self.data.get_tile_images_by_rect(self._tile_view)
        self._flush_tile_queue(surface)

    def get_center_offset(self):
        """ Return x, y pair that will change world coords to screen coords
        :return: int, int
        """
        return (-self.view_rect.centerx + self._half_width,
                -self.view_rect.centery + self._half_height)

    def _render_map(self, surface, rect, surfaces):
        """ Render the map and optional surfaces to destination surface

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        """
        if self._animation_queue:
            self._process_animation_queue()

        if not self.anchored_view:
            surface.fill(0)

        offset = -self._x_offset + rect.left, -self._y_offset + rect.top

        with surface_clipping_context(surface, rect):
            surface.blit(self._buffer, offset)
            if surfaces:
                surfaces_offset = -offset[0], -offset[1]
                self._draw_surfaces(surface, surfaces_offset, surfaces)

    def _draw_surfaces(self, surface, offset, surfaces):
        """ Draw surfaces onto buffer, then redraw tiles that cover them

        :param surface: destination
        :param offset: offset to compensate for buffer alignment
        :param surfaces: sequence of surfaces to blit
        """
        surface_blit = surface.blit
        ox, oy = offset
        left, top = self._tile_view.topleft
        hit = self._layer_quadtree.hit
        get_tile = self.data.get_tile_image
        tile_layers = tuple(self.data.visible_tile_layers)
        dirty = list()
        dirty_append = dirty.append

        # TODO: check to avoid sorting overhead
        layer_getter = itemgetter(2)
        surfaces.sort(key=layer_getter)

        for layer, group in groupby(surfaces, layer_getter):
            del dirty[:]

            for i in group:
                try:
                    flags = i[3]
                except IndexError:
                    dirty_append(surface_blit(i[0], i[1]))
                else:
                    dirty_append(surface_blit(i[0], i[1], None, flags))

            # TODO: make set of covered tiles, in the case where a cluster
            # of sprite surfaces causes excessive over tile overdrawing
            for dirty_rect in dirty:
                for r in hit(dirty_rect.move(ox, oy)):
                    x, y, tw, th = r
                    for l in [i for i in tile_layers if gt(i, layer)]:
                        tile = get_tile((x // tw + left, y // th + top, l))
                        if tile:
                            surface_blit(tile, (x - ox, y - oy))

    def _queue_edge_tiles(self, dx, dy):
        """ Queue edge tiles and clear edge areas on buffer if needed

        :param dx: Edge along X axis to enqueue
        :param dy: Edge along Y axis to enqueue
        :return: None
        """
        v = self._tile_view
        fill = partial(self._buffer.fill, self._clear_color)
        tw, th = self.data.tile_size
        self._tile_queue = iter([])

        def append(rect):
            self._tile_queue = chain(self._tile_queue,
                                     self.data.get_tile_images_by_rect(rect))
            if self._clear_color:
                fill(((rect[0] - v.left) * tw, (rect[1] - v.top) * th,
                      rect[2] * tw, rect[3] * th))

        if dx > 0:  # right side
            append((v.right - 1, v.top, dx, v.height))

        elif dx < 0:  # left side
            append((v.left, v.top, -dx, v.height))

        if dy > 0:  # bottom side
            append((v.left, v.bottom - 1, v.width, dy))

        elif dy < 0:  # top side
            append((v.left, v.top, v.width, -dy))

    def _update_time(self):
        self._last_time = time.time() * 1000

    def reload_animations(self):
        """ Reload animation information
        """
        self._update_time()
        self._animation_queue = list()

        for gid, frame_data in self.data.get_animations():
            frames = list()
            for frame_gid, frame_duration in frame_data:
                image = self.data.get_tile_image_by_gid(frame_gid)
                frames.append(AnimationFrame(image, frame_duration))

            ani = AnimationToken(gid, frames)
            ani.next += self._last_time
            heappush(self._animation_queue, ani)

    def _process_animation_queue(self):
        self._update_time()
        self._tile_queue = list()
        tile_layers = tuple(self.data.visible_tile_layers)

        # test if the next scheduled tile change is ready
        while self._animation_queue[0].next <= self._last_time:
            token = heappop(self._animation_queue)

            # advance the animation frame index, looping by default
            if token.index == len(token.frames) - 1:
                token.index = 0
            else:
                token.index += 1

            next_frame = token.frames[token.index]
            token.next = next_frame.duration + self._last_time
            heappush(self._animation_queue, token)

            # go through the animated tile map:
            #   * queue tiles that need to be changed
            #   * remove map entries that do not collide with screen

            needs_clear = False
            for x, y, l in self._animation_tiles[token.gid]:

                # if this tile is on the buffer (checked by using the tile view)
                if self._tile_view.collidepoint(x, y):

                    # redraw the entire column of tiles
                    for layer in tile_layers:
                        if layer == l:
                            self._tile_queue.append(
                                (x, y, layer, next_frame.image, token.gid))
                        else:
                            image = self.data.get_tile_image((x, y, layer))
                            if image:
                                self._tile_queue.append(
                                    (x, y, layer, image, None))
                else:
                    needs_clear = True

            # this will delete the set of tile locations that are checked for
            # animated tiles.  when the tile queue is flushed, any tiles in the
            # queue will be added again.  i choose to remove the set, rather
            # than removing the item in the set to reclaim memory over time...
            # though i could implement it by removing entries.  idk  -lt
            if needs_clear:
                del self._animation_tiles[token.gid]

        if self._tile_queue:
            self._flush_tile_queue(self._buffer)

    @staticmethod
    def _calculate_zoom_buffer_size(size, value):
        if value <= 0:
            print('zoom level cannot be zero or less')
            raise ValueError
        value = 1.0 / value
        return [int(round(i * value)) for i in size]

    def _create_buffers(self, view_size, buffer_size):
        """ Create the buffers, taking in account pixel alpha or colorkey

        :param view_size: pixel size of the view
        :param buffer_size: pixel size of the buffer
        """
        requires_zoom_buffer = not view_size == buffer_size
        self._zoom_buffer = None

        if self._clear_color == self._alpha_clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.SRCALPHA)
            self._buffer = Surface(buffer_size, flags=pygame.SRCALPHA)
            self.data.convert_surfaces(self._buffer, True)
        elif self._clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.RLEACCEL)
                self._zoom_buffer.set_colorkey(self._clear_color)
            self._buffer = Surface(buffer_size, flags=pygame.RLEACCEL)
            self._buffer.set_colorkey(self._clear_color)
            self._buffer.fill(self._clear_color)
        else:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size)
            self._buffer = Surface(buffer_size)

    def _initialize_buffers(self, view_size):
        """ Create the buffers to cache tile drawing

        :param view_size: (int, int): size of the draw area
        :return: None
        """
        tw, th = self.data.tile_size
        mw, mh = self.data.map_size
        buffer_tile_width = int(math.ceil(view_size[0] / tw) + 1)
        buffer_tile_height = int(math.ceil(view_size[1] / th) + 1)
        buffer_pixel_size = buffer_tile_width * tw, buffer_tile_height * th

        self.map_rect = Rect(0, 0, mw * tw, mh * th)
        self.view_rect.size = view_size
        self._tile_view = Rect(0, 0, buffer_tile_width, buffer_tile_height)
        self._redraw_cutoff = 1  # TODO: optimize this value
        self._create_buffers(view_size, buffer_pixel_size)
        self._half_width = view_size[0] // 2
        self._half_height = view_size[1] // 2
        self._x_offset = 0
        self._y_offset = 0

        def make_rect(x, y):
            return Rect((x * tw, y * th), (tw, th))

        rects = [
            make_rect(*i) for i in product(range(buffer_tile_width),
                                           range(buffer_tile_height))
        ]

        # TODO: figure out what depth -actually- does
        # values <= 8 tend to reduce performance
        self._layer_quadtree = quadtree.FastQuadTree(rects, 4)

        self.redraw_tiles(self._buffer)

    def _flush_tile_queue(self, surface):
        """ Blit the queued tiles and block until the tile queue is empty
        """
        tw, th = self.data.tile_size
        ltw = self._tile_view.left * tw
        tth = self._tile_view.top * th
        surface_blit = surface.blit

        for x, y, l, tile, gid in self._tile_queue:
            self._animation_tiles[gid].add((x, y, l))
            surface_blit(tile, (x * tw - ltw, y * th - tth))
コード例 #17
0
ファイル: map.py プロジェクト: bpa/renegade
class MapBase(Window):
    def __init__(self, width, height, default_tile_name='floor'):
        Window.__init__(self,None,10)
        self.save_data = SaveObject()
        self.tile_manager = TileManager()
        default_tile = self.tile_manager.get_tile(default_tile_name)
        self.tiles = []
        for x in range(width):
            col = []
            self.tiles.append(col)
            for y in range(height):
                location = MapLocation(self, (x,y), default_tile)
                col.append(location)
        self.width = width
        self.height = height
        tiles_x = core.screen.get_width() / 32
        tiles_y = core.screen.get_height() / 32
        self.dimentions = Rect(0,0,width,height)
        self.character = None
        self.entities = RenderEntity()
        self.non_passable_entities = RenderEntity()
        self.viewport = Rect(0,0,tiles_x,tiles_y)
        self.offset = Rect(0,0,0,0)
        self.map_tile_coverage = Rect(0,0,tiles_x+5,tiles_y+5)
        if self.map_tile_coverage.width > width:
            self.map_tile_coverage.width = width
        if self.map_tile_coverage.height > height:
            self.map_tile_coverage.height = height
        self.map_non_scroll_region = \
                    self.viewport.inflate(SCROLL_EDGE*-2,SCROLL_EDGE*-2)
        self.action_listeners = {}
        self.entry_listeners = {}
        self.movement_listeners = []
        self.scrolling = False
        self.frame = 0
        self.map_frames_dirty = [True,True,True,True]
        self.map_frames = []
        self.heal_points = 0
        self.regen_rate = 2000000000
        self.sound = core.mixer.Sound('%s/sounds/beep.wav' % DATA_DIR)
        for f in range(4):
#TODO Add non hardcoded values for buffer
#TODO Make sure we don't make a larger surface than we need
#TODO   Ex: 5x5 map
            self.map_frames.append(Surface(((1+width) * TILE_SIZE, \
                    (1+height) * TILE_SIZE)))

    def __getstate__(self):
        dict = {}
        dict['width']  = self.width
        dict['height'] = self.height
        dict['offset.width'] = self.offset.width
        dict['offset.height'] = self.offset.height
        dict['save_data'] = self.save_data
        return dict
  
    def __setstate__(self, dict):
        if self.__class__.__name__ == 'MapBase':
          self.__init__(dict['width'],dict['height'])
        else:
          self.__init__()
        self.save_data = dict['save_data']
        self.offset.width = dict['offset.width']
        self.offset.height = dict['offset.height']
        self.blur_events()

    def dispose(self):
        self.destroy()
        del self.tiles
        self.tile_manager.clear()
        del self.action_listeners
        del self.entry_listeners
        del self.movement_listeners
        self.entities.empty()
        self.non_passable_entities.empty()
        self.character.map = None
    
    def set_regen_rate(self, rate):
        self.regen_rate = rate

    def get(self, x, y):
        if x<0 or y<0: return None
        try:
            return self.tiles[x][y]
        except:
            return None
    
    def calculate_tile_coverage(self, viewable):
        if self.character is None:
            return
        coverage = self.map_tile_coverage
        coverage.center = self.character.pos
        view_scroll = viewable.inflate(8,8)
        coverage.clamp_ip(view_scroll)
        coverage.clamp_ip(self.dimentions)
        self.offset.left = (viewable.left - coverage.left) * TILE_SIZE
        self.offset.top  = (viewable.top  - coverage.top ) * TILE_SIZE
        if not self.map_non_scroll_region.collidepoint(self.character.pos):
            self.map_non_scroll_region = \
                   self.viewport.inflate(SCROLL_EDGE*-2,SCROLL_EDGE*-2)

    def set_location(self, loc, tile_name, walkable=True, tile_pos=None):
        x, y = loc
        location = self.get(x, y)
        tile = self.tile_manager.get_tile(tile_name, None, tile_pos)
        location.set_tile(tile)
        location.set_walkable(walkable)

    def place_character(self, character, pos, passable=False, direction=NORTH):
        self.character = character
        character.map = self
        character.can_trigger_actions = 1
        if not self.viewport.collidepoint(pos):
          self.viewport.center = pos
          self.viewport.clamp_ip(self.dimentions)
        self.place_entity(character, pos, passable, direction)
        self.calculate_tile_coverage(self.viewport)

    def place_entity(self, entity, entity_pos, passable=False, direction=NORTH):
        entity.face(direction)
        entity.map = self
        entity.move_to(entity_pos)
        self.entities.add(entity)
        if not passable:
            self.non_passable_entities.add(entity)

    def add_entry_listener(self, x, y, listener):
        self.entry_listeners[ (x,y) ] = listener

    def add_movement_listener(self, listener):
        self.movement_listeners.append(listener)

    def update(self):
        """Invoked once per cycle of the event loop, to allow animation to update"""
        if self.character.entered_tile:
            self.character.entered_tile = False
            self.check_heal()
            if self.entry_listeners.has_key( self.character.pos ):
                self.entry_listeners[self.character.pos]()
            for listener in self.movement_listeners:
                listener()
        if self.scrolling:
            axis = self.scroll_axis
            diff = [0,0]
            diff[axis] = self.scroll_anchor - self.character.rect[axis]
            self.entities.scroll(diff)
            diff[axis] = diff[axis] * -1
            self.offset[axis] = self.offset[axis] + diff[axis]
            if not self.character.moving:
                self.scrolling = False
        if self.is_left(): self.move_character(WEST)
        if self.is_right(): self.move_character(EAST)
        if self.is_up(): self.move_character(NORTH)
        if self.is_down(): self.move_character(SOUTH)
        if self.map_frames_dirty[self.frame]:
            self.build_current_frame()
            self.map_frames_dirty[self.frame] = False
        self.entities.update()

    def draw(self, blit):
      blit(self.map_frames[self.frame], (0,0), self.offset)
      self.entities.draw(blit)

    def build_current_frame(self):
#TODO Decide if map_tile_coverage is the right name for this
        blit = self.map_frames[self.frame].blit
        rect = (self.frame * TILE_SIZE, 0, TILE_SIZE, TILE_SIZE)
        x = 0
        y = 0
        for col in range(self.map_tile_coverage.left, \
                         self.map_tile_coverage.right):
            column = self.tiles[col]
            for row in range(self.map_tile_coverage.top, \
                             self.map_tile_coverage.bottom):
                blit(column[row].tile, (x,y), rect)
                y = y + TILE_SIZE
            x = x + TILE_SIZE
            y = 0

    def init(self):
        self.offset.width = core.screen.get_rect().width
        self.offset.height = core.screen.get_rect().height
        self.entities.run_command('enter_map')

    def handle_event(self,event):
        if event.type == PUSH_ACTION_EVENT: self.character_activate()
        if event.type == PUSH_ACTION2_EVENT: menu.run_main_menu()
        if event.type == QUIT_EVENT: core.wm.running = False

    def check_heal(self):
        self.heal_points = self.heal_points + 1
        if self.heal_points >= self.regen_rate:
            core.game.save_data.hero.regenerate()
            self.heal_points = 0

    def character_activate(self):
        if not self.character.moving:
            target = add(self.character.pos,MOVE_VECTORS[self.character.facing])
            entities = self.non_passable_entities.entity_collisions(target)
            for e in entities:
                e.activate()
        
    def move_character(self, dir):
        self.character.move(dir)
        if not self.scrolling:
            if self.character.moving:
                nsr = self.map_non_scroll_region
                x,y = self.character.pos

                if dir % 2 == 0: # North or south
                    if y <  nsr.bottom and \
                       y >= nsr.top:
                          return
                    if y <  SCROLL_EDGE or \
                       y >= self.height - SCROLL_EDGE:
                          return
                    self.scroll_axis = 1
                else:            # East or west
                    if x <  nsr.right and \
                       x >= nsr.left:
                          return
                    if x <  SCROLL_EDGE or \
                       x >= self.width - SCROLL_EDGE:
                          return
                    self.scroll_axis = 0

                self.scrolling = True
                vector = MOVE_VECTORS[dir]
                self.map_non_scroll_region.move_ip(vector)
                old_viewport = self.viewport
                self.viewport = old_viewport.move(vector)
                self.scroll_anchor = self.character.rect[self.scroll_axis]
                if not self.map_tile_coverage.contains(self.viewport):
                    self.calculate_tile_coverage(old_viewport)
                    self.dirty()

    def dirty(self):
        for f in range(4):
            self.map_frames_dirty[f] = True

    def move_ok(self, target_pos, character):
        x, y = target_pos
        target = self.get(x,y)
        if target is not None and target.is_walkable():
            entities = self.non_passable_entities.entity_collisions(target_pos)
            if len(entities) > 0:
                if character.can_trigger_actions:
                    for e in entities:
                        e.touch()
                else:
                    for e in entities:
                        if e.can_trigger_actions: character.touch()
                return 0
            else:
                self.sound.play()
                return 1
        else:
            return 0

    def get_tiles_from_ascii(self,ascii,tile_map):
        for y in range(self.height):
            line = ascii[y]
            for x in range(self.width):
                c = line[x]
                args = tile_map[c]
                pos = None
                if len(args) > 1:
                    pos = args[1]
                self.set_location( (x,y), args[0],
                    tile_map['walkable'].find(c)!=-1, pos )
コード例 #18
0
ファイル: orthographic.py プロジェクト: little-tee/GTC
class BufferedRenderer(object):
    """ Renderer that support scrolling, zooming, layers, and animated tiles

    The buffered renderer must be used with a data class to get tile, shape,
    and animation information.  See the data class api in pyscroll.data, or
    use the built-in pytmx support for loading maps created with Tiled.
    """
    def __init__(self,
                 data,
                 size,
                 clamp_camera=True,
                 colorkey=None,
                 alpha=False,
                 time_source=time.time,
                 scaling_function=pygame.transform.scale):

        # default options
        self.data = data  # reference to data source
        self.clamp_camera = clamp_camera  # if true, cannot scroll past map edge
        self.anchored_view = True  # if true, map will be fixed to upper left corner
        self.map_rect = None  # pygame rect of entire map
        self.time_source = time_source  # determines how tile animations are processed
        self.scaling_function = scaling_function  # what function to use when zooming
        self.default_shape_texture_gid = 1  # [experimental] texture to draw shapes with
        self.default_shape_color = 0, 255, 0  # [experimental] color to fill polygons with

        # internal private defaults
        if colorkey and alpha:
            print('cannot select both colorkey and alpha.  choose one.')
            raise ValueError
        elif colorkey:
            self._clear_color = colorkey
        elif alpha:
            self._clear_color = (0, 0, 0, 0)
        else:
            self._clear_color = None

        # private attributes
        self._size = None  # size that the camera/viewport is on screen, kinda
        self._redraw_cutoff = None  # size of dirty tile edge that will trigger full redraw
        self._x_offset = None  # offsets are used to scroll map in sub-tile increments
        self._y_offset = None
        self._buffer = None  # complete rendering of tilemap
        self._tile_view = None  # this rect represents each tile on the buffer
        self._half_width = None  # 'half x' attributes are used to reduce division ops.
        self._half_height = None
        self._tile_queue = None  # tiles queued to be draw onto buffer
        #self._animation_queue = None  # heap queue of animation token.  schedules tile changes
        #self._animation_map = None    # map of GID to other GIDs in an animation
        self._last_time = None  # used for scheduling animations
        self._layer_quadtree = None  # used to draw tiles that overlap optional surfaces
        self._zoom_buffer = None  # used to speed up zoom operations
        self._zoom_level = 1.0  # negative numbers make map smaller, positive: bigger

        # this represents the viewable pixels, aka 'camera'
        self.view_rect = Rect(0, 0, 0, 0)

        self.reload_animations()
        self.set_size(size)

    def scroll(self, vector):
        """ scroll the background in pixels

        :param vector: (int, int)
        """
        self.center((vector[0] + self.view_rect.centerx,
                     vector[1] + self.view_rect.centery))

    def center(self, coords):
        """ center the map on a pixel

        float numbers will be rounded.

        :param coords: (number, number)
        """
        x, y = [round(i, 0) for i in coords]
        self.view_rect.center = x, y

        mw, mh = self.data.map_size
        tw, th = self.data.tile_size

        self.anchored_view = ((self._tile_view.width < mw)
                              or (self._tile_view.height < mh))

        if self.anchored_view and self.clamp_camera:
            self.view_rect.clamp_ip(self.map_rect)

        x, y = self.view_rect.center

        if not self.anchored_view:
            # calculate offset and do not scroll the map layer
            # this is used to handle maps smaller than screen
            self._x_offset = x - self._half_width
            self._y_offset = y - self._half_height

        else:
            # calc the new position in tiles and offset
            left, self._x_offset = divmod(x - self._half_width, tw)
            top, self._y_offset = divmod(y - self._half_height, th)

            # adjust the view if the view has changed without a redraw
            dx = int(left - self._tile_view.left)
            dy = int(top - self._tile_view.top)
            view_change = max(abs(dx), abs(dy))

            if view_change and (view_change <= self._redraw_cutoff):
                self._buffer.scroll(-dx * tw, -dy * th)
                self._tile_view.move_ip(dx, dy)
                self._queue_edge_tiles(dx, dy)
                self._flush_tile_queue()

            elif view_change > self._redraw_cutoff:
                logger.info('scrolling too quickly.  redraw forced')
                self._tile_view.move_ip(dx, dy)
                self.redraw_tiles()

    def draw(self, surface, rect, surfaces=None):
        """ Draw the map onto a surface

        pass a rect that defines the draw area for:
            drawing to an area smaller that the whole window/screen

        surfaces may optionally be passed that will be blitted onto the surface.
        this must be a sequence of tuples containing a layer number, image, and
        rect in screen coordinates.  surfaces will be drawn in order passed,
        and will be correctly drawn with tiles from a higher layer overlapping
        the surface.

        surfaces list should be in the following format:
        [ (layer, surface, rect), ... ]

        or this:
        [ (layer, surface, rect, blendmode_flags), ... ]

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        """
        if self._zoom_level == 1.0:
            self._render_map(surface, rect, surfaces)
        else:
            self._render_map(self._zoom_buffer, self._zoom_buffer.get_rect(),
                             surfaces)
            self.scaling_function(self._zoom_buffer, rect.size, surface)

    @property
    def zoom(self):
        """ Zoom the map in or out.

        Increase this number to make map appear to come closer to camera.
        Decrease this number to make map appear to move away from camera.

        Default value is 1.0
        This value cannot be negative or 0.0

        :return: float
        """
        return self._zoom_level

    @zoom.setter
    def zoom(self, value):
        self._zoom_level = value
        buffer_size = self._calculate_zoom_buffer_size(value)
        self._initialize_buffers(buffer_size)

    def set_size(self, size):
        """ Set the size of the map in pixels

        This is an expensive operation, do only when absolutely needed.

        :param size: (width, height) pixel size of camera/view of the group
        """
        self._size = size
        buffer_size = self._calculate_zoom_buffer_size(self._zoom_level)
        self._initialize_buffers(buffer_size)

    def redraw_tiles(self):
        """ redraw the visible portion of the buffer -- it is slow.
        """
        if self._clear_color:
            self._buffer.fill(self._clear_color)

        self._tile_queue = self.data.get_tile_images_by_rect(self._tile_view)
        self._flush_tile_queue()

    def get_center_offset(self):
        """ Return x, y pair that will change world coords to screen coords
        :return: int, int
        """
        return (-self.view_rect.centerx + self._half_width,
                -self.view_rect.centery + self._half_height)

    def _render_map(self, surface, rect, surfaces):
        """ Render the map and optional surfaces to destination surface

        :param surface: pygame surface to draw to
        :param rect: area to draw to
        :param surfaces: optional sequence of surfaces to interlace between tiles
        """
        if self._animation_queue:
            self._process_animation_queue()

        if not self.anchored_view:
            surface.fill(0)

        offset = -self._x_offset + rect.left, -self._y_offset + rect.top

        with surface_clipping_context(surface, rect):
            surface.blit(self._buffer, offset)
            if surfaces:
                surfaces_offset = -offset[0], -offset[1]
                self._draw_surfaces(surface, surfaces_offset, surfaces)

    def _draw_surfaces(self, surface, offset, surfaces):
        """ Draw surfaces onto buffer, then redraw tiles that cover them

        :param surface: destination
        :param offset: offset to compensate for buffer alignment
        :param surfaces: sequence of surfaces to blit
        """
        surface_blit = surface.blit
        ox, oy = offset
        left, top = self._tile_view.topleft
        hit = self._layer_quadtree.hit
        get_tile = self.data.get_tile_image
        tile_layers = tuple(self.data.visible_tile_layers)

        dirty = list()
        dirty_append = dirty.append
        for i in surfaces:
            try:
                flags = i[3]
            except IndexError:
                dirty_append((surface_blit(i[0], i[1]), i[2]))
            else:
                dirty_append((surface_blit(i[0], i[1], None, flags), i[2]))

        for dirty_rect, layer in dirty:
            for r in hit(dirty_rect.move(ox, oy)):
                x, y, tw, th = r
                for l in [i for i in tile_layers if gt(i, layer)]:
                    tile = get_tile((x // tw + left, y // th + top, l))
                    if tile:
                        surface_blit(tile, (x - ox, y - oy))

    def _draw_objects(self):
        """ Totally unoptimized drawing of objects to the map [probably broken]
        """
        tw, th = self.data.tile_size
        buff = self._buffer
        blit = buff.blit
        map_gid = self.data.tmx.map_gid
        default_color = self.default_shape_color
        get_image_by_gid = self.data.get_tile_image_by_gid
        _draw_textured_poly = pygame.gfxdraw.textured_polygon
        _draw_poly = pygame.draw.polygon
        _draw_lines = pygame.draw.lines

        ox = self._tile_view.left * tw
        oy = self._tile_view.top * th

        def draw_textured_poly(texture, points):
            try:
                _draw_textured_poly(buff, points, texture, tw, th)
            except pygame.error:
                pass

        def draw_poly(color, points, width=0):
            _draw_poly(buff, color, points, width)

        def draw_lines(color, points, width=2):
            _draw_lines(buff, color, False, points, width)

        def to_buffer(pt):
            return pt[0] - ox, pt[1] - oy

        for layer in self.data.visible_object_layers:
            for o in (o for o in layer if o.visible):
                texture_gid = getattr(o, "texture", None)
                color = getattr(o, "color", default_color)

                # BUG: this is not going to be completely accurate, because it
                # does not take into account times where texture is flipped.
                if texture_gid:
                    texture_gid = map_gid(texture_gid)[0][0]
                    texture = get_image_by_gid(int(texture_gid))

                if hasattr(o, 'points'):
                    points = [to_buffer(i) for i in o.points]
                    if o.closed:
                        if texture_gid:
                            draw_textured_poly(texture, points)
                        else:
                            draw_poly(color, points)
                    else:
                        draw_lines(color, points)

                elif o.gid:
                    tile = get_image_by_gid(o.gid)
                    if tile:
                        pt = to_buffer((o.x, o.y))
                        blit(tile, pt)

                else:
                    x, y = to_buffer((o.x, o.y))
                    points = ((x, y), (x + o.width, y),
                              (x + o.width, y + o.height), (x, y + o.height))
                    if texture_gid:
                        draw_textured_poly(texture, points)
                    else:
                        draw_poly(color, points)

    def _queue_edge_tiles(self, dx, dy):
        """ Queue edge tiles and clear edge areas on buffer if needed

        :param dx: Edge along X axis to enqueue
        :param dy: Edge along Y axis to enqueue
        :return: None
        """
        v = self._tile_view
        fill = partial(self._buffer.fill, self._clear_color)
        tw, th = self.data.tile_size
        self._tile_queue = iter([])

        def append(rect):
            self._tile_queue = chain(self._tile_queue,
                                     self.data.get_tile_images_by_rect(rect))
            if self._clear_color:
                fill(((rect[0] - v.left) * tw, (rect[1] - v.top) * th,
                      rect[2] * tw, rect[3] * th))

        if dx > 0:  # right side
            append((v.right - 1, v.top, dx, v.height))

        elif dx < 0:  # left side
            append((v.left, v.top, -dx, v.height))

        if dy > 0:  # bottom side
            append((v.left, v.bottom - 1, v.width, dy))

        elif dy < 0:  # top side
            append((v.left, v.top, v.width, -dy))

    def _update_time(self):
        self._last_time = time.time() * 1000

    def reload_animations(self):
        """ Reload animation information
        """
        self._update_time()
        self._animation_map = dict()
        self._animation_queue = list()

        for gid, frame_data in self.data.get_animations():
            frames = list()
            for frame_gid, frame_duration in frame_data:
                image = self.data.get_tile_image_by_gid(frame_gid)
                frames.append(AnimationFrame(image, frame_duration))

            ani = AnimationToken(gid, frames)
            ani.next += self._last_time
            self._animation_map[ani.gid] = ani.frames[ani.index].image
            print(self._animation_map[ani.gid])
            heappush(self._animation_queue, ani)

    def _process_animation_queue(self):
        self._update_time()
        requires_redraw = False

        # test if the next scheduled tile change is ready
        while self._animation_queue[0].next <= self._last_time:
            requires_redraw = True
            token = heappop(self._animation_queue)

            # advance the animation index, looping by default
            if token.index == len(token.frames) - 1:
                token.index = 0
            else:
                token.index += 1

            next_frame = token.frames[token.index]
            token.next = next_frame.duration + self._last_time
            self._animation_map[token.gid] = next_frame.image
            heappush(self._animation_queue, token)

        if requires_redraw:
            # TODO: record the tiles that changed and update only affected tiles
            self.redraw_tiles()

    def _calculate_zoom_buffer_size(self, value):
        if value <= 0:
            print('zoom level cannot be zero or less')
            raise ValueError
        value = 1.0 / value
        return [int(round(i * value)) for i in self._size]

    def _create_buffers(self, view_size, buffer_size):
        """ Create the buffers, taking in account pixel alpha or colorkey

        :param view_size: pixel size of the view
        :param buffer_size: pixel size of the buffer
        """
        requires_zoom_buffer = not view_size == buffer_size
        self._zoom_buffer = None

        if self._clear_color == (0, 0, 0, 0):
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.SRCALPHA)
            self._buffer = Surface(buffer_size, flags=pygame.SRCALPHA)
        elif self._clear_color:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size, flags=pygame.RLEACCEL)
                self._zoom_buffer.set_colorkey(self._clear_color)
            self._buffer = Surface(buffer_size, flags=pygame.RLEACCEL)
            self._buffer.set_colorkey(self._clear_color)
            self._buffer.fill(self._clear_color)
        else:
            if requires_zoom_buffer:
                self._zoom_buffer = Surface(view_size)
            self._buffer = Surface(buffer_size)

    def _initialize_buffers(self, view_size):
        """ Create the buffers to cache tile drawing

        :param view_size: (int, int): size of the draw area
        :return: None
        """
        tw, th = self.data.tile_size
        mw, mh = self.data.map_size
        buffer_tile_width = int(math.ceil(view_size[0] / tw) + 2)
        buffer_tile_height = int(math.ceil(view_size[1] / th) + 2)
        buffer_pixel_size = buffer_tile_width * tw, buffer_tile_height * th

        self.map_rect = Rect(0, 0, mw * tw, mh * th)
        self.view_rect.size = view_size
        self._tile_view = Rect(0, 0, buffer_tile_width, buffer_tile_height)
        self._redraw_cutoff = 0.5  # TODO: optimize this value
        self._create_buffers(view_size, buffer_pixel_size)
        self._half_width = view_size[0] // 2
        self._half_height = view_size[1] // 2
        self._x_offset = 0
        self._y_offset = 0

        def make_rect(x, y):
            return Rect((x * tw, y * th), (tw, th))

        rects = [
            make_rect(*i) for i in product(range(buffer_tile_width),
                                           range(buffer_tile_height))
        ]

        # TODO: figure out what depth -actually- does
        self._layer_quadtree = quadtree.FastQuadTree(rects, 4)
        self.redraw_tiles()

    def _flush_tile_queue(self):
        """ Blit the queued tiles and block until the tile queue is empty
        """
        tw, th = self.data.tile_size
        ltw = self._tile_view.left * tw
        tth = self._tile_view.top * th
        blit = self._buffer.blit
        map_get = self._animation_map.get

        for x, y, l, tile, gid in self._tile_queue:
            blit(map_get(gid, tile), (x * tw - ltw, y * th - tth))  #