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:
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.