4
\$\begingroup\$

I made the following code. It's a simple animation of 'raindrops' falling and splattering once they reach the ground. The images I use can be found here: https://i.sstatic.net/oKOVA.jpg

import pygame, sys
from pygame import locals  
import random
import itertools


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

def makeNewDrop(display, drops):
    # there isa 15% chance we'll make a new drop, each frame
    x = display.get_width() * random.random() # a random xpostion
    return RainDrop(x, display, drops)        

class RainDrop(pygame.sprite.Sprite):
    img = pygame.image.load('assests/rain_drop.png')

    def __init__(self, x, display, group):
        # call the super constructor, and pass in the 
        # group you've created and it is automatically added to the group every 
        # time you create an instance of this class
        pygame.sprite.Sprite.__init__(self, group) 
        self.y = 0
        self.x = x
        self.fall_step = 5
        self.display = display
        self.die = False
        self. animation = iter([
            pygame.image.load('assests/splatter_1.png'),
            pygame.image.load('assests/splatter_2.png'),
            pygame.image.load('assests/splatter_3.png'),
            pygame.image.load('assests/splatter_4.png')
        ])

    def update(self):
        self.checkIfShouldDie()
        self.fall()
        if self.die:
            try:
                self.img = next(self.animation)
            except StopIteration:
                self.kill()

        self.display.blit(self.img, (self.x,self.y))

    def fall(self):
        self.y = self.y + self.fall_step



    def checkIfShouldDie(self):
        if self.img.get_rect().bottom + self.y >= self.display.get_height():
            self.die = True
            self.y = self.display.get_height() - self.img.get_rect().bottom - 5

if __name__ == '__main__':
    pygame.init()
    drops = pygame.sprite.Group()
    cooldown = 0

    fps = 30
    clock = pygame.time.Clock()

    main_display = pygame.display.set_mode((400,400),0,32)
    pygame.display.set_caption("rain!") 

    while True:
        main_display.fill((255,255,255))

        if random.random() >= 0.85 and cooldown == 0:
            # a 15% change we'll make a new drop, each frame
            # assuming we didn't make a drop in the last few frames
            drop = makeNewDrop(main_display, drops) # automatically added to the drops group
            cooldown = cooldown + 5

        drops.update()

        # reduce cooldown
        cooldown = 0 if cooldown <= 0 else cooldown - 1
        listenToQuit()
        pygame.display.update()
        clock.tick(30)

How can this code be improved? Specifically the part where I iterator over animations and catch the StopIteration error seems very hacky to me.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

PEP8

The official Python style guide will suggest that:

  • there be two newlines between functions at the global scope, and only one newline between functions in the class scope (i.e. check_if_should_die)
  • functions be named in lower_snake_case, i.e. listen_to_quit

Hard exit

Currently, you have a forever-loop that only exits on sys.exit. Instead, simply return a boolean from listenToQuit (which can be called should_quit), and if the return value is true, break out of the loop.

Abbreviated imports

pygame.sprite.Sprite can just be Sprite if you from pygame.sprite import Sprite.

Typo

assests -> assets

Generator

    self. animation = iter([
        pygame.image.load('assests/splatter_1.png'),
        pygame.image.load('assests/splatter_2.png'),
        pygame.image.load('assests/splatter_3.png'),
        pygame.image.load('assests/splatter_4.png')
    ])

can be

self.animation = iter(
   pygame.image.load(f'assets/splatter_{i}.png')
   for i in range(1, 5)
)

Generally I don't think your iter/StopIteration approach is that bad. That said, you can rework it by changing your outer while True into a for drop in drops, where drops is an iterable through four instances of a drop object tied to a specific asset.

\$\endgroup\$
1
  • \$\begingroup\$ Quick question just to be sure I understand: The last suggestion of for drop in drops would work for an animation functionality, but this would not work if I would want this animation to only be the backdrop, and later add more functionality such as listening to keyboard input etc? Thanks for the detailed answer! It's very useful \$\endgroup\$ Commented Apr 6, 2020 at 11:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.