901 lines
31 KiB
Python
901 lines
31 KiB
Python
"""
|
|
This module manages all of the code around Sprites.
|
|
|
|
For information on Spatial Hash Maps, see:
|
|
https://www.gamedev.net/articles/programming/general-and-gameplay-programming/spatial-hashing-r2697/
|
|
"""
|
|
|
|
import math
|
|
import dataclasses
|
|
|
|
import PIL.Image
|
|
|
|
from arcade.draw_commands import load_texture
|
|
from arcade.draw_commands import draw_texture_rectangle
|
|
from arcade.draw_commands import Texture
|
|
from arcade.draw_commands import rotate_point
|
|
from arcade.arcade_types import RGB, Point
|
|
|
|
from typing import Sequence
|
|
from typing import Tuple
|
|
|
|
FACE_RIGHT = 1
|
|
FACE_LEFT = 2
|
|
FACE_UP = 3
|
|
FACE_DOWN = 4
|
|
|
|
|
|
class Sprite:
|
|
"""
|
|
Class that represents a 'sprite' on-screen.
|
|
|
|
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.
|
|
: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):
|
|
"""
|
|
Create a new sprite.
|
|
|
|
Args:
|
|
filename (str): Filename of an image that represents the sprite.
|
|
scale (float): Scale the image up or down. Scale of 1.0 is none.
|
|
image_x (float): Scale the image up or down. Scale of 1.0 is none.
|
|
image_y (float): Scale the image up or down. Scale of 1.0 is none.
|
|
image_width (float): Width of the sprite
|
|
image_height (float): Height of the sprite
|
|
center_x (float): Location of the sprite
|
|
center_y (float): Location of the sprite
|
|
|
|
"""
|
|
|
|
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.")
|
|
|
|
self.sprite_lists = []
|
|
|
|
if filename is not None:
|
|
self._texture = load_texture(filename, image_x, image_y,
|
|
image_width, image_height)
|
|
|
|
self.textures = [self._texture]
|
|
self._width = self._texture.width * scale
|
|
self._height = self._texture.height * scale
|
|
self._texture.scale = scale
|
|
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]
|
|
self.change_angle = 0
|
|
|
|
self.boundary_left = None
|
|
self.boundary_right = None
|
|
self.boundary_top = None
|
|
self.boundary_bottom = None
|
|
|
|
self.properties = {}
|
|
|
|
self._alpha = 255
|
|
self._collision_radius = None
|
|
self._color = (255, 255, 255)
|
|
|
|
self._points = None
|
|
self._point_list_cache = None
|
|
|
|
self.force = [0, 0]
|
|
self.guid = None
|
|
|
|
self.repeat_count_x = repeat_count_x
|
|
self.repeat_count_y = repeat_count_y
|
|
|
|
def append_texture(self, texture: Texture):
|
|
"""
|
|
Appends a new texture to the list of textures that can be
|
|
applied to this sprite.
|
|
|
|
:param Texture texture: Texture to add ot the list of available textures
|
|
|
|
"""
|
|
self.textures.append(texture)
|
|
|
|
def _get_position(self) -> (float, float):
|
|
"""
|
|
Get the center x coordinate of the sprite.
|
|
|
|
Returns:
|
|
(width, height)
|
|
"""
|
|
return self._position
|
|
|
|
def _set_position(self, new_value: (float, float)):
|
|
"""
|
|
Set the center x coordinate 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[0], self._position[1] = 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: Sequence[Sequence[float]]):
|
|
"""
|
|
Set a sprite's position
|
|
"""
|
|
self._points = points
|
|
|
|
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):
|
|
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):
|
|
self.angle -= theta
|
|
|
|
def turn_left(self, theta: float = 90):
|
|
self.angle += theta
|
|
|
|
def stop(self):
|
|
"""
|
|
Stop the Sprite's motion
|
|
"""
|
|
self.change_x = 0
|
|
self.change_y = 0
|
|
self.change_angle = 0
|
|
|
|
def get_points(self) -> Tuple[Tuple[float, float]]:
|
|
"""
|
|
Get the corner points for the rect that makes up the sprite.
|
|
"""
|
|
if self._point_list_cache is not None:
|
|
return self._point_list_cache
|
|
|
|
if self._points is not None:
|
|
point_list = []
|
|
for point in range(len(self._points)):
|
|
point = (self._points[point][0] + self.center_x,
|
|
self._points[point][1] + self.center_y)
|
|
point_list.append(point)
|
|
self._point_list_cache = tuple(point_list)
|
|
else:
|
|
x1, y1 = rotate_point(self.center_x - self.width / 2,
|
|
self.center_y - self.height / 2,
|
|
self.center_x,
|
|
self.center_y,
|
|
self.angle)
|
|
x2, y2 = rotate_point(self.center_x + self.width / 2,
|
|
self.center_y - self.height / 2,
|
|
self.center_x,
|
|
self.center_y,
|
|
self.angle)
|
|
x3, y3 = rotate_point(self.center_x + self.width / 2,
|
|
self.center_y + self.height / 2,
|
|
self.center_x,
|
|
self.center_y,
|
|
self.angle)
|
|
x4, y4 = rotate_point(self.center_x - self.width / 2,
|
|
self.center_y + self.height / 2,
|
|
self.center_x,
|
|
self.center_y,
|
|
self.angle)
|
|
|
|
self._point_list_cache = ((x1, y1), (x2, y2), (x3, y3), (x4, y4))
|
|
|
|
return self._point_list_cache
|
|
|
|
points = property(get_points, set_points)
|
|
|
|
def _set_collision_radius(self, collision_radius: float):
|
|
"""
|
|
Set 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.
|
|
|
|
: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):
|
|
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_points()
|
|
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_points()
|
|
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_position(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_position(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._height:
|
|
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_position(self)
|
|
|
|
scale = property(_get_scale, _set_scale)
|
|
|
|
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[0] = new_value
|
|
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[1] = 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
|
|
self.add_spatial_hashes()
|
|
|
|
for sprite_list in self.sprite_lists:
|
|
sprite_list.update_angle(self)
|
|
|
|
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:
|
|
"""
|
|
Left-most coordinate.
|
|
"""
|
|
points = self.get_points()
|
|
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.
|
|
"""
|
|
|
|
points = self.get_points()
|
|
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 * texture.scale
|
|
self._height = texture.height * texture.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
|
|
|
|
self.clear_spatial_hashes()
|
|
self._point_list_cache = None
|
|
self._texture = texture
|
|
self._width = texture.width * texture.scale
|
|
self._height = texture.height * texture.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_color(self) -> RGB:
|
|
"""
|
|
Return the RGB color associated with the sprite.
|
|
"""
|
|
return self._color
|
|
|
|
def _set_color(self, color: RGB):
|
|
"""
|
|
Set the current sprite color as a RGB value
|
|
"""
|
|
self._color = color
|
|
for sprite_list in self.sprite_lists:
|
|
sprite_list.update_position(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
|
|
"""
|
|
self._alpha = alpha
|
|
for sprite_list in self.sprite_lists:
|
|
sprite_list.update_position(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 draw(self):
|
|
""" Draw the sprite. """
|
|
|
|
draw_texture_rectangle(self.center_x, self.center_y,
|
|
self.width, self.height,
|
|
self._texture, self.angle, self.alpha, # TODO: review this function
|
|
repeat_count_x=self.repeat_count_x,
|
|
repeat_count_y=self.repeat_count_y)
|
|
|
|
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 update_animation(self):
|
|
"""
|
|
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.
|
|
"""
|
|
for sprite_list in self.sprite_lists:
|
|
if self in sprite_list:
|
|
sprite_list.remove(self)
|
|
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.points)
|
|
|
|
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.geometry import check_for_collision
|
|
|
|
return check_for_collision(self, other)
|
|
|
|
def collides_with_list(self, sprite_list: list) -> 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.geometry import check_for_collision_with_list
|
|
return check_for_collision_with_list(self, sprite_list)
|
|
|
|
|
|
class AnimatedTimeSprite(Sprite):
|
|
"""
|
|
Sprite for platformer games that supports animations.
|
|
"""
|
|
|
|
def __init__(self, scale: float = 1,
|
|
image_x: float = 0, image_y: float = 0,
|
|
center_x: float = 0, center_y: float = 0):
|
|
|
|
super().__init__(scale=scale, image_x=image_x, image_y=image_y,
|
|
center_x=center_x, center_y=center_y)
|
|
self.state = FACE_RIGHT
|
|
self.cur_texture_index = 0
|
|
self.texture_change_frames = 5
|
|
self.frame = 0
|
|
|
|
def update_animation(self):
|
|
"""
|
|
Logic for selecting the proper texture to use.
|
|
"""
|
|
if self.frame % self.texture_change_frames == 0:
|
|
self.cur_texture_index += 1
|
|
if self.cur_texture_index >= len(self.textures):
|
|
self.cur_texture_index = 0
|
|
self.set_texture(self.cur_texture_index)
|
|
self.frame += 1
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class AnimationKeyframe:
|
|
tile_id: int
|
|
duration: int
|
|
image: PIL.Image
|
|
|
|
|
|
class AnimatedTimeBasedSprite(Sprite):
|
|
"""
|
|
Sprite for platformer games that supports animations.
|
|
"""
|
|
|
|
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=1, repeat_count_y=1):
|
|
|
|
super().__init__(filename=filename, scale=scale, image_x=image_x, image_y=image_y,
|
|
image_width=image_width, image_height=image_height,
|
|
center_x=center_x, center_y=center_y)
|
|
self.cur_frame = 0
|
|
self.frames = []
|
|
self.time_counter = 0.0
|
|
|
|
def update_animation(self, delta_time: float):
|
|
"""
|
|
Logic for selecting the proper texture to use.
|
|
"""
|
|
self.time_counter += delta_time
|
|
while self.time_counter > self.frames[self.cur_frame].duration / 1000.0:
|
|
self.time_counter -= self.frames[self.cur_frame].duration / 1000.0
|
|
self.cur_frame += 1
|
|
if self.cur_frame >= len(self.frames):
|
|
self.cur_frame = 0
|
|
source = self.frames[self.cur_frame].image.source
|
|
# print(f"Advance to frame {self.cur_frame}: {source}")
|
|
self.texture = load_texture(source, scale=self.scale)
|
|
|
|
|
|
class AnimatedWalkingSprite(Sprite):
|
|
"""
|
|
Sprite for platformer games that supports animations.
|
|
"""
|
|
|
|
def __init__(self, scale: float = 1,
|
|
image_x: float = 0, image_y: float = 0,
|
|
center_x: float = 0, center_y: float = 0):
|
|
super().__init__(scale=scale, image_x=image_x, image_y=image_y,
|
|
center_x=center_x, center_y=center_y)
|
|
self.state = FACE_RIGHT
|
|
self.stand_right_textures = None
|
|
self.stand_left_textures = None
|
|
self.walk_left_textures = None
|
|
self.walk_right_textures = None
|
|
self.walk_up_textures = None
|
|
self.walk_down_textures = None
|
|
self.cur_texture_index = 0
|
|
self.texture_change_distance = 20
|
|
self.last_texture_change_center_x = 0
|
|
self.last_texture_change_center_y = 0
|
|
|
|
def update_animation(self):
|
|
"""
|
|
Logic for selecting the proper texture to use.
|
|
"""
|
|
x1 = self.center_x
|
|
x2 = self.last_texture_change_center_x
|
|
y1 = self.center_y
|
|
y2 = self.last_texture_change_center_y
|
|
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
texture_list = []
|
|
|
|
change_direction = False
|
|
if self.change_x > 0 \
|
|
and self.change_y == 0 \
|
|
and self.state != FACE_RIGHT \
|
|
and self.walk_right_textures \
|
|
and len(self.walk_right_textures) > 0:
|
|
self.state = FACE_RIGHT
|
|
change_direction = True
|
|
elif self.change_x < 0 and self.change_y == 0 and self.state != FACE_LEFT \
|
|
and self.walk_left_textures and len(self.walk_left_textures) > 0:
|
|
self.state = FACE_LEFT
|
|
change_direction = True
|
|
elif self.change_y < 0 and self.change_x == 0 and self.state != FACE_DOWN \
|
|
and self.walk_down_textures and len(self.walk_down_textures) > 0:
|
|
self.state = FACE_DOWN
|
|
change_direction = True
|
|
elif self.change_y > 0 and self.change_x == 0 and self.state != FACE_UP \
|
|
and self.walk_up_textures and len(self.walk_up_textures) > 0:
|
|
self.state = FACE_UP
|
|
change_direction = True
|
|
|
|
if self.change_x == 0 and self.change_y == 0:
|
|
if self.state == FACE_LEFT:
|
|
self.texture = self.stand_left_textures[0]
|
|
elif self.state == FACE_RIGHT:
|
|
self.texture = self.stand_right_textures[0]
|
|
elif self.state == FACE_UP:
|
|
self.texture = self.walk_up_textures[0]
|
|
elif self.state == FACE_DOWN:
|
|
self.texture = self.walk_down_textures[0]
|
|
|
|
elif change_direction or distance >= self.texture_change_distance:
|
|
self.last_texture_change_center_x = self.center_x
|
|
self.last_texture_change_center_y = self.center_y
|
|
|
|
if self.state == FACE_LEFT:
|
|
texture_list = self.walk_left_textures
|
|
if texture_list is None or len(texture_list) == 0:
|
|
raise RuntimeError("update_animation was called on a sprite that doesn't have a "
|
|
"list of walk left textures.")
|
|
elif self.state == FACE_RIGHT:
|
|
texture_list = self.walk_right_textures
|
|
if texture_list is None or len(texture_list) == 0:
|
|
raise RuntimeError("update_animation was called on a sprite that doesn't have a list of "
|
|
"walk right textures.")
|
|
elif self.state == FACE_UP:
|
|
texture_list = self.walk_up_textures
|
|
if texture_list is None or len(texture_list) == 0:
|
|
raise RuntimeError("update_animation was called on a sprite that doesn't have a list of "
|
|
"walk up textures.")
|
|
elif self.state == FACE_DOWN:
|
|
texture_list = self.walk_down_textures
|
|
if texture_list is None or len(texture_list) == 0:
|
|
raise RuntimeError(
|
|
"update_animation was called on a sprite that doesn't have a list of walk down textures.")
|
|
|
|
self.cur_texture_index += 1
|
|
if self.cur_texture_index >= len(texture_list):
|
|
self.cur_texture_index = 0
|
|
|
|
self.texture = texture_list[self.cur_texture_index]
|
|
|
|
if self._texture is None:
|
|
print("Error, no texture set")
|
|
else:
|
|
self.width = self._texture.width * self.scale
|
|
self.height = self._texture.height * self.scale
|
|
|
|
|
|
def get_distance_between_sprites(sprite1: Sprite, sprite2: Sprite) -> float:
|
|
"""
|
|
Returns the distance between the center of two given sprites
|
|
:param Sprite sprite1: Sprite one
|
|
:param Sprite sprite2: Sprite two
|
|
:return: Distance
|
|
:rtype: float
|
|
"""
|
|
distance = math.sqrt((sprite1.center_x - sprite2.center_x) ** 2 + (sprite1.center_y - sprite2.center_y) ** 2)
|
|
return distance
|