Skip to content
Advertisement

Going through sprites in an array and redrawing them when it’s time to redraw causes some to blink

I’m working on a game that is like Cookie Clickers in Python. I’m trying to replicate the feature where when you get a cookie (1 cookie from CPS or from clicking the cookie) it makes a small cookie fall down the screen in the background with a random X position and a random Y position.

But the falling cookies start blinking if there is 5+ falling cookies in the array (that I store them in).

I know the reason why, it’s because when it’s calling the screen to redraw, I set the background to black and redraw everything BUT then it has to go through each falling cookie to redraw it which could take time if there was, say, 100 cookies in that array.

If there was 100 cookies in the array, the last cookie might disappear for ~1 second before it finally gets to the end of the array to that cookie.

Here is my code:

fcookies = []

    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button != 1:
                    if event.button != 3:
                        continue
                # pygame.mixer.Sound.stop(CRUNCH1)
                pos = pygame.mouse.get_pos()
                clicked_buttons = [s for s in buttons if s.rect.colliderect(pygame.Rect(pos[0], pos[1], 1, 1))]#s.rect.collidepoint(pos)]
                for button in clicked_buttons:
                    if button.name == "cookie":
                        pygame.mixer.Sound.play(random.choice(CRUNCHSFX))
                        fookies.append(FallingCookie(WIN.get_width()))
                        cookies += cpc
                    if button.name == "upgcookie":
                        if cookies >= upgradeB.price:
                            cookies -= upgradeB.price
                            upgradeB.price *= 2
                            upgrades += 1
                            cpc += 1

                    if button.name == "autoclicker":
                        if cookies >= autoclickerB.price:
                            cookies -= autoclickerB.price
                            autoclickerB.price += 50
                            autos += 1
                            cps += .2

                    if button.name == "betterauto":
                        if cookies >= betterAutoB.price:
                            cookies -= betterAutoB.price
                            betterAutoB.price *= 4
                            bautos += 1
                            cps += 50

        clock.tick(FPS)

        curtime = time.time()
        
        if curtime - nowtime > 1:
            nowtime = curtime
            cookies += cps

        for i, cookie in enumerate(fookies):
            if cookie.y > WIN.get_height() + 5:
                fookies.pop(i)
                print("popped")
                continue
            cookie.y += cookie.speed
            cookie.rect = display(FALLING, (cookie.x, cookie.y)) # Issue!

        cookt = font(CFONT64, f"{'%.1f' % cookies} {'Cookies' if floor(cookies) != 1 else 'Cookie'}")
        display(cookt, cookt.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .07)))

        cpst = font(CFONT26, f"{'%.1f' % cps} CPS")
        display(cpst, cpst.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .15)))

        cookieB.rect = display(COOKIE, ..., True)
        upgradeB.rect = display(UPGCOOKIE, (0,0), True, -450, 300)
        autoclickerB.rect = display(AUTOCOOKIE, (0,0), True, -187, 300)
        betterAutoB.rect = display(BETTERAUTO, (0, 0), True, 75, 300)

        upgt = font(CFONT26, f"{upgradeB.price} cookies (lvl. {upgrades})")
        display(upgt, (0,0), True, -450, 370)

        autot = font(CFONT26, f"{autoclickerB.price} cookies (x{autos})")
        display(autot, (0,0), True, -187, 370)

        bautot = font(CFONT26, f"{betterAutoB.price} cookies (x{bautos})")
        display(bautot, (0,0), True, 75, 370)

        pygame.display.update()
        WIN.fill(pygame.Color("black"))

In the for loop, cookie is a Class (FallingCookie). Here is the code to that class:

from random import random
import pygame

from tools import clamp
class FallingCookie:
    def __init__(self, width):
        self.x = random() * width
        self.y = 0
        self.speed = clamp(random() * 2, .9, 2)

    rect = pygame.Rect(1, 1, 1, 1)

So now, how do I make it so they all draw at once so there is no blinking and then make it move down.

Advertisement

Answer

You should avoid calling draw functions every frame, that’s too slow and I doubt your sprites need to be that dynamic. Instead you should draw onto a surface once and then blit that to your main window every frame. Even better, create a sprite that encapsulates much of the functionality you desire.

Here is an example of a FallingCookie class that inherits from pygame.sprite.Sprite:

class FallingCookie(pygame.sprite.Sprite):
    """Cookie that drops from the top to the bottom of the screen"""
    def __init__(self, width, height):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((20, 20), pygame.SRCALPHA)
        # see https://www.pygame.org/docs/ref/color_list.html for named colors
        pygame.draw.circle(self.image, "burlywood4", (9, 9), 9)
        x = random.randint(0, width-1)  # randint includes endpoints
        y = 0
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = random.randint(1, 6)
        self.max_y = height

    def update(self):
        # destroy cookie if it is off the screen
        self.rect.y += self.speed
        if self.rect.y > self.max_y:
            self.kill()

You can then create a Group to manage the sprites:

cookies = pygame.sprite.Group()

To add some cookies to the group:

for _ in range(13):
    cookies.add(FallingCookie(width, height))

To move every cookie and have it check if it has dropped off the window:

cookies.update()

To draw all the cookies:

cookies.draw(WIN)

Putting it all together, and adding some chocolate chips my ancient PC can easily maintain sixty frames per second with two thousand cookies: Choc Chip Cookies!

Full Code Listing:

import random
import pygame


class FallingCookie(pygame.sprite.Sprite):
    """Cookie that drops from the top to the bottom of the screen"""
    def __init__(self, width, height):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((20, 20), pygame.SRCALPHA)
        # see https://www.pygame.org/docs/ref/color_list.html for named colors
        pygame.draw.circle(self.image, "burlywood4", (9, 9), 9)
        # draw random choc chips
        for _ in range(random.randint(2, 8)):
            # a random position inside the cookie
            # 45° line from center intersects edge at ~6.36
            pos = 9 + random.randint(-6, 6), 9 + random.randint(-6, 6)
            # how large should a choc chip be?
            radius = random.uniform(0.8, 3.6)
            color = random.choice(
                ["chocolate4", "saddlebrown", "sienna4", "tan4"])
            pygame.draw.circle(self.image, color, pos, radius)
        self.image.set_alpha(127)  # make transparent
        x = random.randint(0, width-1)  # randint includes endpoints
        y = 0
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = random.randint(1, 6)
        self.max_y = height

    def update(self):
        # destroy self if off screen
        self.rect.y += self.speed
        if self.rect.y > self.max_y:
            self.kill()


pygame.init()
width, height = 800, 400
max_cookies = 2_000

screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

cookies = pygame.sprite.Group()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # update game state
    cookies.update()
    # maybe add more cookies?
    if len(cookies) < max_cookies:
        # add a bakers dozen at a time
        for _ in range(13):
            cookies.add(FallingCookie(width, height))

    screen.fill("black")
    cookies.draw(screen)

    pygame.display.update()
    clock.tick(60)
    title = f"Cookies: {len(cookies):,} FPS: {clock.get_fps():.1f}"
    pygame.display.set_caption(title)

Next you’d want to define some button sprites. See this answer for a good demonstration.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement