Example #1
0
    def animate_capture_monster(self, is_captured, num_shakes, monster):
        """ Animation for capturing monsters.

        :param is_captured: boolean representing success of capture
        :param num_shakes: number of shakes before animation ends
        :param monster: the monster
        :return:
        """
        monster_sprite = self._monster_sprite_map.get(monster, None)
        capdev = self.load_sprite('gfx/items/capture_device.png')
        animate = partial(self.animate, capdev.rect, transition='in_quad', duration=1.0)
        scale_sprite(capdev, .4)
        capdev.rect.center = scale(0), scale(0)
        animate(x=monster_sprite.rect.centerx)
        animate(y=monster_sprite.rect.centery)
        self.task(partial(toggle_visible, monster_sprite), 1.0) # make the monster go away temporarily

        def kill():
            self._monster_sprite_map[monster].kill()
            self.hud[monster].kill()
            del self._monster_sprite_map[monster]
            del self.hud[monster]

        # TODO: cache this sprite from the first time it's used.
        # also, should loading animated sprites be more convenient?
        images = list()
        for fn in ["capture%02d.png" % i for i in range(1, 10)]:
            fn = 'animations/technique/' + fn
            image = tools.load_and_scale(fn)
            images.append((image, .07))

        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        self.task(tech.play, 1.0)
        self.task(partial(self.sprites.add, sprite), 1.0)
        sprite.rect.midbottom = monster_sprite.rect.midbottom

        def shake_ball(initial_delay):
            animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay)
            animate(capdev.rect, y=scale(3), relative=True)

            animate = partial(self.animate, duration=0.2, transition='linear', delay=initial_delay + 0.1)
            animate(capdev.rect, y=-scale(6), relative=True)

            animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay + 0.3)
            animate(capdev.rect, y=scale(3), relative=True)

        for i in range(0, num_shakes):
            shake_ball(1.8 + i * 1.0) # leave a 0.6s wait between each shake

        if is_captured:
            self.task(kill, 2 + num_shakes)
        else:
            self.task(partial(toggle_visible, monster_sprite), 1.8 + num_shakes * 1.0) # make the monster appear again!
            self.task(tech.play, 1.8 + num_shakes * 1.0)
            self.task(capdev.kill, 1.8 + num_shakes * 1.0)
Example #2
0
    def animate_monster_release_bottom(self, feet, monster):
        """

        :type feet: sequence
        :type monster: core.components.monster.Monster
        :return:
        """
        capdev = self.load_sprite('gfx/items/capture_device.png')
        scale_sprite(capdev, .4)
        capdev.rect.center = feet[0], feet[1] - scale(60)

        # animate the capdev falling
        fall_time = .7
        animate = partial(self.animate, duration=fall_time, transition='out_quad')
        animate(capdev.rect, bottom=feet[1], transition='in_back')
        animate(capdev, rotation=720, initial=0)

        # animate the capdev fading away
        delay = fall_time + .6
        fade_duration = .9
        h = capdev.rect.height
        animate = partial(self.animate, duration=fade_duration, delay=delay)
        animate(capdev, width=1, height=h * 1.5)
        animate(capdev.rect, y=-scale(14), relative=True)

        # convert the capdev sprite so we can fade it easily
        def func():
            capdev.image = tools.convert_alpha_to_colorkey(capdev.image)
            self.animate(capdev.image, set_alpha=0, initial=255, duration=fade_duration)

        self.task(func, delay)
        self.task(capdev.kill, fall_time + delay + fade_duration)

        # load monster and set in final position
        monster_sprite = self.load_sprite(monster.back_battle_sprite, midbottom=feet)
        self._monster_sprite_map[monster] = monster_sprite

        # position monster_sprite off screen and set animation to move it back to final spot
        monster_sprite.rect.top = self.game.screen.get_height()
        self.animate(monster_sprite.rect, bottom=feet[1], transition='out_back',
                     duration=.9, delay=fall_time + .5)

        # capdev opening animation
        images = list()
        for fn in ["capture%02d.png" % i for i in range(1, 10)]:
            fn = 'animations/technique/' + fn
            image = tools.load_and_scale(fn)
            images.append((image, .07))

        delay = 1.3
        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        sprite.rect.midbottom = feet
        self.task(tech.play, delay)
        self.task(partial(self.sprites.add, sprite), delay)
Example #3
0
    def load_technique_animation(technique):
        """

        TODO: move to some generic animation loading thingy

        :param technique:
        :rtype: core.components.sprite.Sprite
        """
        frame_time = .09
        images = list()
        for fn in technique.images:
            image = tools.load_and_scale(fn)
            images.append((image, frame_time))

        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        return sprite
Example #4
0
    def load_technique_animation(technique):
        """

        TODO: move to some generic animation loading thingy

        :param technique:
        :rtype: core.components.sprite.Sprite
        """
        frame_time = 0.09
        images = list()
        for fn in technique.images:
            image = tools.load_and_scale(fn)
            images.append((image, frame_time))

        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        return sprite
Example #5
0
    def animate_monster_release_bottom(self, feet, monster):
        """

        :type feet: sequence
        :type monster: core.components.monster.Monster
        :return:
        """
        capdev = self.load_sprite('gfx/items/capture_device.png')
        scale_sprite(capdev, .4)
        capdev.rect.center = feet[0], feet[1] - scale(60)

        # animate the capdev falling
        fall_time = .7
        animate = partial(self.animate,
                          duration=fall_time,
                          transition='out_quad')
        animate(capdev.rect, bottom=feet[1], transition='in_back')
        animate(capdev, rotation=720, initial=0)

        # animate the capdev fading away
        delay = fall_time + .6
        fade_duration = .9
        h = capdev.rect.height
        animate = partial(self.animate, duration=fade_duration, delay=delay)
        animate(capdev, width=1, height=h * 1.5)
        animate(capdev.rect, y=-scale(14), relative=True)

        # convert the capdev sprite so we can fade it easily
        def func():
            capdev.image = tools.convert_alpha_to_colorkey(capdev.image)
            self.animate(capdev.image,
                         set_alpha=0,
                         initial=255,
                         duration=fade_duration)

        self.task(func, delay)
        self.task(capdev.kill, fall_time + delay + fade_duration)

        # load monster and set in final position
        monster_sprite = self.load_sprite(monster.back_battle_sprite,
                                          midbottom=feet)
        self._monster_sprite_map[monster] = monster_sprite

        # position monster_sprite off screen and set animation to move it back to final spot
        monster_sprite.rect.top = self.game.screen.get_height()
        self.animate(monster_sprite.rect,
                     bottom=feet[1],
                     transition='out_back',
                     duration=.9,
                     delay=fall_time + .5)

        # capdev opening animation
        images = list()
        for fn in ["capture%02d.png" % i for i in range(1, 10)]:
            fn = 'animations/technique/' + fn
            image = tools.load_and_scale(fn)
            images.append((image, .07))

        delay = 1.3
        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        sprite.rect.midbottom = feet
        self.task(tech.play, delay)
        self.task(partial(self.sprites.add, sprite), delay)
Example #6
0
    def loadfile(self, tile_size):
        """Loads the tile and collision data from the map file and returns a list of tiles with
        their position and pygame surface, a set of collision tile coordinates, and the size of
        the map itself. The list of tile surfaces is used to draw the map in the main game. The
        list of collision tile coordinates is used for collision detection.

        :param tile_size: An [x, y] size of each tile in pixels AFTER scaling. This is used for
            scaling and positioning.

        :type tile_size: List

        :rtype: List
        :returns: A multi-dimensional list of tiles in dictionary format; a set of collision
            coordinates; the map size.

        **Examples:**

        The list of tiles is structured in a way where you can access an individual tile by
        index number. For example, to get a tile located at (2, 1), you can access the tile's
        details using:

        >>> x = 2
        >>> y = 1
        >>> layer = 0
        >>> tiles[x][y][layer]

        Here is an example of what the the tiles list data structure actually looks like:

        >>> tiles, collisions, mapsize =  map.loadfile([24, 24])
        >>> tiles
            [
              [
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                []
              ],
              [ [],
                [{'layer': 1,
                'name': '6,0',
                'position': (80, 80),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 1],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '7,0',
                'position': (80, 160),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 2],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '8,0',
                'position': (80, 240),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 3],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '9,0',
                'position': (80, 320),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 4],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '9,0',
                'position': (80, 400),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 5],
                'tileset': 'resources/gfx/tileset.png'},
                {'layer': 3,
                'name': '10,0',
                'position': (80, 400),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 5],
                'tileset': 'resources/gfx/tileset.png'}],
        ...


        The collision map is a set of (x,y) coordinates that the player cannot walk
        through. This set is generated based on collision regions defined in the
        map file.

        Here is an example of what the collision set looks like:

        >>> tiles, collisions, mapsize =  map.loadfile([24, 24])
        >>> collisions
        set([(0, 2),
             (0, 3),
             (0, 4),
             (0, 5),
             (0, 6)])

        """

        # Create a list of all of the tiles in the map
        tiles = []

        # Loop through all tiles in our map file and get the pygame surface associated with it.
        for x in range(0, int(self.data.width)):

            # Create a list of tile for the y-axis
            y_list = []

            for y in range(0, int(self.data.height)):

                layer_list = []

                # Get the number of tile layers.
                num_of_layers = 0

                # PyTMX recently changed some of their attribute names.
                # This ensures we get the number of layers regardless of
                # the version of PyTMX.
                try:
                    for layer in self.data.layers:
                        if hasattr(layer, 'data'):
                            num_of_layers += 1
                except AttributeError:
                    for layer in self.data.tilelayers:
                        num_of_layers += 1

                # Get all the map tiles for each layer
                for layer in range(0, num_of_layers):

                    # PyTMX recently changed their method names. This
                    # ensures the map will load regardless of the PyTMX
                    # version.
                    try:
                        surface = self.data.getTileImage(x, y, layer)
                    except AttributeError:
                        surface = self.data.get_tile_image(x, y, layer)

                    # Check to see if this tile has an animation
                    tile_properties = self.data.get_tile_properties(x, y, layer)
                    if tile_properties and "frames" in tile_properties:
                        images_and_durations = []
                        for frame in tile_properties["frames"]:
                            # bitcraft/PyTMX 3.20.14+ support
                            if hasattr(frame, 'gid'):
                                anim_surface = self.data.get_tile_image_by_gid(frame.gid)
                                images_and_durations.append((anim_surface, float(frame.duration) / 1000))
                            # ShadowApex/PyTMX 3.20.13 fork support
                            elif "gid" in frame:
                                anim_surface = self.data.get_tile_image_by_gid(frame["gid"])
                                images_and_durations.append((anim_surface, float(frame["duration"]) / 1000))
                            # bitcraft/PyTMX 3.20.13 support
                            elif "gid" not in frame:
                                images_and_durations = [(surface, 1)]
                                break
                        surface = PygAnimation(images_and_durations)
                        surface.play()

                    # Create a tile based on the image
                    if surface:
                        tile = {'tile_pos': (x, y),
                                'position': (x * tile_size[0], y * tile_size[1]),
                                'layer': layer + 1,
                                'name': str(x) + "," + str(y),
                                'surface': surface
                                }

                        layer_list.append(tile)

                y_list.append(layer_list)

            tiles.append(y_list)

        # Get the dimensions of the map
        mapsize = self.size

        # Create a list of all tile monsters_in_play that we cannot walk through
        collision_map = {}

        # Create a dictionary of coordinates that have conditional collisions
        cond_collision_map = {}

        # Create a list of all pairs of adjacent tiles that are impassable (aka walls)
        # example: ((5,4),(5,3), both)
        collision_lines_map = set()

        # Right now our collisions are defined in our tmx file as large regions that the player
        # can't pass through. We need to convert these areas into individual tile coordinates
        # that the player can't pass through.
        # Loop through all of the collision objects in our tmx file.
        for collision_region in self.collisions:

            # >>> collision_region.__dict__
            #{'gid': 0,
            # 'height': 16,
            # 'name': None,
            # 'parent': <TiledMap: "resources/maps/pallet_town-room.tmx">,
            # 'rotation': 0,
            # 'type': 'collision',
            # 'visible': 1,
            # 'width': 16,
            # 'x': 176,
            # 'y': 64}

            # Get the collision area's tile location and dimension in tiles using the tileset's
            # tile size.
            x = self.round_to_divisible(collision_region.x, self.tile_size[0]) / self.tile_size[0]
            y = self.round_to_divisible(collision_region.y, self.tile_size[1]) / self.tile_size[1]
            width = self.round_to_divisible(collision_region.width, self.tile_size[0]) / self.tile_size[0]
            height = self.round_to_divisible(collision_region.height, self.tile_size[1]) / self.tile_size[1]

            # Loop through properties and create list of directions for each property
            if collision_region.properties:
                enters = []
                exits = []

                for key in collision_region.properties:
                    if "enter" in key:
                        for direction in collision_region.properties[key].split():
                            enters.append(direction)
                    elif "exit" in key:
                        for direction in collision_region.properties[key].split():
                            exits.append(direction)

            # Loop through the area of this region and create all the tile coordinates that are
            # inside this region.
            for a in range(0, int(width)):
                for b in range(0, int(height)):
                    collision_tile = (a + x, b + y)
                    collision_map[collision_tile] = "None"

                    # Check if collision region has properties, and is therefore a conditional zone
                    # then add the location and conditions to semi_collision_map
                    if collision_region.properties:
                        tile_conditions = {}
                        for key in collision_region.properties.keys():
                            if "enter" in key:
                                tile_conditions['enter'] = enters
                            if "exit" in key:
                                tile_conditions['exit'] = exits
                        collision_map[collision_tile] = tile_conditions

        # Similar to collisions, except we need to identify the tiles
        # on either side of the poly-line and prevent moving between
        # them
        for collision_line in self.collision_lines:

            # >>> collision_wall.__dict__
            # {'name': None,
            # 'parent': <TiledMap: "resources/maps/test_pathfinding.tmx">,
            # 'visible': 1,
            # 'height': 160.0,
            # 'width': 80.0, '
            # gid': 0,
            # 'closed': False,
            # 'y': 80.0, 'x': 80.0,
            # 'rotation': 0,
            # 'type': 'collision-wall',
            # 'points': ((80.0, 80.0), (80.0, 128.0), (160.0, 128.0), (160.0, 240.0))

            # Another example:
            # 'points': ((192.0, 80.0), (192.0, 192.0))

            # For each pair of points, get the tiles on either side of the line.
            # Assumption: A pair of points will only be vertical or horizontal (no diagonal lines)

            if len(collision_line.points) < 2:
                raise Exception("Error: map has polyline with only one point")

            # get two points, and round them
            point1 = (self.round_to_divisible(collision_line.points[0][0], self.tile_size[0]),
                      self.round_to_divisible(collision_line.points[0][1], self.tile_size[1]))
            point2 = (self.round_to_divisible(collision_line.points[1][0], self.tile_size[0]),
                      self.round_to_divisible(collision_line.points[1][1], self.tile_size[1]))

            # check to see if horizontal or vertical
            line_type = None
            if point1[0] == point2[0] and point1[1] != point2[1]:
                # x's are same, must be vertical
                line_type = 'vertical'
            elif point1[0] != point2[0] and point1[1] == point2[1]:
                # y's are same, must be horizontal
                line_type = 'horizontal'
            else:
                raise Exception("Error: Points on polyline are not strictly horizontal or vertical....")

            if line_type is 'vertical':
                # get all tile coordinates on either side
                x = point1[0] / self.tile_size[0] # same as point2[0] b/c vertical
                line_start = point1[1]
                line_end = point2[1]
                num_tiles_in_line = abs(line_start - line_end) / self.tile_size[1] # [1] b/c vertical
                curr_y = line_start / self.tile_size[1]
                for i in range(int(num_tiles_in_line)):
                    if line_start > line_end: # slightly different
                                              # behavior depending on
                                              # direction
                        left_side_tile = (x-1,curr_y-1)
                        right_side_tile = (x,curr_y-1)
                        curr_y -= 1
                    else:
                        left_side_tile = (x-1,curr_y)
                        right_side_tile = (x,curr_y)
                        curr_y += 1

                    # TODO - if we want to enable single-direction
                    # walls (i.e. for jumping) then ask map-designer
                    # to include a special property for the direction
                    # to block, and then here we only block in one
                    # direction, not both.
                    collision_lines_map.add((left_side_tile, "right"))
                    collision_lines_map.add((right_side_tile, "left"))

            elif line_type is 'horizontal':
                # get all tile coordinates on either side
                y = point1[1] / self.tile_size[1] # same as point2[1] b/c horizontal
                line_start = point1[0]
                line_end = point2[0]
                num_tiles_in_line = abs(line_start - line_end) / self.tile_size[0] # [0] b/c horizontal
                curr_x = line_start / self.tile_size[0]
                for i in range(int(num_tiles_in_line)):
                    if line_start > line_end: # slightly different
                                              # behavior depending on
                                              # direction
                        top_side_tile = (curr_x-1,y-1)
                        bottom_side_tile = (curr_x-1,y)
                        curr_x -= 1
                    else:
                        top_side_tile = (curr_x, y-1)
                        bottom_side_tile = (curr_x, y)
                        curr_x += 1

                    # TODO - if we want to enable single-direction
                    # walls (i.e. for jumping) then ask map-designer
                    # to include a special property for the direction
                    # to block, and then here we only block in one
                    # direction, not both.
                    collision_lines_map.add((top_side_tile, "down"))
                    collision_lines_map.add((bottom_side_tile, "up"))

        return tiles, collision_map, collision_lines_map, mapsize
Example #7
0
    def loadfile(self, tile_size):
        """Loads the tile and collision data from the map file and returns a list of tiles with
        their position and pygame surface, a set of collision tile coordinates, and the size of
        the map itself. The list of tile surfaces is used to draw the map in the main game. The
        list of collision tile coordinates is used for collision detection.

        :param tile_size: An [x, y] size of each tile in pixels AFTER scaling. This is used for
            scaling and positioning.

        :type tile_size: List

        :rtype: List
        :returns: A multi-dimensional list of tiles in dictionary format; a set of collision
            coordinates; the map size.

        **Examples:**

        The list of tiles is structured in a way where you can access an individual tile by
        index number. For example, to get a tile located at (2, 1), you can access the tile's
        details using:

        >>> x = 2
        >>> y = 1
        >>> layer = 0
        >>> tiles[x][y][layer]

        Here is an example of what the the tiles list data structure actually looks like:

        >>> tiles, collisions, mapsize =  map.loadfile([24, 24])
        >>> tiles
            [
              [
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                [],
                []
              ],
              [ [],
                [{'layer': 1,
                'name': '6,0',
                'position': (80, 80),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 1],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '7,0',
                'position': (80, 160),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 2],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '8,0',
                'position': (80, 240),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 3],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '9,0',
                'position': (80, 320),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 4],
                'tileset': 'resources/gfx/tileset.png'}],
                [{'layer': 1,
                'name': '9,0',
                'position': (80, 400),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 5],
                'tileset': 'resources/gfx/tileset.png'},
                {'layer': 3,
                'name': '10,0',
                'position': (80, 400),
                'surface': <Surface(16x16x32 SW)>,
                'tile_pos': [1, 5],
                'tileset': 'resources/gfx/tileset.png'}],
        ...


        The collision map is a set of (x,y) coordinates that the player cannot walk
        through. This set is generated based on collision regions defined in the
        map file.

        Here is an example of what the collision set looks like:

        >>> tiles, collisions, mapsize =  map.loadfile([24, 24])
        >>> collisions
        set([(0, 2),
             (0, 3),
             (0, 4),
             (0, 5),
             (0, 6)])

        """

        # Create a list of all of the tiles in the map
        tiles = []

        # Loop through all tiles in our map file and get the pygame surface associated with it.
        for x in range(0, int(self.data.width)):

            # Create a list of tile for the y-axis
            y_list = []

            for y in range(0, int(self.data.height)):

                layer_list = []

                # Get the number of tile layers.
                num_of_layers = 0

                # PyTMX recently changed some of their attribute names.
                # This ensures we get the number of layers regardless of
                # the version of PyTMX.
                try:
                    for layer in self.data.layers:
                        if hasattr(layer, "data"):
                            num_of_layers += 1
                except AttributeError:
                    for layer in self.data.tilelayers:
                        num_of_layers += 1

                # Get all the map tiles for each layer
                for layer in range(0, num_of_layers):

                    # PyTMX recently changed their method names. This
                    # ensures the map will load regardless of the PyTMX
                    # version.
                    try:
                        surface = self.data.getTileImage(x, y, layer)
                    except AttributeError:
                        surface = self.data.get_tile_image(x, y, layer)

                    # Check to see if this tile has an animation
                    tile_properties = self.data.get_tile_properties(x, y, layer)
                    if tile_properties and "frames" in tile_properties:
                        images_and_durations = []
                        for frame in tile_properties["frames"]:
                            # bitcraft/PyTMX 3.20.14+ support
                            if hasattr(frame, "gid"):
                                anim_surface = self.data.get_tile_image_by_gid(frame.gid)
                                images_and_durations.append((anim_surface, float(frame.duration) / 1000))
                            # ShadowApex/PyTMX 3.20.13 fork support
                            elif "gid" in frame:
                                anim_surface = self.data.get_tile_image_by_gid(frame["gid"])
                                images_and_durations.append((anim_surface, float(frame["duration"]) / 1000))
                            # bitcraft/PyTMX 3.20.13 support
                            elif "gid" not in frame:
                                images_and_durations = [(surface, 1)]
                                break
                        surface = PygAnimation(images_and_durations)
                        surface.play()

                    # Create a tile based on the image
                    if surface:
                        tile = {
                            "tile_pos": (x, y),
                            "position": (x * tile_size[0], y * tile_size[1]),
                            "layer": layer + 1,
                            "name": str(x) + "," + str(y),
                            "surface": surface,
                        }

                        layer_list.append(tile)

                y_list.append(layer_list)

            tiles.append(y_list)

        # Get the dimensions of the map
        mapsize = self.size

        # Create a list of all tile monsters_in_play that we cannot walk through
        collision_map = {}

        # Create a dictionary of coordinates that have conditional collisions
        cond_collision_map = {}

        # Create a list of all pairs of adjacent tiles that are impassable (aka walls)
        # example: ((5,4),(5,3), both)
        collision_lines_map = set()

        # Right now our collisions are defined in our tmx file as large regions that the player
        # can't pass through. We need to convert these areas into individual tile coordinates
        # that the player can't pass through.
        # Loop through all of the collision objects in our tmx file.
        for collision_region in self.collisions:

            # >>> collision_region.__dict__
            # {'gid': 0,
            # 'height': 16,
            # 'name': None,
            # 'parent': <TiledMap: "resources/maps/pallet_town-room.tmx">,
            # 'rotation': 0,
            # 'type': 'collision',
            # 'visible': 1,
            # 'width': 16,
            # 'x': 176,
            # 'y': 64}

            # Get the collision area's tile location and dimension in tiles using the tileset's
            # tile size.
            x = self.round_to_divisible(collision_region.x, self.tile_size[0]) / self.tile_size[0]
            y = self.round_to_divisible(collision_region.y, self.tile_size[1]) / self.tile_size[1]
            width = self.round_to_divisible(collision_region.width, self.tile_size[0]) / self.tile_size[0]
            height = self.round_to_divisible(collision_region.height, self.tile_size[1]) / self.tile_size[1]

            # Loop through properties and create list of directions for each property
            if collision_region.properties:
                enters = []
                exits = []

                for key in collision_region.properties:
                    if "enter" in key:
                        for direction in collision_region.properties[key].split():
                            enters.append(direction)
                    elif "exit" in key:
                        for direction in collision_region.properties[key].split():
                            exits.append(direction)

            # Loop through the area of this region and create all the tile coordinates that are
            # inside this region.
            for a in range(0, int(width)):
                for b in range(0, int(height)):
                    collision_tile = (a + x, b + y)
                    collision_map[collision_tile] = "None"

                    # Check if collision region has properties, and is therefore a conditional zone
                    # then add the location and conditions to semi_collision_map
                    if collision_region.properties:
                        tile_conditions = {}
                        for key in collision_region.properties.keys():
                            if "enter" in key:
                                tile_conditions["enter"] = enters
                            if "exit" in key:
                                tile_conditions["exit"] = exits
                        collision_map[collision_tile] = tile_conditions

        # Similar to collisions, except we need to identify the tiles
        # on either side of the poly-line and prevent moving between
        # them
        for collision_line in self.collision_lines:

            # >>> collision_wall.__dict__
            # {'name': None,
            # 'parent': <TiledMap: "resources/maps/test_pathfinding.tmx">,
            # 'visible': 1,
            # 'height': 160.0,
            # 'width': 80.0, '
            # gid': 0,
            # 'closed': False,
            # 'y': 80.0, 'x': 80.0,
            # 'rotation': 0,
            # 'type': 'collision-wall',
            # 'points': ((80.0, 80.0), (80.0, 128.0), (160.0, 128.0), (160.0, 240.0))

            # Another example:
            # 'points': ((192.0, 80.0), (192.0, 192.0))

            # For each pair of points, get the tiles on either side of the line.
            # Assumption: A pair of points will only be vertical or horizontal (no diagonal lines)

            if len(collision_line.points) < 2:
                raise Exception("Error: map has polyline with only one point")

            # get two points, and round them
            point1 = (
                self.round_to_divisible(collision_line.points[0][0], self.tile_size[0]),
                self.round_to_divisible(collision_line.points[0][1], self.tile_size[1]),
            )
            point2 = (
                self.round_to_divisible(collision_line.points[1][0], self.tile_size[0]),
                self.round_to_divisible(collision_line.points[1][1], self.tile_size[1]),
            )

            # check to see if horizontal or vertical
            line_type = None
            if point1[0] == point2[0] and point1[1] != point2[1]:
                # x's are same, must be vertical
                line_type = "vertical"
            elif point1[0] != point2[0] and point1[1] == point2[1]:
                # y's are same, must be horizontal
                line_type = "horizontal"
            else:
                raise Exception("Error: Points on polyline are not strictly horizontal or vertical....")

            if line_type is "vertical":
                # get all tile coordinates on either side
                x = point1[0] / self.tile_size[0]  # same as point2[0] b/c vertical
                line_start = point1[1]
                line_end = point2[1]
                num_tiles_in_line = abs(line_start - line_end) / self.tile_size[1]  # [1] b/c vertical
                curr_y = line_start / self.tile_size[1]
                for i in range(int(num_tiles_in_line)):
                    if line_start > line_end:  # slightly different
                        # behavior depending on
                        # direction
                        left_side_tile = (x - 1, curr_y - 1)
                        right_side_tile = (x, curr_y - 1)
                        curr_y -= 1
                    else:
                        left_side_tile = (x - 1, curr_y)
                        right_side_tile = (x, curr_y)
                        curr_y += 1

                    # TODO - if we want to enable single-direction
                    # walls (i.e. for jumping) then ask map-designer
                    # to include a special property for the direction
                    # to block, and then here we only block in one
                    # direction, not both.
                    collision_lines_map.add((left_side_tile, "right"))
                    collision_lines_map.add((right_side_tile, "left"))

            elif line_type is "horizontal":
                # get all tile coordinates on either side
                y = point1[1] / self.tile_size[1]  # same as point2[1] b/c horizontal
                line_start = point1[0]
                line_end = point2[0]
                num_tiles_in_line = abs(line_start - line_end) / self.tile_size[0]  # [0] b/c horizontal
                curr_x = line_start / self.tile_size[0]
                for i in range(int(num_tiles_in_line)):
                    if line_start > line_end:  # slightly different
                        # behavior depending on
                        # direction
                        top_side_tile = (curr_x - 1, y - 1)
                        bottom_side_tile = (curr_x - 1, y)
                        curr_x -= 1
                    else:
                        top_side_tile = (curr_x, y - 1)
                        bottom_side_tile = (curr_x, y)
                        curr_x += 1

                    # TODO - if we want to enable single-direction
                    # walls (i.e. for jumping) then ask map-designer
                    # to include a special property for the direction
                    # to block, and then here we only block in one
                    # direction, not both.
                    collision_lines_map.add((top_side_tile, "down"))
                    collision_lines_map.add((bottom_side_tile, "up"))

        return tiles, collision_map, collision_lines_map, mapsize
Example #8
0
    def animate_capture_monster(self, is_captured, num_shakes, monster):
        """ Animation for capturing monsters.

        :param is_captured: boolean representing success of capture
        :param num_shakes: number of shakes before animation ends
        :param monster: the monster
        :return:
        """
        monster_sprite = self._monster_sprite_map.get(monster, None)
        capdev = self.load_sprite('gfx/items/capture_device.png')
        animate = partial(self.animate,
                          capdev.rect,
                          transition='in_quad',
                          duration=1.0)
        scale_sprite(capdev, .4)
        capdev.rect.center = scale(0), scale(0)
        animate(x=monster_sprite.rect.centerx)
        animate(y=monster_sprite.rect.centery)
        self.task(partial(toggle_visible, monster_sprite),
                  1.0)  # make the monster go away temporarily

        def kill():
            self._monster_sprite_map[monster].kill()
            self.hud[monster].kill()
            del self._monster_sprite_map[monster]
            del self.hud[monster]

        # TODO: cache this sprite from the first time it's used.
        # also, should loading animated sprites be more convenient?
        images = list()
        for fn in ["capture%02d.png" % i for i in range(1, 10)]:
            fn = 'animations/technique/' + fn
            image = tools.load_and_scale(fn)
            images.append((image, .07))

        tech = PygAnimation(images, False)
        sprite = Sprite()
        sprite.image = tech
        sprite.rect = tech.get_rect()
        self.task(tech.play, 1.0)
        self.task(partial(self.sprites.add, sprite), 1.0)
        sprite.rect.midbottom = monster_sprite.rect.midbottom

        def shake_ball(initial_delay):
            animate = partial(self.animate,
                              duration=0.1,
                              transition='linear',
                              delay=initial_delay)
            animate(capdev.rect, y=scale(3), relative=True)

            animate = partial(self.animate,
                              duration=0.2,
                              transition='linear',
                              delay=initial_delay + 0.1)
            animate(capdev.rect, y=-scale(6), relative=True)

            animate = partial(self.animate,
                              duration=0.1,
                              transition='linear',
                              delay=initial_delay + 0.3)
            animate(capdev.rect, y=scale(3), relative=True)

        for i in range(0, num_shakes):
            shake_ball(1.8 + i * 1.0)  # leave a 0.6s wait between each shake

        if is_captured:
            self.task(kill, 2 + num_shakes)
        else:
            self.task(partial(toggle_visible, monster_sprite),
                      1.8 + num_shakes * 1.0)  # make the monster appear again!
            self.task(tech.play, 1.8 + num_shakes * 1.0)
            self.task(capdev.kill, 1.8 + num_shakes * 1.0)