diff --git a/arcade/__init__.py b/arcade/__init__.py index 6793ca180..2d76250c4 100644 --- a/arcade/__init__.py +++ b/arcade/__init__.py @@ -58,6 +58,17 @@ def configure_logging(level: Optional[int] = None): # noinspection PyPep8 import pyglet +# TODO: Remove ASAP after pyglet >= 2.1dev2 is out +if pyglet.version == "2.1.dev2": + # Temporary monkeypatch via deletion since dev2 still includes + # overly-specific __eq__ behavior. Later pyglet commits restore + # equality with same-valued tuples by deleting the __eq__ methods. + from pyglet import math as _pyglet_math + + del _pyglet_math.Vec2.__eq__ + del _pyglet_math.Vec3.__eq__ + del _pyglet_math.Vec4.__eq__ + # Env variable shortcut for headless mode if os.environ.get("ARCADE_HEADLESS"): pyglet.options["headless"] = True diff --git a/arcade/examples/particle_fireworks.py b/arcade/examples/particle_fireworks.py index 6ae4bda3c..5b6c05938 100644 --- a/arcade/examples/particle_fireworks.py +++ b/arcade/examples/particle_fireworks.py @@ -360,7 +360,7 @@ def firework_spark_mutator(particle: FadeParticle): def rocket_smoke_mutator(particle: LifetimeParticle): - particle.scale = lerp(0.5, 3.0, particle.lifetime_elapsed / particle.lifetime_original) + particle.scale = lerp(0.5, 3.0, particle.lifetime_elapsed / particle.lifetime_original) # type: ignore def main(): diff --git a/arcade/examples/sprite_health.py b/arcade/examples/sprite_health.py index 173b78abd..24689ad1d 100644 --- a/arcade/examples/sprite_health.py +++ b/arcade/examples/sprite_health.py @@ -52,7 +52,7 @@ def __init__(self, bar_list: arcade.SpriteList) -> None: scale=SPRITE_SCALING_PLAYER, ) self.indicator_bar: IndicatorBar = IndicatorBar( - self, bar_list, (self.center_x, self.center_y), scale=1.5, + self, bar_list, (self.center_x, self.center_y), scale=(1.5, 1.5), ) self.health: int = PLAYER_HEALTH @@ -98,7 +98,7 @@ def __init__( width: int = 100, height: int = 4, border_size: int = 4, - scale: float = 1.0, + scale: Tuple[float, float] = (1.0, 1.0), ) -> None: # Store the reference to the owner and the sprite list self.owner: Player = owner @@ -110,7 +110,7 @@ def __init__( self._center_x: float = 0.0 self._center_y: float = 0.0 self._fullness: float = 0.0 - self._scale: float = 1.0 + self._scale: Tuple[float, float] = (1.0, 1.0) # Create the boxes needed to represent the indicator bar self._background_box: arcade.SpriteSolidColor = arcade.SpriteSolidColor( @@ -206,8 +206,8 @@ def fullness(self, new_fullness: float) -> None: else: # Set the full_box to be visible incase it wasn't then update the bar self.full_box.visible = True - self.full_box.width = self._bar_width * new_fullness * self.scale - self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale + self.full_box.width = self._bar_width * new_fullness * self.scale[0] + self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale[0] @property def position(self) -> Tuple[float, float]: @@ -224,15 +224,15 @@ def position(self, new_position: Tuple[float, float]) -> None: self.full_box.position = new_position # Make sure full_box is to the left of the bar instead of the middle - self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale + self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale[0] @property - def scale(self) -> float: + def scale(self) -> Tuple[float, float]: """Returns the scale of the bar.""" return self._scale @scale.setter - def scale(self, value: float) -> None: + def scale(self, value: Tuple[float, float]) -> None: """Sets the new scale of the bar.""" # Check if the scale has changed. If so, change the bar's scale if value != self.scale: diff --git a/arcade/hitbox/base.py b/arcade/hitbox/base.py index afd098bbb..7ae0163a1 100644 --- a/arcade/hitbox/base.py +++ b/arcade/hitbox/base.py @@ -103,7 +103,7 @@ def __init__( self, points: Point2List, position: Point2 = (0.0, 0.0), - scale: tuple[float, float] = (1.0, 1.0), + scale: Point2 = (1.0, 1.0), ): self._points = points self._position = position @@ -249,7 +249,7 @@ def __init__( *, position: tuple[float, float] = (0.0, 0.0), angle: float = 0.0, - scale: tuple[float, float] = (1.0, 1.0), + scale: Point2 = (1.0, 1.0), ): super().__init__(points, position=position, scale=scale) self._angle: float = angle diff --git a/arcade/pymunk_physics_engine.py b/arcade/pymunk_physics_engine.py index 320543e94..8ee35b331 100644 --- a/arcade/pymunk_physics_engine.py +++ b/arcade/pymunk_physics_engine.py @@ -189,7 +189,7 @@ def velocity_callback( # Set the physics shape to the sprite's hitbox poly = sprite.hit_box.points - scaled_poly = [[x * sprite.scale for x in z] for z in poly] + scaled_poly = [[x * sprite.scale_x for x in z] for z in poly] shape = pymunk.Poly(body, scaled_poly, radius=radius) # type: ignore # Set collision type, used in collision callbacks diff --git a/arcade/sprite/animated.py b/arcade/sprite/animated.py index 55970f1e8..3d6757795 100644 --- a/arcade/sprite/animated.py +++ b/arcade/sprite/animated.py @@ -335,5 +335,5 @@ def update_animation(self, delta_time: float = 1 / 60) -> None: 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 + self.width = self._texture.width * self.scale_x + self.height = self._texture.height * self.scale_x diff --git a/arcade/sprite/base.py b/arcade/sprite/base.py index d9f1f041f..88e804696 100644 --- a/arcade/sprite/base.py +++ b/arcade/sprite/base.py @@ -3,11 +3,11 @@ from typing import TYPE_CHECKING, Iterable, TypeVar, Any import arcade -from arcade.types import Point, Point2, Color, RGBA255, RGBOrA255, PointList, Rect, LRBT +from arcade.types import AsFloat, Point, Color, Point2, RGBA255, RGBOrA255, PointList, Rect, LRBT from arcade.color import BLACK, WHITE from arcade.hitbox import HitBox from arcade.texture import Texture -from arcade.utils import copy_dunders_unimplemented +from arcade.utils import copy_dunders_unimplemented, ReplacementWarning, warning from pyglet.math import Vec2 @@ -60,9 +60,10 @@ def __init__( self._position = (center_x, center_y) self._depth = 0.0 self._texture = texture - self._width = texture.width * scale - self._height = texture.height * scale - self._scale = Vec2(scale, scale) + width, height = texture.size + self._width = width * scale + self._height = height * scale + self._scale = (scale, scale) self._visible = bool(visible) self._color: Color = WHITE self.sprite_lists: list["SpriteList"] = [] @@ -180,60 +181,158 @@ def size(self) -> Point: return self._width, self._height @size.setter - def size(self, new_value: Point): - if new_value[0] != self._width or new_value[1] != self._height: - self._scale = new_value[0] / self._texture.width, new_value[1] / self._texture.height - self._width = new_value[0] - self._height = new_value[1] + def size(self, new_value: Point2): + try: + width, height = new_value + except ValueError: + raise ValueError( + "size must be a tuple-like object which unpacks to exactly 2 coordinates" + ) + except TypeError: + raise TypeError( + "size must be a tuple-like object which unpacks to exactly 2 coordinates" + ) + + if width != self._width or height != self._height: + texture_width, texture_height = self._texture.size + self._scale = width / texture_width, height / texture_height + self._width = width + self._height = height self.update_spatial_hash() + for sprite_list in self.sprite_lists: sprite_list._update_size(self) @property - def scale(self) -> float: + def scale_x(self) -> float: """ - Get or set the sprite's x scale value or set both x & y scale to the same value. + Get or set the sprite's x scale value. .. note:: Negative values are supported. They will flip & mirror the sprite. """ return self._scale[0] - @scale.setter - def scale(self, new_value: float): - if new_value == self._scale[0] and new_value == self._scale[1]: + @scale_x.setter + def scale_x(self, new_scale_x: AsFloat): + old_scale_x, old_scale_y = self._scale + if new_scale_x == old_scale_x: return - self._scale = Vec2(new_value, new_value) - self._hit_box.scale = self._scale - if self._texture: - self._width = self._texture.width * self._scale[0] - self._height = self._texture.height * self._scale[1] + new_scale = (new_scale_x, old_scale_y) + + # Apply scale to hitbox first to raise any exceptions quickly + self._hit_box.scale = new_scale + self._scale = new_scale + self._width = self._texture.width * new_scale_x self.update_spatial_hash() for sprite_list in self.sprite_lists: sprite_list._update_size(self) @property - def scale_xy(self) -> Point2: - """Get or set the x & y scale of the sprite as a pair of values.""" - return self._scale + def scale_y(self) -> float: + """ + Get or set the sprite's y scale value. - @scale_xy.setter - def scale_xy(self, new_value: Point2): - if new_value[0] == self._scale[0] and new_value[1] == self._scale[1]: + .. note:: Negative values are supported. They will flip & + mirror the sprite. + """ + return self._scale[1] + + @scale_y.setter + def scale_y(self, new_scale_y: AsFloat): + old_scale_x, old_scale_y = self._scale + if new_scale_y == old_scale_y: return - x, y = new_value - self._scale = Vec2(x, y) - self._hit_box.scale = self._scale - if self._texture: - self._width = self._texture.width * self._scale[0] - self._height = self._texture.height * self._scale[1] + new_scale = (old_scale_x, new_scale_y) + + # Apply scale to hitbox first to raise any exceptions quickly + self._hit_box.scale = new_scale + self._scale = new_scale + self._height = self._texture.height * new_scale_y self.update_spatial_hash() + for sprite_list in self.sprite_lists: + sprite_list._update_size(self) + + @property + def scale(self) -> Vec2: + """Get or set the x & y scale of the sprite as a pair of values. + + You may set it to either a single value or a pair of values: + + .. list-table:: + :header-rows: 0 + + * - Single value + - ``sprite.scale = 2.0`` + + * - Tuple or :py:class:`~pyglet,math.Vec2` + - ``sprite.scale = (1.0, 3.0)`` + + The two-channel version is useful for making health bars and + other indicators. + + .. note:: Returns a :py:class:`pyglet.math.Vec2` for + compatibility. + + Arcade versions lower than 3,0 used one or both of the following + for scale: + + * A single :py:class:`float` on versions <= 2.6 + * A ``scale_xy`` property and exposing only the x component + on some intermediate dev releases + + Although scale is internally stored as a :py:class:`tuple`, we + return a :py:class:`pyglet.math.Vec2` to allow the in-place + operators to work in addition to setting values directly: + + * Old-style (``sprite.scale *= 2.0``) + * New-style (``sprite.scale *= 2.0, 2.0``) + + .. note:: Negative scale values are supported. + + This applies to both single-axis and dual-axis. + Negatives will flip & mirror the sprite, but the + with will use :py:func:`abs` to report total width + and height instead of negatives. + + """ + return Vec2(*self._scale) + + @scale.setter + def scale(self, new_scale: Point2 | AsFloat): + if isinstance(new_scale, (float, int)): + scale_x = new_scale + scale_y = new_scale + + else: # Treat it as some sort of iterable or sequence + # Don't abstract this. Keep it here since it's a hot code path + try: + scale_x, scale_y = new_scale # type / length implicit check + except ValueError: + raise ValueError( + "scale must be a tuple-like object which unpacks to exactly 2 coordinates" + ) + except TypeError: + raise TypeError( + "scale must be a tuple-like object which unpacks to exactly 2 coordinates" + ) + new_scale = scale_x, scale_y + if new_scale == self._scale: + return + + self._hit_box.scale = new_scale + tex_width, tex_height = self._texture.size + self._scale = new_scale + self._width = tex_width * scale_x + self._height = tex_height * scale_y + + self.update_spatial_hash() for sprite_list in self.sprite_lists: sprite_list._update_size(self) @@ -506,42 +605,76 @@ def update_animation(self, delta_time: float = 1 / 60) -> None: # --- Scale methods ----- - def rescale_relative_to_point(self, point: Point, factor: float) -> None: - """ - Rescale the sprite and its distance from the passed point. + def rescale_relative_to_point(self, point: Point2, scale_by: AsFloat | Point2) -> None: + """Rescale the sprite and its distance from the passed point. This function does two things: - 1. Multiply both values in the sprite's :py:attr:`~scale_xy` - value by ``factor``. + 1. Multiply both values in the sprite's :py:attr:`.scale` + value by the values in ``scale_by``: + + * If ``scale_by`` is an :py:class:`int` or :py:class:`float`, + use it for both the x and y axes + * If ``scale_by`` is a tuple-like object which unpacks to + two numbers, then use + * Otherwise, raise an exception + 2. Scale the distance between the sprite and ``point`` by ``factor``. - If ``point`` equals the sprite's :py:attr:`~position`, - the distance will be zero and the sprite will not move. + .. note:: If ``point`` equals the sprite's :py:attr:`.position` + the distance will be zero and the sprite won't move. + + Args: + point: + The point to scale relative to. + scale_by: + A multiplier for both the sprite scale and its distance + from the point. Note that although factor may be negative, + it may have unexpected effects. - :param point: The reference point for rescaling. - :param factor: Multiplier for sprite scale & distance to point. - :return: """ # abort if the multiplier wouldn't do anything - if factor == 1.0: - return + if isinstance(scale_by, (float, int)): + if scale_by == 1.0: + return + factor_x = scale_by + factor_y = scale_by + else: + try: + factor_x, factor_y = scale_by + if factor_x == 1.0 and factor_y == 1.0: + return + except ValueError: + raise ValueError( + "factor must be a float, int, or tuple-like which unpacks as two float-like values" + ) + except TypeError: + raise TypeError( + "factor must be a float, int, or tuple-like unpacks as two float-like values" + ) # set the scale and, if this sprite has a texture, the size data - self.scale_xy = self._scale[0] * factor, self._scale[1] * factor - if self._texture: - self._width = self._texture.width * self._scale[0] - self._height = self._texture.height * self._scale[1] + old_scale_x, old_scale_y = self._scale + new_scale_x = old_scale_x * factor_x + new_scale_y = old_scale_y * factor_y + self._scale = new_scale_x, new_scale_y - # detect the edge case where distance to multiply is zero - position_changed = point != self._position + tex_width, tex_height = self._texture.size + self._width = tex_width * new_scale_x + self._height = tex_height * new_scale_y + + # If the scaling point is the sprite's center, it doesn't move + old_position = self._position + position_changed = point != old_position # Stored to use below # be lazy about math; only do it if we have to if position_changed: + point_x, point_y = point + old_x, old_y = old_position self.position = ( - (self._position[0] - point[0]) * factor + point[0], - (self._position[1] - point[1]) * factor + point[1], + (old_x - point_x) * factor_x + point_x, + (old_y - point_y) * factor_y + point_y, ) # rebuild all spatial metadata @@ -551,9 +684,15 @@ def rescale_relative_to_point(self, point: Point, factor: float) -> None: if position_changed: sprite_list._update_position(self) + @warning(warning_type=ReplacementWarning, new_name="rescale_relative_to_point") def rescale_xy_relative_to_point(self, point: Point, factors_xy: Iterable[float]) -> None: - """ - Rescale the sprite and its distance from the passed point. + """Rescale the sprite and its distance from the passed point. + + .. deprecated:: 3.0 + Use :py:meth:`.rescale_relative_to_point` instead. + + This was added during the 3.0 development cycle before scale was + made into a vector quantitity. This method can scale by different amounts on each axis. To scale along only one axis, set the other axis to ``1.0`` in @@ -576,33 +715,7 @@ def rescale_xy_relative_to_point(self, point: Point, factors_xy: Iterable[float] ``point``. :return: """ - # exit early if nothing would change - factor_x, factor_y = factors_xy - if factor_x == 1.0 and factor_y == 1.0: - return - - # set the scale and, if this sprite has a texture, the size data - self.scale_xy = Vec2(self._scale[0] * factor_x, self._scale[1] * factor_y) - if self._texture: - self._width = self._texture.width * self._scale[0] - self._height = self._texture.height * self._scale[1] - - # detect the edge case where the distance to multiply is 0 - position_changed = point != self._position - - # be lazy about math; only do it if we have to - if position_changed: - self.position = ( - (self._position[0] - point[0]) * factor_x + point[0], - (self._position[1] - point[1]) * factor_y + point[1], - ) - - # rebuild all spatial metadata - self.update_spatial_hash() - for sprite_list in self.sprite_lists: - sprite_list._update_size(self) - if position_changed: - sprite_list._update_position(self) + self.rescale_relative_to_point(point, factors_xy) # type: ignore # ---- Utility Methods ---- diff --git a/arcade/tilemap/tilemap.py b/arcade/tilemap/tilemap.py index b19fe6372..e435eb27e 100644 --- a/arcade/tilemap/tilemap.py +++ b/arcade/tilemap/tilemap.py @@ -575,7 +575,7 @@ def _create_sprite_from_tile( cast(list[Point2], points), position=my_sprite.position, angle=my_sprite.angle, - scale=my_sprite.scale_xy, + scale=my_sprite.scale, ) if tile.animation: diff --git a/tests/unit/sprite/test_sprite.py b/tests/unit/sprite/test_sprite.py index 839d0b080..ae470699d 100644 --- a/tests/unit/sprite/test_sprite.py +++ b/tests/unit/sprite/test_sprite.py @@ -51,14 +51,22 @@ def test_set_size(): assert sprite.size == (96, 128) assert sprite.width == 96 assert sprite.height == 128 - assert sprite.scale == 1.0 + assert sprite.scale == (1.0, 1.0) # Reduce to half width and height sprite.size = 48, 64 assert sprite.size == (48, 64) assert sprite.width == 48 assert sprite.height == 64 - assert sprite.scale == 0.5 + assert sprite.scale == (0.5, 0.5) + + # Validate size data + with pytest.raises(ValueError): + sprite.size = (1,) + with pytest.raises(ValueError): + sprite.size = (0, 1, 2) + with pytest.raises(TypeError): + sprite.size = 1 @pytest.mark.parametrize('not_a_texture', [ @@ -123,44 +131,46 @@ def test_sprite_scale_xy(window): # setting vector equivalent of previous scale doesn't change values sprite.scale = 1.0 - sprite.scale_xy = (1.0, 1.0) - assert sprite.scale == 1.0 - assert sprite.scale_xy == Vec2(1.0, 1.0) + sprite.scale = (1.0, 1.0) + assert sprite.scale == (1.0, 1.0) assert sprite.width, sprite.height == (20, 20) # setting scale_xy to identical values in each channel works - sprite.scale_xy = 2.0, 2.0 - assert sprite.scale == 2.0 - assert sprite.scale_xy == Vec2(2.0, 2.0) + sprite.scale = 2.0, 2.0 + assert sprite.scale == (2.0, 2.0) assert sprite.width, sprite.height == (40, 40) # setting scale_xy with x < y scale works correctly - sprite.scale_xy = 1.0, 4.0 - assert sprite.scale_xy == Vec2(1.0, 4.0) - assert sprite.scale == 1.0 + sprite.scale = 1.0, 4.0 + assert sprite.scale == (1.0, 4.0) + assert sprite.scale_x == 1.0 + assert sprite.scale_y == 4.0 assert sprite.width, sprite.height == (20, 80) # setting scale_xy with x > y scale works correctly - sprite.scale_xy = 5.0, 3.0 - assert sprite.scale_xy == Vec2(5.0, 3.0) - assert sprite.scale == 5.0 + sprite.scale = 5.0, 3.0 + assert sprite.scale == (5.0, 3.0) + assert sprite.scale_x == 5.0 + assert sprite.scale_y == 3.0 assert sprite.width, sprite.height == (100, 60) # edge case: setting scale_xy with x < 0 works correctly - sprite.scale_xy = (-1.0, 1.0) - assert sprite.scale == -1.0 + sprite.scale = -1.0, 1.0 + assert sprite.scale == (-1.0, 1.0) assert sprite.width == -20 assert sprite.height == 20 # edge case: setting scale_xy with y < 0 works correctly - sprite.scale_xy = (1.0, -1.0) - assert sprite.scale == 1.0 + sprite.scale = (1.0, -1.0) + assert sprite.scale_x == 1.0 + assert sprite.scale_y == -1.0 assert sprite.width == 20 assert sprite.height == -20 # edge case: setting scale_xy with x < 0, y < 0 works correctly - sprite.scale_xy = (-1.0, -1.0) - assert sprite.scale == -1.0 + sprite.scale = (-1.0, -1.0) + assert sprite.scale_x == -1.0 + assert sprite.scale_y == -1.0 assert sprite.width == -20 assert sprite.width == -20 @@ -169,55 +179,54 @@ def test_sprite_scale_resets_mismatched_xy_settings(window): sprite = arcade.SpriteSolidColor(20, 20, color=arcade.color.WHITE) # check if x dimension is properly reset - sprite.scale_xy = 3.0, 2.0 + sprite.scale = 3.0, 2.0 sprite.scale = 2.0 - assert sprite.scale == 2.0 - assert sprite.scale_xy == Vec2(2.0, 2.0) + assert sprite.scale == (2.0, 2.0) assert sprite.width == 40 assert sprite.height == 40 # check if y dimension is properly reset - sprite.scale_xy = 5.0, 3.0 + sprite.scale = 5.0, 3.0 sprite.scale = 5.0 - assert sprite.scale == 5.0 - assert sprite.scale_xy == Vec2(5.0, 5.0) + assert sprite.scale_x == 5.0 + assert sprite.scale == (5.0, 5.0) assert sprite.width == 100 assert sprite.height == 100 # check if both dimensions properly reset - sprite.scale_xy = 0.5, 4.0 + sprite.scale = 0.5, 4.0 sprite.scale = 1.0 - assert sprite.scale == 1.0 - assert sprite.scale_xy == Vec2(1.0, 1.0) + assert sprite.scale_x == 1.0 + assert sprite.scale == (1.0, 1.0) assert sprite.width == 20 assert sprite.height == 20 # edge case: setting negative values works - sprite.scale_xy = 0.5, 4.0 + sprite.scale = 0.5, 4.0 sprite.scale = -1.0 - assert sprite.scale == -1.0 - assert sprite.scale_xy == Vec2(-1.0, -1.0) + assert sprite.scale_x == -1.0 + assert sprite.scale == (-1.0, -1.0) assert sprite.width == -20 assert sprite.height == -20 # edge case: x scale < 0 is reset to positive - sprite.scale_xy = -1.0, 1.0 + sprite.scale = -1.0, 1.0 sprite.scale = 2.0 - assert sprite.scale_xy == Vec2(2.0, 2.0) + assert sprite.scale == (2.0, 2.0) assert sprite.width == 40 assert sprite.height == 40 # edge case: y scale < 0 is reset to positive - sprite.scale_xy = 1.0, -1.0 + sprite.scale = 1.0, -1.0 sprite.scale = 2.0 - assert sprite.scale_xy == Vec2(2.0, 2.0) + assert sprite.scale == (2.0, 2.0) assert sprite.width == 40 assert sprite.height == 40 # edge case: x < 0, y < 0 is reset to positive - sprite.scale_xy = -1.0, -1.0 + sprite.scale = -1.0, -1.0 sprite.scale = 2.0 - assert sprite.scale_xy == Vec2(2.0, 2.0) + assert sprite.scale == (2.0, 2.0) assert sprite.width == 40 assert sprite.height == 40 @@ -271,7 +280,7 @@ def sprite_64x64_at_position(x, y): window_center_y - 50 ) sprite_1.rescale_relative_to_point((0, 0), 3.31) - assert sprite_1.scale == 3.31 + assert sprite_1.scale == (3.31, 3.31) assert sprite_1.center_x == (window_center_x + 50) * 3.31 assert sprite_1.center_y == (window_center_y - 50) * 3.31 assert sprite_1.width == 64 * 3.31 @@ -288,10 +297,9 @@ def sprite_64x64_at_position(x, y): window_center_x + 10, window_center_y + 10 ) - sprite_2.scale_xy = 2.0, 1.0 + sprite_2.scale = (2.0, 1.0) sprite_2.rescale_relative_to_point(window_center, 2.0) - assert sprite_2.scale == 4.0 - assert sprite_2.scale_xy == Vec2(4.0, 2.0) + assert sprite_2.scale == (4.0, 2.0) assert sprite_2.center_x == window_center_x + 20 assert sprite_2.center_y == window_center_y + 20 assert sprite_2.width == 256 @@ -308,9 +316,9 @@ def sprite_64x64_at_position(x, y): window_center_x - 10, window_center_y - 10 ) - sprite_3.scale_xy = 0.5, 1.5 + sprite_3.scale = (0.5, 1.5) sprite_3.rescale_relative_to_point(window_center, 3.0) - assert sprite_3.scale_xy == Vec2(1.5, 4.5) + assert sprite_3.scale == (1.5, 4.5) assert sprite_3.center_x == window_center_x - 30 assert sprite_3.center_y == window_center_y - 30 assert sprite_3.width == 96 @@ -320,8 +328,7 @@ def sprite_64x64_at_position(x, y): # expected: sprite does not move, but scale and dimensions change sprite_4 = sprite_64x64_at_position(*window_center) sprite_4.rescale_relative_to_point(sprite_4.position, 2.0) - assert sprite_4.scale == 2.0 - assert sprite_4.scale_xy == Vec2(2.0, 2.0) + assert sprite_4.scale == (2.0, 2.0) assert sprite_4.center_x == window_center_x assert sprite_4.center_y == window_center_y assert sprite_4.width == 128 @@ -331,8 +338,7 @@ def sprite_64x64_at_position(x, y): # expected : sprite doesn't move, but scale data doubled & negative sprite_5 = sprite_64x64_at_position(*window_center) sprite_5.rescale_relative_to_point(sprite_5.position, -2.0) - assert sprite_5.scale == -2.0 - assert sprite_5.scale_xy == Vec2(-2.0, -2.0) + assert sprite_5.scale == (-2.0, -2.0) assert sprite_5.center_x == window_center_x assert sprite_5.center_y == window_center_y assert sprite_5.width == -128 @@ -345,7 +351,7 @@ def sprite_64x64_at_position(x, y): window_center_y + 81 ) sprite_6.rescale_relative_to_point((50, 40), 1.0) - assert sprite_6.scale == 1.0 + assert sprite_6.scale == (1.0, 1.0) assert sprite_6.center_x == window_center_x - 81 assert sprite_6.center_y == window_center_y + 81 assert sprite_6.width == 64 @@ -358,7 +364,7 @@ def sprite_64x64_at_position(x, y): window_center_y + 81 ) sprite_7.rescale_relative_to_point((50, 40), 1.0) - assert sprite_7.scale == 1.0 + assert sprite_7.scale == (1.0, 1.0) assert sprite_7.center_x == window_center_x - 81 assert sprite_7.center_y == window_center_y + 81 assert sprite_7.width == 64 @@ -373,14 +379,14 @@ def sprite_64x64_at_position(x, y): window_center_y + 81 ) sprite_8.rescale_relative_to_point(window_center, -1.0) - assert sprite_8.scale == -1.0 + assert sprite_8.scale == (-1.0, -1.0) assert sprite_8.center_x == window_center_x + 81 assert sprite_8.center_y == window_center_y - 81 assert sprite_8.width == -64 assert sprite_8.height == -64 -def test_rescale_xy_relative_to_point(window): +def test_rescale_relative_to_point_with_vec_quants(window): window_center = window.width // 2, window.height // 2 window_center_x, window_center_y = window_center @@ -395,9 +401,8 @@ def sprite_64x64_at_position(x, y): window_center_x + 50, window_center_y - 50 ) - sprite_1.rescale_xy_relative_to_point((0, 0), (3.31, 3.31)) - assert sprite_1.scale == 3.31 - assert sprite_1.scale_xy == Vec2(3.31, 3.31) + sprite_1.rescale_relative_to_point((0, 0), (3.31, 3.31)) + assert sprite_1.scale == (3.31, 3.31) assert sprite_1.center_x == (window_center_x + 50) * 3.31 assert sprite_1.center_y == (window_center_y - 50) * 3.31 assert sprite_1.width == 64 * 3.31 @@ -408,10 +413,9 @@ def sprite_64x64_at_position(x, y): window_center_x + 10, window_center_y + 10 ) - sprite_2.scale_xy = 2.0, 1.0 - sprite_2.rescale_xy_relative_to_point(window_center, (2.0, 2.0)) - assert sprite_2.scale == 4.0 - assert sprite_2.scale_xy == Vec2(4.0, 2.0) + sprite_2.scale = (2.0, 1.0) + sprite_2.rescale_relative_to_point(window_center, (2.0, 2.0)) + assert sprite_2.scale == (4.0, 2.0) assert sprite_2.center_x == window_center_x + 20 assert sprite_2.center_y == window_center_y + 20 assert sprite_2.width == 256 @@ -422,10 +426,9 @@ def sprite_64x64_at_position(x, y): window_center_x - 10, window_center_y - 10 ) - sprite_3.scale_xy = 0.5, 1.5 - sprite_3.rescale_xy_relative_to_point(window_center, (3.0, 3.0)) - assert sprite_3.scale == 1.5 - assert sprite_3.scale_xy == Vec2(1.5, 4.5) + sprite_3.scale = (0.5, 1.5) + sprite_3.rescale_relative_to_point(window_center, (3.0, 3.0)) + assert sprite_3.scale == (1.5, 4.5) assert sprite_3.center_x == window_center_x - 30 assert sprite_3.center_y == window_center_y - 30 assert sprite_3.width == 96 @@ -434,8 +437,8 @@ def sprite_64x64_at_position(x, y): # edge case: point == sprite center, factor > 1 # expected: sprite does not move, but scale and dimensions change sprite_4 = sprite_64x64_at_position(*window_center) - sprite_4.rescale_xy_relative_to_point(sprite_4.position, (2.0, 2.0)) - assert sprite_4.scale == 2.0 + sprite_4.rescale_relative_to_point(sprite_4.position, (2.0, 2.0)) + assert sprite_4.scale == (2.0, 2.0) assert sprite_4.center_x == window_center_x assert sprite_4.center_y == window_center_y assert sprite_4.width == 128 @@ -447,8 +450,8 @@ def sprite_64x64_at_position(x, y): window_center_x - 81, window_center_y + 81 ) - sprite_5.rescale_xy_relative_to_point((50, 40), (1.0, 1.0)) - assert sprite_5.scale == 1.0 + sprite_5.rescale_relative_to_point((50, 40), (1.0, 1.0)) + assert sprite_5.scale == (1.0, 1.0) assert sprite_5.center_x == window_center_x - 81 assert sprite_5.center_y == window_center_y + 81 assert sprite_5.width == 64 @@ -457,9 +460,8 @@ def sprite_64x64_at_position(x, y): # edge case: point == sprite center, negative factor # expected : sprite doesn't move, but scale, width, & height < 0 sprite_6 = sprite_64x64_at_position(*window_center) - sprite_6.rescale_xy_relative_to_point(sprite_6.position, (-2.0, -2.0)) - assert sprite_6.scale == -2.0 - assert sprite_6.scale_xy == Vec2(-2.0, -2.0) + sprite_6.rescale_relative_to_point(sprite_6.position, (-2.0, -2.0)) + assert sprite_6.scale == (-2.0, -2.0) assert sprite_6.center_x == window_center_x assert sprite_6.center_y == window_center_y assert sprite_6.width == -128 @@ -471,8 +473,8 @@ def sprite_64x64_at_position(x, y): window_center_x - 81, window_center_y + 81 ) - sprite_7.rescale_xy_relative_to_point((50, 40), (1.0, 1.0)) - assert sprite_7.scale == 1.0 + sprite_7.rescale_relative_to_point((50, 40), (1.0, 1.0)) + assert sprite_7.scale == (1.0, 1.0) assert sprite_7.center_x == window_center_x - 81 assert sprite_7.center_y == window_center_y + 81 assert sprite_7.width == 64 diff --git a/tests/unit/sprite/test_sprite_render.py b/tests/unit/sprite/test_sprite_render.py index 443248683..5594bf33d 100644 --- a/tests/unit/sprite/test_sprite_render.py +++ b/tests/unit/sprite/test_sprite_render.py @@ -202,20 +202,20 @@ def test_render_scaled(window): # ensure normal scaling works correctly gold_1 = arcade.Sprite(":resources:/images/items/gold_1.png") - assert gold_1.scale == 1.0 + assert gold_1.scale == (1.0, 1.0) assert gold_1.width, gold_1.height == (64, 64) gold_1.scale = 2.0 - assert gold_1.scale == 2.0 + assert gold_1.scale == (2.0, 2.0) assert gold_1.width, gold_1.height == (128, 128) gold_1.scale *= 0.25 - assert gold_1.scale == 0.5 + assert gold_1.scale == (0.5, 0.5) assert gold_1.width, gold_1.height == (32, 32) # edge case: negative scale values are supported gold_1.scale *= -1.0 - assert gold_1.scale == - 0.5 + assert gold_1.scale == (-0.5, -0.5) assert gold_1.width, gold_1.height == (-32, -32) # visual spot check