Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprite.frect is now supported as an alias of Sprite.rect #3033

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion buildconfig/stubs/pygame/sprite.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class _SupportsSprite(Protocol):
@rect.setter
def rect(self, value: Optional[Union[FRect, Rect]]) -> None: ...
@property
def frect(self) -> Optional[Union[FRect, Rect]]: ...
@frect.setter
def frect(self, value: Optional[Union[FRect, Rect]]) -> None: ...
@property
def layer(self) -> int: ...
@layer.setter
def layer(self, value: int) -> None: ...
Expand Down Expand Up @@ -181,7 +185,7 @@ class Group(AbstractGroup[_TSprite]):
def __init__(
self, *sprites: Union[_TSprite, AbstractGroup[_TSprite], Iterable[_TSprite]]
) -> None: ...

# these are aliased in the code too
@deprecated("Use `pygame.sprite.Group` instead")
class RenderPlain(Group): ...
Expand Down
31 changes: 16 additions & 15 deletions docs/reST/ref/sprite.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ specially customized Sprite instances they contain.

The basic Group class can draw the Sprites it contains to a Surface. The
``Group.draw()`` method requires that each Sprite have a ``Sprite.image``
attribute and a ``Sprite.rect``. The ``Group.clear()`` method requires these
attribute and a ``Sprite.rect`` or ``Sprite.frect``. Note that ``Sprite.frect``
is just an alias for ``Sprite.rect``. The ``Group.clear()`` method requires these
same attributes, and can be used to erase all the Sprites with background.
There are also more advanced Groups: ``pygame.sprite.RenderUpdates()``.

Lastly, this module contains several collision functions. These help find
sprites inside multiple groups that have intersecting bounding rectangles. To
find the collisions, the Sprites are required to have a ``Sprite.rect``
attribute assigned.
find the collisions, the Sprites are required to have a ``Sprite.rect`` or
``Sprite.frect`` attribute assigned.

The groups are designed for high efficiency in removing and adding Sprites to
them. They also allow cheap testing to see if a Sprite already exists in a
Expand Down Expand Up @@ -64,24 +65,24 @@ Sprites are not thread safe. So lock them yourself if using threads.
adding the Sprite to Groups. For example:

.. code-block:: python

class Block(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,

# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
pygame.sprite.Sprite.__init__(self)

# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)

# Fetch the rectangle object that has the dimensions of the image
# Update the position of this object by setting the values of rect.x and rect.y
self.rect = self.image.get_rect()
self.rect = self.image.get_rect()

.. method:: update

| :sl:`method to control sprite behavior`
Expand Down Expand Up @@ -667,17 +668,17 @@ Sprites are not thread safe. So lock them yourself if using threads.
collide_circle_ratio, collide_mask

Example:

.. code-block:: python

# See if the Sprite block has collided with anything in the Group block_list
# The True flag will remove the sprite in block_list
blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)

# Check the list of colliding sprites, and add one to the score for each one
for block in blocks_hit_list:
score +=1

.. ## pygame.sprite.spritecollide ##

.. function:: collide_rect
Expand Down
27 changes: 22 additions & 5 deletions src_py/sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
# specialized cases.

from warnings import warn
from typing import Optional
from typing import Optional, Union

import pygame

Expand All @@ -101,8 +101,9 @@ class Sprite:

The base class for visible game objects. Derived classes will want to
override the Sprite.update() method and assign Sprite.image and Sprite.rect
attributes. The initializer can accept any number of Group instances that
the Sprite will become a member of.
or Sprite.frect attributes. Sprite.frect is just an alias for Sprite.rect.
The initializer can accept any number of
Group instances that the Sprite will become a member of.

When subclassing the Sprite class, be sure to call the base initializer
before adding the Sprite to Groups.
Expand All @@ -112,7 +113,7 @@ class Sprite:
def __init__(self, *groups):
self.__g = {} # The groups the sprite is in
self.__image: Optional[pygame.surface.Surface] = None
self.__rect: Optional[pygame.rect.Rect] = None
self.__rect: Optional[Union[pygame.rect.Rect, pygame.rect.FRect]] = None
if groups:
self.add(*groups)

Expand All @@ -129,7 +130,15 @@ def rect(self):
return self.__rect

@rect.setter
def rect(self, value: Optional[pygame.rect.Rect]):
def rect(self, value: Optional[Union[pygame.rect.Rect, pygame.rect.FRect]]):
self.__rect = value

@property
def frect(self):
return self.__rect

@frect.setter
def frect(self, value: Optional[Union[pygame.rect.Rect, pygame.rect.FRect]]):
self.__rect = value

def add(self, *groups):
Expand Down Expand Up @@ -562,6 +571,14 @@ def draw(self, surface):

"""
sprites = self.sprites()
for spr in sprites:
if not hasattr(spr, "rect"):
raise AttributeError(
"Sprite must have a rect attribute of type Rect or FRect"
)
Comment on lines +575 to +578
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spr is very likely (almost certain) to have this attribute since it's a property of Sprite. If it hasn't been set by the user, it will fail below on the isinstance check because it defaults to None.

if not isinstance(spr.rect, (pygame.rect.FRect, pygame.rect.Rect)):
raise TypeError("Sprite.rect must be either Rect or FRect")

Comment on lines +574 to +581
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds an insane performance overhead, it's two (expensive) calls per sprite before iterating over all the sprites again in the blit(s) calls below. Also interestingly it doesn't check for .frect which this PR aims to implement.

if hasattr(surface, "blits"):
self.spritedict.update(
zip(sprites, surface.blits((spr.image, spr.rect) for spr in sprites))
Expand Down
27 changes: 27 additions & 0 deletions test/sprite_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,18 @@ def test_draw(self):
self.assertEqual(self.ag.spritedict[self.s1], pygame.Rect(0, 0, 10, 10))
self.assertEqual(self.ag.spritedict[self.s2], pygame.Rect(10, 0, 10, 10))

test_sprite = sprite.Sprite()
test_sprite.image = pygame.Surface((10, 10))

# test_sprite.rect should be None here
self.assertRaises(TypeError, lambda: sprite.Group(test_sprite).draw(self.scr))

# this should be fine
group = sprite.Group(test_sprite)
test_sprite.frect = test_sprite.image.get_frect()
group.draw(self.scr)
self.assertEqual(group.spritedict[test_sprite], pygame.FRect(0, 0, 10, 10))

def test_empty(self):
self.ag.empty()
self.assertFalse(self.s1 in self.ag)
Expand Down Expand Up @@ -1342,6 +1354,21 @@ class SpriteTypeTest(SpriteBase, unittest.TestCase):
sprite.RenderUpdates,
]

def test_rect(self):
"""Tests the setter and getter for Sprite.(f)rect"""
spr = sprite.Sprite()
self.assertIsNone(spr.rect)

spr.rect = pygame.Rect(1, 2, 3, 4)
self.assertEqual(spr.rect, pygame.Rect(1, 2, 3, 4))
self.assertIsInstance(spr.rect, pygame.Rect)

spr.frect = pygame.FRect(2, 3, 4, 5)
self.assertEqual(spr.rect, pygame.FRect(2, 3, 4, 5))
self.assertIsInstance(spr.rect, pygame.FRect)

self.assertIs(spr.frect, spr.rect)


class DirtySpriteTypeTest(SpriteBase, unittest.TestCase):
Sprite = sprite.DirtySprite
Expand Down
Loading