sw123456
sw123456

Reputation: 3459

Understand object orientated python - initialization of objects in pygame

I'm learning Object Orientated Python and understand the main principals of classes and creating objects from classes however I need something explained Re: the pygame code below. I'm struggling to get my head around what's happening when sprite lists are being created and the two lines of code under the code which creates the ball object (allsprites.add etc). In other words what are sprites and why are lists of them created? Why isn't the ball object just created from the class on its own? Why does it need to be added to a sprite list?? What's going on? Any explanation would be greatly appreciated.

""" 
 Sample Breakout Game

 Sample Python/Pygame Programs
 Simpson College Computer Science
 http://programarcadegames.com/
 http://simpson.edu/computer-science/
"""

# --- Import libraries used for this program

import math
import pygame

# Define some colors
black = (0, 0, 0)
white = (255, 255, 255)
blue = (0, 0, 255)

# Size of break-out blocks
block_width = 23
block_height = 15

class Block(pygame.sprite.Sprite):
    """This class represents each block that will get knocked out by the ball
    It derives from the "Sprite" class in Pygame """

    def __init__(self, color, x, y):
        """ Constructor. Pass in the color of the block, 
            and its x and y position. """

        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        # Create the image of the block of appropriate size
        # The width and height are sent as a list for the first parameter.
        self.image = pygame.Surface([block_width, block_height])

        # Fill the image with the appropriate color
        self.image.fill(color)

        # Fetch the rectangle object that has the dimensions of the image
        self.rect = self.image.get_rect()

        # Move the top left of the rectangle to x,y.
        # This is where our block will appear..
        self.rect.x = x
        self.rect.y = y


class Ball(pygame.sprite.Sprite):
    """ This class represents the ball        
        It derives from the "Sprite" class in Pygame """

    # Speed in pixels per cycle
    speed = 10.0

    # Floating point representation of where the ball is
    x = 0.0
    y = 180.0

    # Direction of ball (in degrees)
    direction = 200

    width = 10
    height = 10

    # Constructor. Pass in the color of the block, and its x and y position
    def __init__(self):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        # Create the image of the ball
        self.image = pygame.Surface([self.width, self.height])

        # Color the ball
        self.image.fill(white)

        # Get a rectangle object that shows where our image is
        self.rect = self.image.get_rect()

        # Get attributes for the height/width of the screen
        self.screenheight = pygame.display.get_surface().get_height()
        self.screenwidth = pygame.display.get_surface().get_width()

    def bounce(self, diff):
        """ This function will bounce the ball 
            off a horizontal surface (not a vertical one) """

        self.direction = (180 - self.direction) % 360
        self.direction -= diff

    def update(self):
        """ Update the position of the ball. """
        # Sine and Cosine work in degrees, so we have to convert them
        direction_radians = math.radians(self.direction)

        # Change the position (x and y) according to the speed and direction
        self.x += self.speed * math.sin(direction_radians)
        self.y -= self.speed * math.cos(direction_radians)

        # Move the image to where our x and y are
        self.rect.x = self.x
        self.rect.y = self.y

        # Do we bounce off the top of the screen?
        if self.y <= 0:
            self.bounce(0)
            self.y = 1

        # Do we bounce off the left of the screen?
        if self.x <= 0:
            self.direction = (360 - self.direction) % 360
            self.x = 1

        # Do we bounce of the right side of the screen?
        if self.x > self.screenwidth - self.width:
            self.direction = (360 - self.direction) % 360
            self.x = self.screenwidth - self.width - 1

        # Did we fall off the bottom edge of the screen?
        if self.y > 600:
            return True
        else:
            return False

class Player(pygame.sprite.Sprite):
    """ This class represents the bar at the bottom that the player controls. """

    def __init__(self):
        """ Constructor for Player. """
        # Call the parent's constructor
        pygame.sprite.Sprite.__init__(self)

        self.width = 75
        self.height = 15
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill((white))

        # Make our top-left corner the passed-in location.
        self.rect = self.image.get_rect()
        self.screenheight = pygame.display.get_surface().get_height()
        self.screenwidth = pygame.display.get_surface().get_width()

        self.rect.x = 0
        self.rect.y = self.screenheight-self.height

    def update(self):
        """ Update the player position. """
        # Get where the mouse is
        pos = pygame.mouse.get_pos()
        # Set the left side of the player bar to the mouse position
        self.rect.x = pos[0]
        # Make sure we don't push the player paddle 
        # off the right side of the screen
        if self.rect.x > self.screenwidth - self.width:
            self.rect.x = self.screenwidth - self.width

# Call this function so the Pygame library can initialize itself
pygame.init()

# Create an 800x600 sized screen
screen = pygame.display.set_mode([800, 600])

# Set the title of the window
pygame.display.set_caption('Breakout')

# Enable this to make the mouse disappear when over our window
pygame.mouse.set_visible(0)

# This is a font we use to draw text on the screen (size 36)
font = pygame.font.Font(None, 36)

# Create a surface we can draw on
background = pygame.Surface(screen.get_size())

# Create sprite lists
blocks = pygame.sprite.Group()
balls = pygame.sprite.Group()
allsprites = pygame.sprite.Group()

# Create the player paddle object
player = Player()
allsprites.add(player)

# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)

# The top of the block (y position)
top = 80

# Number of blocks to create
blockcount = 32

# --- Create blocks

# Five rows of blocks
for row in range(5):
    # 32 columns of blocks
    for column in range(0, blockcount):
        # Create a block (color,x,y)
        block = Block(blue, column * (block_width + 2) + 1, top)
        blocks.add(block)
        allsprites.add(block)
    # Move the top of the next row down
    top += block_height + 2

# Clock to limit speed
clock = pygame.time.Clock()

# Is the game over?
game_over = False

# Exit the program?
exit_program = False

# Main program loop
while exit_program != True:

    # Limit to 30 fps
    clock.tick(30)

    # Clear the screen
    screen.fill(black)

    # Process the events in the game
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit_program = True

    # Update the ball and player position as long
    # as the game is not over.
    if not game_over:
        # Update the player and ball positions
        player.update()
        game_over = ball.update()

    # If we are done, print game over
    if game_over:
        text = font.render("Game Over", True, white)
        textpos = text.get_rect(centerx=background.get_width()/2)
        textpos.top = 300
        screen.blit(text, textpos)

    # See if the ball hits the player paddle
    if pygame.sprite.spritecollide(player, balls, False):
        # The 'diff' lets you try to bounce the ball left or right 
        # depending where on the paddle you hit it
        diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)

        # Set the ball's y position in case 
        # we hit the ball on the edge of the paddle
        ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
        ball.bounce(diff)

    # Check for collisions between the ball and the blocks
    deadblocks = pygame.sprite.spritecollide(ball, blocks, True)

    # If we actually hit a block, bounce the ball
    if len(deadblocks) > 0:
        ball.bounce(0)

        # Game ends if all the blocks are gone
        if len(blocks) == 0:
            game_over = True

    # Draw Everything
    allsprites.draw(screen)

    # Flip the screen and show what we've drawn
    pygame.display.flip()

pygame.quit()

Upvotes: 0

Views: 1321

Answers (1)

Peter Gibson
Peter Gibson

Reputation: 19554

You don't need to add the balls and blocks to sprite lists - it's just a matter of convenience. You could manually check each ball for a collision, but it's easier to just tell pygame to check them all for you

# See if the ball hits the player paddle
if pygame.sprite.spritecollide(player, balls, False):
    # The 'diff' lets you try to bounce the ball left or right 
    # depending where on the paddle you hit it
    diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)

    # Set the ball's y position in case 
    # we hit the ball on the edge of the paddle
    ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
    ball.bounce(diff)

You could draw each thing to the screen separately on each frame, but it's easier just to tell pygame to do it for you:

# Draw Everything
allsprites.draw(screen)

Things can be in more than one list as required, for example a ball is added to the balls list so that you can easily check for collisions, but also added to the allsprites list so that you can easily draw everything on the screen

# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)

Edit: An important distinction is that allsprites is actually a sprite.Group. It has a list of sprites inside it, but it also has other methods like draw.

To address your question of "what is a Sprite", it's simply a thing that gets drawn on screen. pygame methods like sprite.Group.draw expect a list of things with certain attributes - such as update. The easiest way to make sure that you provide all of those attributes with the right names is to subclass Sprite, however this is also a (strongly recommended) convenience thing - for instance, this is from the pygame source code:

While it is possible to design sprite and group classes that don't derive from the Sprite and AbstractGroup classes below, it is strongly recommended that you extend those when you add a Sprite or Group class.

So what specifically does subclassing Sprite get you? Let's take a look at the source. Here's how to find the source code for a python module:

>>> import pygame.sprite
>>> pygame.sprite.__file__
'c:\\Python27\\lib\\site-packages\\pygame\\sprite.py'
>>>

Every python module has a __file__ attribute that tells you where the source is located (well not quite every). If you open it up in your editor, and scroll down, you see the class definition for Sprite:

class Sprite(object):
    """simple base class for visible game objects
    pygame.sprite.Sprite(*groups): return Sprite

    The base class for visible game objects. Derived classes will want to 
    override the Sprite.update() and assign a Sprite.image and 
    Sprite.rect attributes.  The initializer can accept any number of 
    Group instances to be added to.

    When subclassing the Sprite, be sure to call the base initializer before
    adding the Sprite to Groups.
    """

    def __init__(self, *groups):
        self.__g = {} # The groups the sprite is in
        if groups: self.add(groups)

    def add(self, *groups):
        """add the sprite to groups
        Sprite.add(*groups): return None

        Any number of Group instances can be passed as arguments. The 
        Sprite will be added to the Groups it is not already a member of.
        """
        has = self.__g.__contains__
        for group in groups:
            if hasattr(group, '_spritegroup'):
                if not has(group):
                    group.add_internal(self)
                    self.add_internal(group)
            else: self.add(*group)

    def remove(self, *groups):
        """remove the sprite from groups
        Sprite.remove(*groups): return None

        Any number of Group instances can be passed as arguments. The Sprite will
        be removed from the Groups it is currently a member of.
        """
        has = self.__g.__contains__
        for group in groups:
            if hasattr(group, '_spritegroup'):
                if has(group):
                    group.remove_internal(self)
                    self.remove_internal(group)
            else: self.remove(*group)

    def add_internal(self, group):
        self.__g[group] = 0

    def remove_internal(self, group):
        del self.__g[group]

    def update(self, *args):
        """method to control sprite behavior
        Sprite.update(*args):

        The default implementation of this method does nothing; it's just a
        convenient "hook" that you can override. This method is called by
        Group.update() with whatever arguments you give it.

        There is no need to use this method if not using the convenience 
        method by the same name in the Group class.
        """
        pass

    def kill(self):
        """remove the Sprite from all Groups
        Sprite.kill(): return None

        The Sprite is removed from all the Groups that contain it. This won't
        change anything about the state of the Sprite. It is possible to continue
        to use the Sprite after this method has been called, including adding it
        to Groups.
        """
        for c in self.__g.keys():
            c.remove_internal(self)
        self.__g.clear()

    def groups(self):
        """list of Groups that contain this Sprite
        Sprite.groups(): return group_list

        Return a list of all the Groups that contain this Sprite.
        """
        return self.__g.keys()

    def alive(self):
        """does the sprite belong to any groups
        Sprite.alive(): return bool

        Returns True when the Sprite belongs to one or more Groups.
        """
        return (len(self.__g) != 0)

    def __repr__(self):
        return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))

So in summary, you don't have to subclass Sprite - you could just provide all of these methods on your own - but it's easier if you do ;)

Upvotes: 1

Related Questions