Pygame images glitching + appearing in corner. May be RECT issue?

Viewed 42

Making a simple game that runs itself with ducks eating bread. It works fine but the images "glitch" or blit all over the screen, i have narrowed it down to: whenever a bread is "eaten" a new bread appears and blits all over the screen before settling down

Code in question:

            for BreadPos in breadxy:
                if (((Ducky.xpos - BreadPos[0]) + (Ducky.ypos - BreadPos[1]))) > -5:
                
                    del FoodList[breadxy.index(BreadPos)]

                    Ducky.hunger += 60
                    
                    FoodList.append(Food(random.randint(30,600),random.randint(30,600)))
                    #this line here is supposed to be indented properly but i am seemingly bad at this stack overflow thing. 



Here is my code in its entirety:

import pygame
import pygame, sys
from pygame.locals import *
import random

def main():
    pygame.init()

    (width,height) = (640,640)

    clock = pygame.time.Clock()

    DISPLAY = pygame.display.set_mode((width,height))

    blue= (0,60,200)

    DISPLAY.fill(blue)

    #pictures and spirtes

    DuckPic = pygame.image.load("Duck Sprite.jpg")
    DuckPic = pygame.transform.scale(DuckPic, (128, 72))

    BreadPic = pygame.image.load("breadpic.jpg")
    BreadPic = pygame.transform.scale(BreadPic, (128, 72))

    class Food(pygame.sprite.Sprite):
        def __init__(self, xpos,ypos):
            super().__init__()
            self.xpos = xpos
            self.ypos = ypos
            self.image = BreadPic
            self.rect = self.image.get_rect()

    FoodList = []

    for i in range (10):
        FoodList.append(Food(random.randint(30,600),random.randint(30,600)))
    
    class Duck(pygame.sprite.Sprite):
        def __init__(self,xpos,ypos,speed,hunger,movement):
            super().__init__()
            self.xpos = xpos
            self.ypos = ypos
            self.image = DuckPic
            self.rect = self.image.get_rect()
            self.speed = speed
            self.hunger = hunger
            self.movement = movement

    Ducks = []

    for i in range(1):
        Ducks.append(Duck(xpos = random.randint(0, 320), ypos = random.randint(0, 320), speed =(random.randint(1,3)), hunger = 1000, movement = random.randint(1,100)))


    ###------------ GAME STUFF INSTANCES HAPPENING

    while True:
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        if FoodList == []:
            print("ducks ate all the bread")
            pygame.quit()
            sys.exit()

        if Ducks == []:
            print("all ducks died")
            pygame.quit()
            sys.exit()
        

        #display screen then food then ducks
        DISPLAY.fill(blue)

        breadxy = []

        #gets xy pos of all food items 
        for i,FoodItem, in enumerate(FoodList):
            xyList = [FoodItem.xpos , FoodItem.ypos]
            breadxy.append(xyList)
        
        for i,Ducky in enumerate(Ducks):

            closestlist = [] #reset list of closest breads for all ducks

            for count,j in enumerate(breadxy):

                
               
                closestlist.append(((Ducky.xpos - j[0]) + (Ducky.ypos - j[1])))
                #closest list shows a value of how close a duck is to the bread 
                #where 0 or -1 = best score 

            indexofBread = closestlist.index(max(closestlist))
            
            print(f"I'm moving to bread {indexofBread} with value of {max(closestlist)} which is at {FoodList[indexofBread].xpos,FoodList[indexofBread].ypos}, my postion is {Ducky.xpos, Ducky.ypos} other values include = {closestlist}")

            if True:
            #move duck to closest bread
                if Ducky.xpos < FoodList[indexofBread].xpos:
                    Ducky.xpos += 1 * Ducky.speed
                    
                if Ducky.xpos > FoodList[indexofBread].xpos:
                    Ducky.xpos -= 1 * Ducky.speed

                if Ducky.ypos > FoodList[indexofBread].ypos:
                    Ducky.ypos -= 1 * Ducky.speed   

                if Ducky.ypos< FoodList[indexofBread].ypos:
                    Ducky.ypos += 1 * Ducky.speed            

            Ducky.hunger = Ducky.hunger - 1*Ducky.speed
            #duck hunger goes down, faster duck looses more hunger

            if Ducky.hunger < 0: #if duck at 0 hunger he dies :(
                del Ducks[i]


            #display the bread

            #check for collision thorugh seeing if score is good
            for BreadPos in breadxy:
                if (((Ducky.xpos - BreadPos[0]) + (Ducky.ypos - BreadPos[1]))) > -5:
                
                    del FoodList[breadxy.index(BreadPos)]

                    Ducky.hunger += 60
                    
                    FoodList.append(Food(random.randint(30,600),random.randint(30,600)))
                    #print(len(FoodList))
                        
            #print(Ducky.hunger)

            for FoodItem in (FoodList):
            
                DISPLAY.blit(FoodItem.image, (FoodItem.xpos, FoodItem.ypos))

            #DISPLAY DUCK

            DISPLAY.blit(Ducky.image, (Ducky.xpos, Ducky.ypos))
        
        pygame.display.update()
            
        #make the screen
        
    
    
main()
2 Answers

The issue is with how you defined the collision distance. From your formula (Ducky.xpos - BreadPos[0]) + (Ducky.ypos - BreadPos[1]) > -5, it basically means you want the bread on the upper and left portion of your duck deleted, and consequently all the bread gets crowded at the lower-right portion of the screen.

Because of that issue, a food is randomly generated and blit to screen in 1 frame, but then on the next frame that food is deleted because of your distance formula condition and then another food is randomly generated and blit to screen.

Below is a sample manhattan distance formula. You may also apply pythagorean/euclidian distance formula. Also, if you want to go deeper with pygame, there are built-in collision functions such as colliderect, collidepoint, etc.

for BreadPos in breadxy:
    distance = abs(Ducky.xpos - BreadPos[0]) + abs(Ducky.ypos - BreadPos[1])
    if distance < 5:
        del FoodList[breadxy.index(BreadPos)]
        Ducky.hunger += 60
        FoodList.append(Food(random.randint(30, 600), random.randint(30, 600)))

So it looks like the issue is the tracking of the FoodList index via the breadxy list.

The breadxy tracking is an index into FoodList, so the bread that was eaten can be removed from the list. But as soon as the breads are removed, the indexes into the list are no longer correct. The "eating" loop doesn't stop checking after an item is consumed, so theoretically this loop can be repeated multiple times. After the first del(), no index is valid. Sometimes it will crash because the index could be more than the new (shorted) length of the list.

I couldn't work out the distance algorithm!? So I substituted it with a member function on Duck that calculates the Euclidian Distance (straight line distance).

This code seems to work OK. I modified your sprites to use the rect item, rather than relying on xpos and ypos.

#! /usr/bin/env python3
import pygame
import pygame, sys
from pygame.locals import *
import math
import random

def main():
    pygame.init()

    (width,height) = (640,640)

    clock = pygame.time.Clock()

    DISPLAY = pygame.display.set_mode((width,height))

    blue= (0,60,200)

    DISPLAY.fill(blue)

    #pictures and spirtes

    DuckPic = pygame.image.load("Duck Sprite.png").convert_alpha()
    DuckPic = pygame.transform.smoothscale( DuckPic, (128, 72))

    BreadPic = pygame.image.load("breadpic.png").convert_alpha()
    BreadPic = pygame.transform.smoothscale(BreadPic, (128, 72))

    class Food(pygame.sprite.Sprite):
        def __init__(self, xpos,ypos):
            super().__init__()
            self.image = BreadPic
            self.rect = self.image.get_rect()
            self.rect.topleft = ( xpos, ypos )
            self.eaten = False

        def eat( self, ate=True ):
            self.eaten = ate

        def __str__( self ):
            value = "Food at (%d,%d)" % ( self.rect.x, self.rect.y )
            if ( self.eaten ):
                value += " (eaten)"
            return value
                

    FoodList = []
    for i in range (10):
        FoodList.append(Food(random.randint(30,600),random.randint(30,600)))
    
    class Duck(pygame.sprite.Sprite):
        def __init__(self,xpos,ypos,speed,hunger,movement):
            super().__init__()
            self.image = DuckPic
            self.rect = self.image.get_rect()
            self.rect.topleft = ( xpos, ypos )
            self.speed = speed
            self.hunger = hunger
            self.movement = movement

        def distanceTo( self, bread_sprite ):
            """ Calculate the distance from us to a FoodItem """
            duck_x, duck_y   = self.rect.center
            bread_x, bread_y = bread_sprite.rect.center
            # ref: https://en.wikipedia.org/wiki/Euclidean_distance
            x_part = ( bread_x - duck_x )
            y_part = ( bread_y - duck_y )
            distance = math.sqrt( ( x_part * x_part ) + ( y_part * y_part ) )
            return distance

        def __str__( self ):
            return "Duck at (%d,%d)" % ( self.rect.x, self.rect.y )


    Ducks = []

    for i in range(1):
        Ducks.append(Duck(xpos = random.randint(0, 320), ypos = random.randint(0, 320), speed =(random.randint(1,3)), hunger = 1000, movement = random.randint(1,100)))


    ###------------ GAME STUFF INSTANCES HAPPENING

    while True:
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        if FoodList == []:
            print("ducks ate all the bread")
            pygame.quit()
            sys.exit()

        if Ducks == []:
            print("all ducks died")
            pygame.quit()
            sys.exit()
        

        breadxy = []

        for i,Ducky in enumerate(Ducks):

            closestlist = [] #reset list of closest breads for all ducks
            for bread in FoodList:
                # Made a list of breads & their distance
                closestlist.append( [ Ducky.distanceTo( bread ), bread ] )

            closestlist.sort( key=lambda x : x[0] )  # sort by distance
            closest_bread_distance, closest_bread = closestlist[0]
            
            #print(f"I'm moving to bread {closest_bread} with at a distance of {closest_bread_distance}, my postion is {Ducky.rect.x, Ducky.rect.y} all values include = {closestlist}")

            if True:
            #move duck to closest bread
                if Ducky.rect.x < closest_bread.rect.x:
                    Ducky.rect.x += 1 * Ducky.speed
                    
                if Ducky.rect.x > closest_bread.rect.x:
                    Ducky.rect.x -= 1 * Ducky.speed

                if Ducky.rect.y > closest_bread.rect.y:
                    Ducky.rect.y -= 1 * Ducky.speed   

                if Ducky.rect.y < closest_bread.rect.y: 
                    Ducky.rect.y += 1 * Ducky.speed            

            Ducky.hunger = Ducky.hunger - 1*Ducky.speed
            #duck hunger goes down, faster duck looses more hunger

            if Ducky.hunger < 0: #if duck at 0 hunger he dies :(
                del Ducks[i]


            #display the bread

            #check for collision thorugh seeing if score is good
            # we already have the distance to the bread, so just check that
            breads_to_add = 0
            for distance, bread in closestlist:
                if ( distance < 5 ):    # pixels
                    print( "EATING BREAD" )
                    bread.eat()
                    breads_to_add += 1
                    Ducky.hunger += 60
                    
            if ( breads_to_add > 0 ):
                print( "ATE %d BREADS" % ( breads_to_add ) )
                # remove the eaten breads
                FoodList = [ b for b in FoodList if ( not b.eaten ) ]

            # add some new bread
            for i in range( breads_to_add ):
                FoodList.append(Food(random.randint(30,600),random.randint(30,600)))
                        
            #print(Ducky.hunger)


        #make the screen

        #display screen then food then ducks
        # fill the background
        DISPLAY.fill(blue)

        # paint the bread
        for FoodItem in (FoodList):
            DISPLAY.blit(FoodItem.image, (FoodItem.rect.topleft))

        # paint the duck
        DISPLAY.blit(Ducky.image, (Ducky.rect.topleft))
        
        pygame.display.update()
            
        
main()

ducks

Related