示例#1
0
 def append_shapes(self, shapes: arcade.ShapeElementList):
     shapes.append(
         arcade.create_rectangle_filled(self.centerX, self.upperCenterY,
                                        PIPE_WIDTH, self.upperHeight,
                                        self.color))
     shapes.append(
         arcade.create_rectangle_filled(self.centerX, self.lowerCenterY,
                                        PIPE_WIDTH, self.lowerHeight,
                                        self.color))
示例#2
0
def create_isometric_grid_lines(width, height, tile_width, tile_height, color, line_width):

    # Grid lines 1
    shape_list = ShapeElementList()

    for tile_row in range(-1, height):
        tile_x = 0
        start_x, start_y = isometric_grid_to_screen(tile_x, tile_row, width, height, tile_width, tile_height)
        tile_x = width - 1
        end_x, end_y = isometric_grid_to_screen(tile_x, tile_row, width, height, tile_width, tile_height)

        start_x -= tile_width // 2
        end_y -= tile_height // 2

        line = create_line(start_x, start_y, end_x, end_y, color, line_width=line_width)
        shape_list.append(line)

    # Grid lines 2
    for tile_column in range(-1, width):
        tile_y = 0
        start_x, start_y = isometric_grid_to_screen(tile_column, tile_y, width, height, tile_width, tile_height)
        tile_y = height - 1
        end_x, end_y = isometric_grid_to_screen(tile_column, tile_y, width, height, tile_width, tile_height)

        start_x += tile_width // 2
        end_y -= tile_height // 2

        line = create_line(start_x, start_y, end_x, end_y, color, line_width=line_width)
        shape_list.append(line)

    return shape_list
示例#3
0
    def generate_shapes(self):
        size = sqrt(len(self.tiles))

        shapes = []
        for i, tile in enumerate(self.tiles):
            tx = i % size
            ty = i // size

            cx = TILE_SIZE + tx * TILE_SIZE
            cy = TILE_SIZE + ty * TILE_SIZE

            shape = ShapeElementList()
            for y, row in enumerate(reversed(tile.lines)):
                for x, pxl in enumerate(row):
                    if pxl == "#":
                        shape.append(
                            create_rectangle_filled(
                                cx + x * PIXEL,
                                cy + y * PIXEL,
                                PIXEL,
                                PIXEL,
                                color=WHITE,
                            ))
            shapes.append(shape)

            # Add border
            print(shape.center_x, shape.center_y)
            rect = create_rectangle_outline(
                shape.center_x,
                shape.center_y,
                len(tile.lines) * PIXEL,
                len(tile.lines) * PIXEL,
                RED,
                # border_width=1,
            )
            shapes.append(rect)

        return shapes
示例#4
0
    def draw_hit_box(self, color: Color = BLACK, line_thickness: float = 1):
        """
        Draw a sprite's hit-box.

        The 'hit box' drawing is cached, so if you change the color/line thickness
        later, it won't take.

        :param color: Color of box
        :param line_thickness: How thick the box should be
        """

        if self._hit_box_shape is None:

            # Adjust the hitbox
            point_list = []
            for point in self.hit_box:
                # Get a copy of the point
                point = [point[0], point[1]]

                # Scale the point
                if self.scale != 1:
                    point[0] *= self.scale
                    point[1] *= self.scale

                # Rotate the point
                if self.angle:
                    point = rotate_point(point[0], point[1], 0, 0, self.angle)

                point_list.append(point)

            shape = create_line_loop(point_list, color, line_thickness)
            self._hit_box_shape = ShapeElementList()
            self._hit_box_shape.append(shape)

        self._hit_box_shape.center_x = self.center_x
        self._hit_box_shape.center_y = self.center_y
        self._hit_box_shape.angle = self.angle
        self._hit_box_shape.draw()
示例#5
0
    def create_map_debug_grid(self) -> ShapeElementList:
        grid = ShapeElementList()
        h_offset = TILE_HEIGHT // 2
        w_offset = TILE_WIDTH // 2
        # horizontal lines:
        for i in range(self.game.map.rows):
            y = i * TILE_HEIGHT
            h_line = create_line(0, y, self.game.map.width, y, BLACK)
            grid.append(h_line)

            y = i * TILE_HEIGHT + h_offset
            h2_line = create_line(w_offset, y, self.game.map.width, y, WHITE)
            grid.append(h2_line)
        # vertical lines:
        for j in range(self.game.map.columns * 2):
            x = j * TILE_WIDTH
            v_line = create_line(x, 0, x, self.game.map.height, BLACK)
            grid.append(v_line)

            x = j * TILE_WIDTH + w_offset
            v2_line = create_line(x, h_offset, x, self.game.map.height, WHITE)
            grid.append(v2_line)
        return grid
示例#6
0
    def create_shapes_lists(self):
        """
        Create one ShapeElementList for each Map row, to avoid updating single,
        humongous list each time new MapNode is revealed. Smaller lists are
        updated faster.

        :return: List[ShapeElementList]
        """
        self.shapes_lists = {
            row: ShapeElementList()
            for row in range(self.rows)
        }
        if self.loaded:
            r, t = self.screen_width - MARGIN_RIGHT, self.screen_height - MARGIN_TOP
            dx, dy = r - self.max_width // 2 - self.width // 2, t - self.max_height
        else:
            dx, dy = self.minimap_left_and_bottom
        self.move_shapes_lists(dx + 9, dy + 60)
        return self.shapes_lists
示例#7
0
class Sprite:
    """
    Class that represents a 'sprite' on-screen. Most games center around sprites.
    For examples on how to use this class, see:
    http://arcade.academy/examples/index.html#sprites

    Attributes:
        :alpha: Transparency of sprite. 0 is invisible, 255 is opaque.
        :angle: Rotation angle in degrees.
        :radians: Rotation angle in radians.
        :bottom: Set/query the sprite location by using the bottom coordinate. \
        This will be the 'y' of the bottom of the sprite.
        :boundary_left: Used in movement. Left boundary of moving sprite.
        :boundary_right: Used in movement. Right boundary of moving sprite.
        :boundary_top: Used in movement. Top boundary of moving sprite.
        :boundary_bottom: Used in movement. Bottom boundary of moving sprite.
        :center_x: X location of the center of the sprite
        :center_y: Y location of the center of the sprite
        :change_x: Movement vector, in the x direction.
        :change_y: Movement vector, in the y direction.
        :change_angle: Change in rotation.
        :color: Color tint the sprite
        :collision_radius: Used as a fast-check to see if this item is close \
        enough to another item. If this check works, we do a slower more accurate check. \
        You probably don't want to use this field. Instead, set points in the \
        hit box.
        :cur_texture_index: Index of current texture being used.
        :guid: Unique identifier for the sprite. Useful when debugging.
        :height: Height of the sprite.
        :force: Force being applied to the sprite. Useful when used with Pymunk \
        for physics.
        :left: Set/query the sprite location by using the left coordinate. This \
        will be the 'x' of the left of the sprite.
        :points: Points, in relation to the center of the sprite, that are used \
        for collision detection. Arcade defaults to creating points for a rectangle \
        that encompass the image. If you are creating a ramp or making better \
        hit-boxes, you can custom-set these.
        :position: A list with the (x, y) of where the sprite is.
        :repeat_count_x: Unused
        :repeat_count_y: Unused
        :right: Set/query the sprite location by using the right coordinate. \
        This will be the 'y=x' of the right of the sprite.
        :sprite_lists: List of all the sprite lists this sprite is part of.
        :texture: `Texture` class with the current texture.
        :textures: List of textures associated with this sprite.
        :top: Set/query the sprite location by using the top coordinate. This \
        will be the 'y' of the top of the sprite.
        :scale: Scale the image up or down. Scale of 1.0 is original size, 0.5 \
        is 1/2 height and width.
        :velocity: Change in x, y expressed as a list. (0, 0) would be not moving.
        :width: Width of the sprite

    It is common to over-ride the `update` method and provide mechanics on
    movement or other sprite updates.

    """
    def __init__(self,
                 filename: str = None,
                 scale: float = 1,
                 image_x: float = 0,
                 image_y: float = 0,
                 image_width: float = 0,
                 image_height: float = 0,
                 center_x: float = 0,
                 center_y: float = 0,
                 repeat_count_x: int = 1,
                 repeat_count_y: int = 1,
                 flipped_horizontally: bool = False,
                 flipped_vertically: bool = False,
                 flipped_diagonally: bool = False,
                 mirrored: bool = None,
                 hit_box_algorithm: str = "Simple",
                 hit_box_detail: float = 4.5):
        """
        Create a new sprite.

        :param str filename: Filename of an image that represents the sprite.
        :param float scale: Scale the image up or down. Scale of 1.0 is none.
        :param float image_x: X offset to sprite within sprite sheet.
        :param float image_y: Y offset to sprite within sprite sheet.
        :param float image_width: Width of the sprite
        :param float image_height: Height of the sprite
        :param float center_x: Location of the sprite
        :param float center_y: Location of the sprite
        :param bool flipped_horizontally: Mirror the sprite image. Flip left/right across vertical axis.
        :param bool flipped_vertically: Flip the image up/down across the horizontal axis.
        :param bool flipped_diagonally: Transpose the image, flip it across the diagonal.
        :param mirrored: Deprecated.
        :param str hit_box_algorithm: One of 'None', 'Simple' or 'Detailed'. \
        Defaults to 'Simple'. Use 'Simple' for the :data:`PhysicsEngineSimple`, \
        :data:`PhysicsEnginePlatformer` \
        and 'Detailed' for the :data:`PymunkPhysicsEngine`.

            .. figure:: images/hit_box_algorithm_none.png
               :width: 40%

               hit_box_algorithm = "None"

            .. figure:: images/hit_box_algorithm_simple.png
               :width: 55%

               hit_box_algorithm = "Simple"

            .. figure:: images/hit_box_algorithm_detailed.png
               :width: 75%

               hit_box_algorithm = "Detailed"
        :param float hit_box_detail: Float, defaults to 4.5. Used with 'Detailed' to hit box


        """

        if image_width < 0:
            raise ValueError("Width of image can't be less than zero.")

        if image_height < 0:
            raise ValueError(
                "Height entered is less than zero. Height must be a positive float."
            )

        if image_width == 0 and image_height != 0:
            raise ValueError("Width can't be zero.")

        if image_height == 0 and image_width != 0:
            raise ValueError("Height can't be zero.")

        if mirrored is not None:
            from warnings import warn
            warn(
                "In Sprite, the 'mirrored' parameter is deprecated. Use 'flipped_horizontally' instead.",
                DeprecationWarning)
            flipped_horizontally = mirrored

        if hit_box_algorithm != "Simple" and \
           hit_box_algorithm != "Detailed" and \
           hit_box_algorithm != "None":
            raise ValueError(
                "hit_box_algorithm must be 'Simple', 'Detailed', or 'None'.")
        self._hit_box_algorithm = hit_box_algorithm

        self._hit_box_detail = hit_box_detail

        self.sprite_lists: List[Any] = []
        self.physics_engines: List[Any] = []

        self._texture: Optional[Texture]

        self._points: Optional[PointList] = None

        self._hit_box_shape: Optional[ShapeElementList] = None

        if filename is not None:
            try:
                self._texture = load_texture(
                    filename,
                    image_x,
                    image_y,
                    image_width,
                    image_height,
                    flipped_horizontally=flipped_horizontally,
                    flipped_vertically=flipped_vertically,
                    flipped_diagonally=flipped_diagonally,
                    hit_box_algorithm=hit_box_algorithm,
                    hit_box_detail=hit_box_detail)

            except Exception as e:
                print(f"Unable to load {filename} {e}")
                self._texture = None

            if self._texture:
                self.textures = [self._texture]
                # Ignore the texture's scale and use ours
                self._width = self._texture.width * scale
                self._height = self._texture.height * scale
            else:
                self.textures = []
                self._width = 0
                self._height = 0
        else:
            self.textures = []
            self._texture = None
            self._width = 0
            self._height = 0

        self.cur_texture_index = 0

        self._scale = scale
        self._position = (center_x, center_y)
        self._angle = 0.0

        self.velocity = [0.0, 0.0]
        self.change_angle = 0.0

        self.boundary_left = None
        self.boundary_right = None
        self.boundary_top = None
        self.boundary_bottom = None

        self.properties: Dict[str, Any] = {}

        self._alpha = 255
        self._collision_radius: Optional[float] = None
        self._color: RGB = (255, 255, 255)

        if self._texture and not self._points:
            self._points = self._texture.hit_box_points

        self._point_list_cache: Optional[PointList] = None

        self.force = [0, 0]
        self.guid: Optional[str] = None

        self.repeat_count_x = repeat_count_x
        self.repeat_count_y = repeat_count_y
        self._texture_transform = Matrix3x3()

        # Used if someone insists on doing a sprite.draw()
        self._sprite_list = None

        self.pymunk = PyMunk()

    def append_texture(self, texture: Texture):
        """
        Appends a new texture to the list of textures that can be
        applied to this sprite.

        :param arcade.Texture texture: Texture to add ot the list of available textures

        """
        self.textures.append(texture)

    def _get_position(self) -> Tuple[float, float]:
        """
        Get the center x and y coordinates of the sprite.

        Returns:
            (center_x, center_y)
        """
        return self._position

    def _set_position(self, new_value: Tuple[float, float]):
        """
        Set the center x and y coordinates of the sprite.

        Args:
            new_value:

        Returns:

        """
        if new_value[0] != self._position[0] or new_value[1] != self._position[
                1]:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._position = new_value
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_location(self)

    position = property(_get_position, _set_position)

    def set_position(self, center_x: float, center_y: float):
        """
        Set a sprite's position

        :param float center_x: New x position of sprite
        :param float center_y: New y position of sprite
        """
        self._set_position((center_x, center_y))

    def set_points(self, points: PointList):
        """
        Set a sprite's hitbox
        """
        from warnings import warn
        warn('set_points has been deprecated. Use set_hit_box instead.',
             DeprecationWarning)

        self._points = points

    def get_points(self) -> PointList:
        """
        Get the points that make up the hit box for the rect that makes up the
        sprite, including rotation and scaling.
        """
        from warnings import warn
        warn('get_points has been deprecated. Use get_hit_box instead.',
             DeprecationWarning)

        return self.get_adjusted_hit_box()

    points = property(get_points, set_points)

    def set_hit_box(self, points: PointList):
        """
        Set a sprite's hit box. Hit box should be relative to a sprite's center,
        and with a scale of 1.0.
        Points will be scaled with get_adjusted_hit_box.
        """
        self._point_list_cache = None
        self._hit_box_shape = None
        self._points = points

    def get_hit_box(self) -> PointList:
        """
        Get a sprite's hit box, unadjusted for translation, rotation, or scale.
        """
        # If there is no hitbox, use the width/height to get one
        if self._points is None and self._texture:
            self._points = self._texture.hit_box_points

        if self._points is None and self._width:
            x1, y1 = -self._width / 2, -self._height / 2
            x2, y2 = +self._width / 2, -self._height / 2
            x3, y3 = +self._width / 2, +self._height / 2
            x4, y4 = -self._width / 2, +self._height / 2

            self._points = ((x1, y1), (x2, y2), (x3, y3), (x4, y4))

        if self._points is None and self.texture is not None:
            self._points = self.texture.hit_box_points

        if self._points is None:
            raise ValueError(
                "Error trying to get the hit box of a sprite, when no hit box is set.\nPlease make sure the "
                "Sprite.texture is set to a texture before trying to draw or do collision testing.\n"
                "Alternatively, manually call Sprite.set_hit_box with points for your hitbox."
            )

        return self._points

    hit_box = property(get_hit_box, set_hit_box)

    def get_adjusted_hit_box(self) -> PointList:
        """
        Get the points that make up the hit box for the rect that makes up the
        sprite, including rotation and scaling.
        """

        # If we've already calculated the adjusted hit box, use the cached version
        if self._point_list_cache is not None:
            return self._point_list_cache

        # Adjust the hitbox
        point_list = []
        for point in self.hit_box:
            # Get a copy of the point
            point = [point[0], point[1]]

            # Scale the point
            if self.scale != 1:
                point[0] *= self.scale
                point[1] *= self.scale

            # Rotate the point
            if self.angle:
                point = rotate_point(point[0], point[1], 0, 0, self.angle)

            # Offset the point
            point = [point[0] + self.center_x, point[1] + self.center_y]
            point_list.append(point)

        # Cache the results
        self._point_list_cache = point_list

        # if self.texture:
        #     print(self.texture.name, self._point_list_cache)

        return self._point_list_cache

    def forward(self, speed: float = 1.0):
        """
        Set a Sprite's position to speed by its angle
        :param speed: speed factor
        """
        self.change_x += math.cos(self.radians) * speed
        self.change_y += math.sin(self.radians) * speed

    def reverse(self, speed: float = 1.0):
        """
        Set a new speed, but in reverse.
        :param speed: speed factor
        """
        self.forward(-speed)

    def strafe(self, speed: float = 1.0):
        """
        Set a sprites position perpendicular to its angle by speed
        :param speed: speed factor
        """
        self.change_x += -math.sin(self.radians) * speed
        self.change_y += math.cos(self.radians) * speed

    def turn_right(self, theta: float = 90):
        """
        Rotate the sprite right a certain number of degrees.
        :param theta: change in angle
        """
        self.angle -= theta

    def turn_left(self, theta: float = 90):
        """
        Rotate the sprite left a certain number of degrees.
        :param theta: change in angle
        """
        self.angle += theta

    def stop(self):
        """
        Stop the Sprite's motion
        """
        self.change_x = 0
        self.change_y = 0
        self.change_angle = 0

    def _set_collision_radius(self, collision_radius: float):
        """
        Set the collision radius.

        .. note:: Final collision checking is done via geometry that was
            set in the hit_box property. These points are used in the
            check_for_collision function. This collision_radius variable
            is used as a "pre-check." We do a super-fast check with
            collision_radius and see if the sprites are close. If they are,
            then we look at the geometry and figure if they really are colliding.

        :param float collision_radius: Collision radius
        """
        self._collision_radius = collision_radius

    def _get_collision_radius(self):
        """
        Get the collision radius.

        .. note:: Final collision checking is done via geometry that was
            set in get_points/set_points. These points are used in the
            check_for_collision function. This collision_radius variable
            is used as a "pre-check." We do a super-fast check with
            collision_radius and see if the sprites are close. If they are,
            then we look at the geometry and figure if they really are colliding.

        """
        if not self._collision_radius:
            self._collision_radius = max(self.width, self.height)
        return self._collision_radius

    collision_radius = property(_get_collision_radius, _set_collision_radius)

    def __lt__(self, other):
        return self._texture.texture_id.value < other.texture.texture_id.value

    def clear_spatial_hashes(self):
        """
        Search the sprite lists this sprite is a part of, and remove it
        from any spatial hashes it is a part of.

        """
        for sprite_list in self.sprite_lists:
            if sprite_list._use_spatial_hash and sprite_list.spatial_hash is not None:
                try:
                    sprite_list.spatial_hash.remove_object(self)
                except ValueError:
                    print(
                        "Warning, attempt to remove item from spatial hash that doesn't exist in the hash."
                    )

    def add_spatial_hashes(self):
        """
        Add spatial hashes for this sprite in all the sprite lists it is part of.
        """
        for sprite_list in self.sprite_lists:
            if sprite_list._use_spatial_hash:
                sprite_list.spatial_hash.insert_object_for_box(self)

    def _get_bottom(self) -> float:
        """
        Return the y coordinate of the bottom of the sprite.
        """
        points = self.get_adjusted_hit_box()

        # This happens if our point list is empty, such as a completely
        # transparent sprite.
        if len(points) == 0:
            return self.center_x

        my_min = points[0][1]
        for point in range(1, len(points)):
            my_min = min(my_min, points[point][1])
        return my_min

    def _set_bottom(self, amount: float):
        """
        Set the location of the sprite based on the bottom y coordinate.
        """
        lowest = self._get_bottom()
        diff = lowest - amount
        self.center_y -= diff

    bottom = property(_get_bottom, _set_bottom)

    def _get_top(self) -> float:
        """
        Return the y coordinate of the top of the sprite.
        """
        points = self.get_adjusted_hit_box()

        # This happens if our point list is empty, such as a completely
        # transparent sprite.
        if len(points) == 0:
            return self.center_x

        my_max = points[0][1]
        for i in range(1, len(points)):
            my_max = max(my_max, points[i][1])
        return my_max

    def _set_top(self, amount: float):
        """ The highest y coordinate. """
        highest = self._get_top()
        diff = highest - amount
        self.center_y -= diff

    top = property(_get_top, _set_top)

    def _get_width(self) -> float:
        """ Get the width of the sprite. """
        return self._width

    def _set_width(self, new_value: float):
        """ Set the width in pixels of the sprite. """
        if new_value != self._width:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._width = new_value
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_size(self)

    width = property(_get_width, _set_width)

    def _get_height(self) -> float:
        """ Get the height in pixels of the sprite. """
        return self._height

    def _set_height(self, new_value: float):
        """ Set the center x coordinate of the sprite. """
        if new_value != self._height:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._height = new_value
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_height(self)

    height = property(_get_height, _set_height)

    def _get_scale(self) -> float:
        """ Get the scale of the sprite. """
        return self._scale

    def _set_scale(self, new_value: float):
        """ Set the center x coordinate of the sprite. """
        if new_value != self._scale:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._scale = new_value
            if self._texture:
                self._width = self._texture.width * self._scale
                self._height = self._texture.height * self._scale
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_size(self)

    scale = property(_get_scale, _set_scale)

    def rescale_relative_to_point(self, point: Point, factor: float) -> None:
        """ Rescale the sprite relative to a different point than its center. """
        self.scale *= factor
        self.center_x = (self.center_x - point[0]) * factor + point[0]
        self.center_y = (self.center_y - point[1]) * factor + point[1]

    def _get_center_x(self) -> float:
        """ Get the center x coordinate of the sprite. """
        return self._position[0]

    def _set_center_x(self, new_value: float):
        """ Set the center x coordinate of the sprite. """
        if new_value != self._position[0]:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._position = (new_value, self._position[1])
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_location(self)

    center_x = property(_get_center_x, _set_center_x)

    def _get_center_y(self) -> float:
        """ Get the center y coordinate of the sprite. """
        return self._position[1]

    def _set_center_y(self, new_value: float):
        """ Set the center y coordinate of the sprite. """
        if new_value != self._position[1]:
            self.clear_spatial_hashes()
            self._point_list_cache = None
            self._position = (self._position[0], new_value)
            self.add_spatial_hashes()

            for sprite_list in self.sprite_lists:
                sprite_list.update_location(self)

    center_y = property(_get_center_y, _set_center_y)

    def _get_change_x(self) -> float:
        """ Get the velocity in the x plane of the sprite. """
        return self.velocity[0]

    def _set_change_x(self, new_value: float):
        """ Set the velocity in the x plane of the sprite. """
        self.velocity[0] = new_value

    change_x = property(_get_change_x, _set_change_x)

    def _get_change_y(self) -> float:
        """ Get the velocity in the y plane of the sprite. """
        return self.velocity[1]

    def _set_change_y(self, new_value: float):
        """ Set the velocity in the y plane of the sprite. """
        self.velocity[1] = new_value

    change_y = property(_get_change_y, _set_change_y)

    def _get_angle(self) -> float:
        """ Get the angle of the sprite's rotation. """
        return self._angle

    def _set_angle(self, new_value: float):
        """ Set the angle of the sprite's rotation. """
        if new_value != self._angle:
            self.clear_spatial_hashes()
            self._angle = new_value
            self._point_list_cache = None

            for sprite_list in self.sprite_lists:
                sprite_list.update_angle(self)

            self.add_spatial_hashes()

    angle = property(_get_angle, _set_angle)

    def _to_radians(self) -> float:
        """
        Converts the degrees representation of self.angle into radians.
        :return: float
        """
        return self.angle / 180.0 * math.pi

    def _from_radians(self, new_value: float):
        """
        Converts a radian value into degrees and stores it into angle.
        """
        self.angle = new_value * 180.0 / math.pi

    radians = property(_to_radians, _from_radians)

    def _get_left(self) -> float:
        """
        Return the x coordinate of the left-side of the sprite's hit box.
        """
        points = self.get_adjusted_hit_box()

        # This happens if our point list is empty, such as a completely
        # transparent sprite.
        if len(points) == 0:
            return self.center_x

        my_min = points[0][0]
        for i in range(1, len(points)):
            my_min = min(my_min, points[i][0])
        return my_min

    def _set_left(self, amount: float):
        """ The left most x coordinate. """
        leftmost = self._get_left()
        diff = amount - leftmost
        self.center_x += diff

    left = property(_get_left, _set_left)

    def _get_right(self) -> float:
        """
        Return the x coordinate of the right-side of the sprite's hit box.
        """

        points = self.get_adjusted_hit_box()

        # This happens if our point list is empty, such as a completely
        # transparent sprite.
        if len(points) == 0:
            return self.center_x

        my_max = points[0][0]
        for point in range(1, len(points)):
            my_max = max(my_max, points[point][0])
        return my_max

    def _set_right(self, amount: float):
        """ The right most x coordinate. """
        rightmost = self._get_right()
        diff = rightmost - amount
        self.center_x -= diff

    right = property(_get_right, _set_right)

    def set_texture(self, texture_no: int):
        """
        Sets texture by texture id. Should be renamed because it takes
        a number rather than a texture, but keeping
        this for backwards compatibility.
        """
        if self.textures[texture_no] == self._texture:
            return

        texture = self.textures[texture_no]
        self.clear_spatial_hashes()
        self._point_list_cache = None
        self._texture = texture
        self._width = texture.width * self.scale
        self._height = texture.height * self.scale
        self.add_spatial_hashes()
        for sprite_list in self.sprite_lists:
            sprite_list.update_texture(self)

    def _set_texture2(self, texture: Texture):
        """ Sets texture by texture id. Should be renamed but keeping
        this for backwards compatibility. """
        if texture == self._texture:
            return

        assert (isinstance(texture, Texture))

        self.clear_spatial_hashes()
        self._point_list_cache = None
        self._texture = texture
        self._width = texture.width * self.scale
        self._height = texture.height * self.scale
        self.add_spatial_hashes()
        for sprite_list in self.sprite_lists:
            sprite_list.update_texture(self)

    def _get_texture(self):
        return self._texture

    texture = property(_get_texture, _set_texture2)

    def _get_texture_transform(self) -> Matrix3x3:
        return self._texture_transform

    def _set_texture_transform(self, m: Matrix3x3):
        self._texture_transform = m

    texture_transform = property(_get_texture_transform,
                                 _set_texture_transform)

    def _get_color(self) -> RGB:
        """
        Return the RGB color associated with the sprite.
        """
        return self._color

    def _set_color(self, color: Color):
        """
        Set the current sprite color as a RGB value
        """
        if color is None:
            raise ValueError("Color must be three or four ints from 0-255")
        if len(color) == 3:
            if self._color[0] == color[0] \
                    and self._color[1] == color[1] \
                    and self._color[2] == color[2]:
                return
        elif len(color) == 4:
            color = cast(List, color)  # Prevent typing error
            if self._color[0] == color[0] \
                    and self._color[1] == color[1] \
                    and self._color[2] == color[2]\
                    and self.alpha == color[3]:
                return
            self.alpha = color[3]
        else:
            raise ValueError("Color must be three or four ints from 0-255")

        self._color = color[0], color[1], color[2]

        for sprite_list in self.sprite_lists:
            sprite_list.update_color(self)

    color = property(_get_color, _set_color)

    def _get_alpha(self) -> int:
        """
        Return the alpha associated with the sprite.
        """
        return self._alpha

    def _set_alpha(self, alpha: int):
        """
        Set the current sprite color as a value
        """
        if alpha < 0 or alpha > 255:
            raise ValueError(
                f"Invalid value for alpha. Must be 0 to 255, received {alpha}")

        self._alpha = alpha
        for sprite_list in self.sprite_lists:
            sprite_list.update_color(self)

    alpha = property(_get_alpha, _set_alpha)

    def register_sprite_list(self, new_list):
        """
        Register this sprite as belonging to a list. We will automatically
        remove ourselves from the the list when kill() is called.
        """
        self.sprite_lists.append(new_list)

    def register_physics_engine(self, physics_engine):
        """ Called by the Pymunk physics engine when this sprite is added
        to that physics engine. Lets the sprite know about the engine and
        remove itself if it gets deleted. """
        self.physics_engines.append(physics_engine)

    def pymunk_moved(self, physics_engine, dx, dy, d_angle):
        """ Called by the pymunk physics engine if this sprite moves. """
        pass

    def draw(self):
        """ Draw the sprite. """

        if self._sprite_list is None:
            from arcade import SpriteList
            self._sprite_list = SpriteList()
            self._sprite_list.append(self)

        self._sprite_list.draw()

    def draw_hit_box(self, color: Color = BLACK, line_thickness: float = 1):
        """
        Draw a sprite's hit-box.

        The 'hit box' drawing is cached, so if you change the color/line thickness
        later, it won't take.

        :param color: Color of box
        :param line_thickness: How thick the box should be
        """

        if self._hit_box_shape is None:

            # Adjust the hitbox
            point_list = []
            for point in self.hit_box:
                # Get a copy of the point
                point = [point[0], point[1]]

                # Scale the point
                if self.scale != 1:
                    point[0] *= self.scale
                    point[1] *= self.scale

                # Rotate the point
                if self.angle:
                    point = rotate_point(point[0], point[1], 0, 0, self.angle)

                point_list.append(point)

            shape = create_line_loop(point_list, color, line_thickness)
            self._hit_box_shape = ShapeElementList()
            self._hit_box_shape.append(shape)

        self._hit_box_shape.center_x = self.center_x
        self._hit_box_shape.center_y = self.center_y
        self._hit_box_shape.angle = self.angle
        self._hit_box_shape.draw()

        # point_list = self.get_adjusted_hit_box()
        # draw_polygon_outline(point_list, color, line_thickness)

    def update(self):
        """
        Update the sprite.
        """
        self.position = [
            self._position[0] + self.change_x,
            self._position[1] + self.change_y
        ]
        self.angle += self.change_angle

    def on_update(self, delta_time: float = 1 / 60):
        """
        Update the sprite. Similar to update, but also takes a delta-time.
        """
        pass

    def update_animation(self, delta_time: float = 1 / 60):
        """
        Override this to add code that will change
        what image is shown, so the sprite can be
        animated.
        """
        pass

    def remove_from_sprite_lists(self):
        """
        Remove the sprite from all sprite lists.
        """
        if len(self.sprite_lists) > 0:
            # We can't modify a list as we iterate through it, so create a copy.
            sprite_lists = self.sprite_lists.copy()
        else:
            # If the list is a size 1, we don't need to copy
            sprite_lists = self.sprite_lists

        for sprite_list in sprite_lists:
            if self in sprite_list:
                sprite_list.remove(self)

        for engine in self.physics_engines:
            engine.remove_sprite(self)

        self.physics_engines.clear()
        self.sprite_lists.clear()

    def kill(self):
        """
        Alias of `remove_from_sprite_lists`
        """
        self.remove_from_sprite_lists()

    def collides_with_point(self, point: Point) -> bool:
        """Check if point is within the current sprite.

        Args:
            self: Current sprite
            point: Point to check.

        Returns:
            True if the point is contained within the sprite's boundary.
        """
        from arcade.geometry import is_point_in_polygon

        x, y = point
        return is_point_in_polygon(x, y, self.get_adjusted_hit_box())

    def collides_with_sprite(self, other: 'Sprite') -> bool:
        """Will check if a sprite is overlapping (colliding) another Sprite.

        Args:
            self: Current Sprite.
            other: The other sprite to check against.

        Returns:
            True or False, whether or not they are overlapping.
        """
        from arcade import check_for_collision
        return check_for_collision(self, other)

    def collides_with_list(self, sprite_list: 'SpriteList') -> list:
        """Check if current sprite is overlapping with any other sprite in a list

        Args:
            self: current Sprite
            sprite_list: SpriteList to check against

        Returns:
            SpriteList of all overlapping Sprites from the original SpriteList
        """
        from arcade import check_for_collision_with_list
        # noinspection PyTypeChecker
        return check_for_collision_with_list(self, sprite_list)
示例#8
0
 def append_shapes(self, shapes: arcade.ShapeElementList):
     radius = self.radius
     shapes.append(
         arcade.create_ellipse_filled(self.centerX, self.centerY, radius,
                                      radius, self.color))