1

I am trying to create a stream of raindrops to give a sense of rain using the code below. Raindrop is used to model the raindrop. It has an update method() to increase the y coordinate so that it will look like its is dropping on the screen when I call this method.

from pygame.sprite import Sprite
class Raindrop(Sprite):
    """A class to represent a single raindrop."""

    def __init__(self, rain_game):
        """Initialize the raindrop and set its starting position."""
        super().__init__()
        self.screen = rain_game.screen
        self.settings = rain_game.settings

        # Load the raindrop image and set its rect attribute
        self.image = pygame.image.load('raindrop.bmp')
        self.rect = self.image.get_rect()

        #Start each raindrop near the top left of the screen.
        self.rect.x = self.rect.width
        self.rect.y = self.rect.height

        #Store the raindrop`s exact horizontal and vertical positions.
        self.x = self.rect.x
        self.y = self.rect.y

    def update(self):
        """Shift raindrops to one level below"""
        self.y += self.settings.rain_speed
        self.rect.y = self.y

class Settings:
    """A Class to store all settings for a Sky Full of Stars."""

    def __init__(self):
        """Initialize the game`s settings."""
        # Screen setiings
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (255, 255, 255)
        self.rain_speed = 1.0

I have a problem with the remove_add_raindrops method in the below code. It works as expected but only for 4 rows of raindrops in my laptop. After 4 rows this code stops to print any raindrops to the screen. So I see only 8 rows of raindrops dropping from top to the bottom of my screen.

import sys

import pygame

from settings import Settings
from raindrop import Raindrop

class Raining():
    """Overall class to display a sky with raindrops."""

    def __init__(self):
        """Initialize the game, and create game resources."""
        pygame.init()
        self.settings = Settings()

        self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
        self.settings.screen_width = self.screen.get_rect().width
        self.settings.screen_height = self.screen.get_rect().height
        pygame.display.set_caption("It is raining today")

        self.raindrops = pygame.sprite.Group()

        self._create_raindrops()

    def _create_raindrop(self, raindrop_number, row_number):
        """Create a raindrop and place it in the row."""
        raindrop = Raindrop(self)
        raindrop_width, raindrop_height = raindrop.rect.size
        raindrop.x = raindrop_width + 2 * raindrop_width * raindrop_number
        raindrop.rect.x = raindrop.x
        raindrop.y = raindrop_height + 2 * raindrop_height * row_number
        raindrop.rect.y = raindrop.y
        self.raindrops.add(raindrop)

    def _create_raindrops(self):
        """Create a full screen fo raindrops."""
        # Create a raindrop and find the number of raindrops in a row.
        # Spacing between each raindrop is equal to one raindrop width.
        raindrop = Raindrop(self)
        raindrop_width, raindrop_height = raindrop.rect.size
        available_space_x = self.settings.screen_width - (2 * raindrop_width)
        number_raindrops_x = available_space_x // (2 * raindrop_width)

        # Determine the number of rows of raindrops that fit on the screen.
        available_space_y = (self.settings.screen_height - 2 * raindrop_height)
        number_rows = available_space_y // (2 * raindrop_height)

        # Create a full screen of raindrops.
        for row_number in range(number_rows):
            for raindrop_number in range (number_raindrops_x):
                self._create_raindrop(raindrop_number, row_number)


    def _remove_add_raindrops(self):
        """
        Get rid of old raindrops. 
        Then update their y coordinate and append to the raindrops sprite group
        """ 
        for raindrop in self.raindrops.copy():
            if raindrop.rect.y >= self.settings.screen_height:
                self.raindrops.remove(raindrop)
                raindrop.rect.y -= self.settings.screen_height
                self.raindrops.add(raindrop)

    def run_game(self):
        """Start the main loop for the game."""
        while True:
            self._check_events()
            self._update_raindrops()
            self._remove_add_raindrops()
            self._update_screen()
            print(f"Nr of remaining raindrops: {len(self.raindrops)}")
            #Make the most recently drawn screen visible.
            pygame.display.flip()

    def _check_events(self):
        """Respond to keypresses and mouse events."""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys-exit()
            elif event.type == pygame.KEYDOWN:
                self._check_keydown_events(event)

    def _check_keydown_events(self, event):
        """Respond to keypresses."""
        if event.key == pygame.K_q:
            sys.exit()

    def _update_raindrops(self):
        """Update the positions of all raindrops."""
        self.raindrops.update()

    def _update_screen(self):
        """Update images on the screen, and flip to the new screen."""
        self.screen.fill(self.settings.bg_color)
        self.raindrops.draw(self.screen)

if __name__ == '__main__':
    # Make a game instance, and run the game.
    rain_game = Raining()
    rain_game.run_game()

I also printed the number of raindrops in the list and it is always fixed at 40. I suppose that means I keep adding new raindrops with a new y coordinate after I delete the ones that slips below the screen.

Any hints?

Update:

I removed the _remove_add_raindrops method and re-wrote the update method under the raindrop class like this:

def update(self):
    """Shift raindrops to one level below"""
    self.y += self.settings.rain_speed
    self.rect.y = self.y
    if self.rect.top >= self.screen.get_rect().bottom:
        self.rect.y -= self.settings.screen_height

It is still the same behavior.

2 Answers 2

1

_remove_add_raindrops runs

raindrop.rect.y -= self.settings.screen_height

to change the y attribute of a Raindrop's rect.

But that change is immediately overwritten when Raindrop's update function is called:

def update(self):
    """Shift raindrops to one level below"""
    self.y += self.settings.rain_speed
    self.rect.y = self.y

because it gets changed again to self.y.

Just remove _remove_add_raindrops (removing and immediately readding a Raindrop to self.raindrops makes no sense anyway) and change your Raindrop class something like this:

class Raindrop(Sprite):
    """A class to represent a single raindrop."""

    def __init__(self, rain_game):
        """Initialize the raindrop and set its starting position."""
        super().__init__()
        self.screen = rain_game.screen
        self.settings = rain_game.settings

        # Load the raindrop image and set its rect attribute
        self.image = pygame.image.load('raindrop.bmp')
        self.rect = self.image.get_rect()

        #Start each raindrop near the top left of the screen.
        self.rect.x = self.rect.width
        self.rect.y = self.rect.height

    def update(self):
        """Shift raindrops to one level below"""
        self.rect.move_ip(0, self.settings.rain_speed)
        if not pygame.display.get_surface().get_rect().bottom > self.rect.top:
            self.rect.bottom = 0

so the Raindrop class can check itself if it is going out of screen. If you want to store the position of a Sprite using floats instead of integers, I recommend using the Vector2 class to do so instead of single attributes, because this makes it easier to move your sprites diagonally in the long run.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the detailed comment. I am trying to achieve this by using the methods I used in the OP. I also added a code of different try according to your suggestion at the end of my post. Can you please have a look at it? This code also behaves the same. After 4 rows of airdrops, I see an empty screen. I am not removing any raindrops this time, though!
0

The problem was that self.y was increasing indefinitely since I was not re-setting it anywhere in the original code or in the updated code.

def update(self):
    """Shift raindrops to one level below"""
    self.y += self.settings.rain_speed
    self.rect.y = self.y
    if self.rect.top >= self.screen.get_rect().bottom:
        self.rect.y -= self.settings.screen_height

Change of last line above from "self.rect.y" to "self.y" solved the problem.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.