Search code examples
pythonpygamespritedrawrectangles

Pygame: strange behaviour of a drawn rectangle


I'm trying to make a lifebar class for my game in Pygame. I've done this:

class Lifebar():
    def __init__(self, x, y, max_health):
        self.x = x
        self.y = y
        self.health = max_health
        self.max_health = max_health

    def update(self, surface, add_health):
        if self.health > 0:
            self.health += add_health
            pygame.draw.rect(surface, (0, 255, 0), (self.x, self.y, 30 - 30 * (self.max_health - self.health) / self.max_health, 10))


    print(30 - 30 * (self.max_health - self.health) / self.max_health)

It works, but when I tried it to down its health to zero, the rectangle surpasses the left limit by a bit. Why does this happen?

Here you have a code to try it on your own (just run it if my explanation of the problem wasn't clear):

import pygame
from pygame.locals import *
import sys

WIDTH = 640
HEIGHT = 480

class Lifebar():
    def __init__(self, x, y, max_health):
        self.x = x
        self.y = y
        self.health = max_health
        self.max_health = max_health

    def update(self, surface, add_health):
        if self.health > 0:
            self.health += add_health
            pygame.draw.rect(surface, (0, 255, 0), (self.x, self.y, 30 - 30 * (self.max_health - self.health) / self.max_health, 10))
        print(30 - 30 * (self.max_health - self.health) / self.max_health)

def main():
    pygame.init()

    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Prueba")


    clock = pygame.time.Clock()

    lifebar = Lifebar(WIDTH // 2, HEIGHT // 2, 100)

    while True:
        clock.tick(15)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        screen.fill((0,0,255))

        lifebar.update(screen, -1)

        pygame.display.flip()

if __name__ == "__main__":
    main()  

Solution

  • I think it's because your code draws rectangles less than 1 pixel wide, and even though the pygame documentation says "The area covered by a Rect does not include the right- and bottom-most edge of pixels", apparently that implies that it always does include the left- and top-most edges, which is what is giving the results. This arguably could be considered a bug—and it shouldn't draw anything in those cases.

    Below is a workaround that simply avoids drawing Rects which are less than a whole pixel wide. I also simplified the math being done a bit to make things more clear (and faster).

        def update(self, surface, add_health):
            if self.health > 0:
                self.health += add_health
                width = 30 * self.health/self.max_health
                if width >= 1.0:
                    pygame.draw.rect(surface, (0, 255, 0), 
                                     (self.x, self.y, width, 10))
                    print(self.health, (self.x, self.y, width, 10))