Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame overlapping Sprites (draw order) based on location

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:
enter image description here enter image description here

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:

enter image description here

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

enter image description here

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.

like image 360
Alex Foster Avatar asked Oct 28 '25 04:10

Alex Foster


1 Answers

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()

enter image description here

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.

like image 104
sloth Avatar answered Oct 29 '25 19:10

sloth



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!