I'm still relatively new to Pygame and Python in general, so hopefully this isn't too out there.
I'm making a top-down RPG, and I have two Sprite objects with images that look (for example) like these:

that use rects that do not represent the entirety of the image:
class NPC(pygame.sprite.Sprite):
def __init__(self, image, pos, *groups):
super().__init__(*groups)
self.image = pygame.load(image)
self.rect = self.image.get_rect()
self.collisionRect = pygame.Rect(self.rect.left, self.rect.top + 12, self.image.get_width(), self.image.get_width())
#12 is the number of pixels that will overlap in the y-dimension
I'm doing this because I want the top few pixels of the NPC to overlap with other sprites. The collisionRect in each object is used over the rect whenever I detect a collision, so that I can create this effect.
However, I need a way to redraw them within my update() function so that one draws over the other based on their relative locations to each other.
So, when one NPC is above the other it looks like this:

But, when it's the other way around, it should look like this:

Which means that the images need to be drawn in a different order depending on which sprite is 'below' the other.
I've thought about possibly cutting the sprites into separate sprites and just have the 'head' sprites draw last, but I was wondering if there was a simpler (or at least a reliable) way to detect whether a sprite should be drawn last or not, based on whether or not it is both overlapping another sprite and immediately below it in the y-dimension.
I apologize if this question is too broad or needs more context; can provide those if needed.
As Kingsley already said in a comment, sorting your sprites by their y coordinate is a common way to do this.
Here's a full, running example (I named your images guy.png and gal.png). Since you already use sprites, I used a simple pygame.sprite.Group-subclass:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.pos = pygame.Vector2(pos)
self.rect = self.image.get_rect(center=self.pos)
def update(self, events, dt):
pass
class Player(Actor):
def __init__(self, image, pos):
super().__init__(image, pos)
def update(self, events, dt):
pressed = pygame.key.get_pressed()
move = pygame.Vector2((0, 0))
if pressed[pygame.K_w]: move += (0, -1)
if pressed[pygame.K_a]: move += (-1, 0)
if pressed[pygame.K_s]: move += (0, 1)
if pressed[pygame.K_d]: move += (1, 0)
if move.length() > 0: move.normalize_ip()
self.pos += move*(dt/5)
self.rect.center = self.pos
class YAwareGroup(pygame.sprite.Group):
def by_y(self, spr):
return spr.pos.y
def draw(self, surface):
sprites = self.sprites()
surface_blit = surface.blit
for spr in sorted(sprites, key=self.by_y):
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
self.lostsprites = []
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
dt = 0
sprites = YAwareGroup(Player(pygame.image.load('guy.png').convert_alpha(), (100, 200)),
Actor(pygame.image.load('gal.png').convert_alpha(), (200, 200)))
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()

If you need custom drawing logic, it's usually not the worst idea to subclass pygame's Group classes. You can find their source here to see how they work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With