What is the correct way to add a surface positional argument to the pygame draw method?

Question:

I am trying to use the draw method to update the screen in pygame.

However I get the following error when I run my program:

➜  Stars python3 stars.py
pygame 2.0.1 (SDL 2.0.14, Python 3.9.6)

Traceback (most recent call last):
  File "/Users/Desktop/python/Stars/stars.py", line 93, in <module>
    st.run_game()
  File "/Users/Desktop/python/Stars/stars.py", line 31, in run_game
    self._update_screen()
  File "/Users/Desktop/python/Stars/stars.py", line 84, in _update_screen
    star_draw.draw(self.screen)
TypeError: draw() missing 1 required positional argument: 'surface'

My program consists of three python files: stars.py, star.py, and settings.py.

stars.py contains the Stars class which manages the ‘game’.

star.py contains the Star class which manages a single star.

settings.py contains the Settings class which manages the screen settings.

stars.py:

import sys
import pygame
from settings import Settings
from star import Star
# Make a grid of stars appear on the screen
# /images/star.bmp
# A class to manage a star
class Stars:
    """Overall class to manage the game."""
    def __init__(self):
        """Initialize the game and create game resources."""
        # Initialize pygame.
        pygame.init()
        # Make an instance of the Settings class and assign it.
        self.settings = Settings()
        # Make a screen with dimensions defined in the settings module.
        self.screen = pygame.display.set_mode(
            (self.settings.screen_width, self.settings.screen_height))
        # Set a caption on the pygame.
        pygame.display.set_caption("Stars")
        # Create a group for the stars.
        self.stars = pygame.sprite.Group()
        # Add an attribute that is a method to create stars.
        self._create_stars()
        self.star = Star(self)

    def run_game(self):
        """Start the main loop for the game."""
        while True:
            self._check_events()
            self._update_screen()

    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 _create_stars(self):
        """Create a sky full of stars."""
        # Create a star and find the number of stars in a row.
        # Spacing between each star is equal to two star widths.
        star = Star(self)
        star_width, star_height = star.rect.size
        available_space_x = self.settings.screen_width - (star_width)
        number_stars_x = available_space_x // (2 * star_width)

        # Determine the number of rows of stars that fit on the screen.
        # Fill most of the screen with stars.
        available_space_y = (self.settings.screen_height - (2 * star_height))
        number_rows = available_space_y // (2 * star_height)

        # Fill the sky with stars.
        # For every row in the total amount of rows.
        for row_number in range(number_rows):
            # For every star in the total amount of stars.
            for star_number in range(number_stars_x):
                # Call _create_star with the loops as arguments.
                self._create_star(star_number, row_number)

    def _create_star(self, star_number, row_number):
        """Create a star and place it in the row."""
        star = Star(self)
        star_width, star_height = star.rect.size
        star.x = star_width + (2 * star_width) * star_number
        star.rect.x = star.x
        star.rect.y = star.rect.height + (2 * star.rect.height) * row_number
        # Add the star to the group stars.
        self.stars.add(star)

    def _update_screen(self):
        """Update images on the screen, and flip to the new screen."""
        # Redraw the screen during each pass through the loop.
        self.screen.fill(self.settings.bg_color)
        # Draw the star on the screen
        self.star.draw(self.screen)
        # Make the most recently drawn screen visible.
        pygame.display.flip()


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

star.py:

import pygame
from pygame.sprite import Sprite

class Star(pygame.sprite.Sprite):
    """A class to manage a single star."""

    def __init__(self, star_game):
        """Initialize the star and set it's starting position."""
        super().__init__()
        self.screen = star_game.screen

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

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

        # Store the star's exact vertical position.
        self.y = float(self.rect.y)
        self.draw = pygame.sprite.Group.draw

settings.py:

class Settings:
    """A class to store all settings for the star game."""

    def __init__(self):
        """Initialize the game's settings."""
        # Screen settings
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (50, 50, 50)
Asked By: mrm

||

Answers:

pygame.sprite.Group.draw is a method. It must be called with an instance of pygame.sprite.Group.

pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.

The former delegates the to the update method of the contained pygame.sprite.Sprites – you have to implement the method. See pygame.sprite.Group.update():

Calls the update() method on all Sprites in the Group […]

The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects – you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():

Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. […]

Remove the line self.draw = pygame.sprite.Group.draw it is misleading. The Star object doesn’t need a draw method, because it’s drawn by the Group.

Just call:

self.stars.draw(self.screen)

Method _update_screen:

class Stars:
    # [...]

    def _update_screen(self):
        """Update images on the screen, and flip to the new screen."""
        # Redraw the screen during each pass through the loop.
        self.screen.fill(self.settings.bg_color)
        
        # Draw the star on the screen
        self.stars.draw(self.screen)        # <--
        
        # Make the most recently drawn screen visible.
        pygame.display.flip()
Answered By: Rabbid76
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.