pythonpygame

How to spawn random color-changing shapes and add a counter in pygame


Currently, my game involves a player collecting shapes of a certain color a random number of times. The shapes will randomly spawn all over the screen, with ten shapes in all. The shapes will stay in for fifteen seconds before new shapes appear and the old ones disappear.

I am currently trying to make the shapes spawn in different places. The places don't need to be on random -- I don't know how to set the shapes to a different point without changing the size. I also need a way to switch the shapes' colors every 15 seconds.

Here is my current code:

import pygame
import random
import time

pygame.init()

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
size = ((300,250,50,50))
clock = pygame.time.Clock()
FPS = 150

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Game")
#colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
random_color = [red, green, blue]

#random
shape_number = random.randint (1,5)
shape_color = random.choice(random_color)
#shapes
player = pygame.Rect(size)
shape = pygame.Rect(size)
shape1 = pygame.Rect(size)
shape2 = pygame.Rect(size)
shape3 = pygame.Rect(size)
shape4 = pygame.Rect(size)
shape5 = pygame.Rect(size)
shape6 = pygame.Rect(size)
shape7 = pygame.Rect(size)
shape8 = pygame.Rect(size)
shape9 = pygame.Rect(size)
shape10 = pygame.Rect(size)
shape_color = random.choice(random_color)
counter = "You have to get ", shape_number, " ", shape_color, " colored shapes"

runWindow = True
while runWindow:
  pygame.draw.rect(screen, (white), player)
  pygame.draw.rect(screen, (shape_color), shape)
  pygame.draw.rect(screen, (shape_color), shape1)
  pygame.draw.rect(screen, (shape_color), shape2)
  pygame.draw.rect(screen, (shape_color), shape3)
  pygame.draw.rect(screen, (shape_color), shape4)
  pygame.draw.rect(screen, (shape_color), shape5)
  pygame.draw.rect(screen, (shape_color), shape6)
  pygame.draw.rect(screen, (shape_color), shape7)
  pygame.draw.rect(screen, (shape_color), shape8)
  pygame.draw.rect(screen, (shape_color), shape9)
  pygame.draw.rect(screen, (shape_color), shape10)
  
  key = pygame.key.get_pressed()
  if key[pygame.K_a] == True:
    player.move_ip(-1, 0)
  if key[pygame.K_w] == True:
    player.move_ip(0, -1)
  if key[pygame.K_d] == True:
    player.move_ip(1, 0)
  if key[pygame.K_s] == True:
    player.move_ip(0, 1)
  if player == shape:
    pygame.draw.rect(screen, (shape_color), shape)

  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      runWindow = False
  clock.tick(FPS)
  pygame.display.update()
  screen.fill(black)
  continue

pygame.quit()

The current code spawns in 10 different shapes and the player. However, as you can see if you run the code, the shapes are all clumped together, and I do not know how to set different spawnpoints. If you could also explain everything to me, I would be grateful as I am new to pygame.

Thank you for responding to this question if you do!


Solution

  • To spawn your shapes in a random position, you can modify the Rect virtual attributes, x, y, top, left, bottom, … etc.

    With minimal modifications to your code:

    shape1 = pygame.Rect(size)
    shape1.x = random.randint(0, SCREEN_WIDTH-1)  # randint includes the endpoint.
    shape1.y = random.randint(0, SCREEN_HEIGHT-1)
    

    You might want to further limit the range so your shapes aren't touching the borders of your window.

    As others have suggested, you should be creating your shapes in a loop. I also recommend you use sprites and store them in sprite groups to make things cleaner and easier.

    EDIT: Here's an example that uses sprites and groups for collision detection. It also uses KEYDOWN events to move the player sprite rather than checking which keys are currently pressed. I used a match statement to allow easy support for WASD and keys.

    import random
    import pygame
    from collections import Counter
    
    SCREEN_WIDTH = 800
    SCREEN_HEIGHT = 600
    FPS = 150
    
    my_colors = ["red", "green", "blue"]
    move_keys = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN, pygame.K_a, pygame.K_d, pygame.K_s, pygame.K_w, pygame.K_SPACE]
    
    class Shape(pygame.sprite.Sprite):
        def __init__(self, size, pos, color=None):
            pygame.sprite.Sprite.__init__(self)
            self.size = size
            self.image = pygame.Surface([size[0], size[1]])
            if color:
                self.color = color
            else:
                self.color = random.choice(my_colors)
            self.image.fill(self.color)
            self.rect = self.image.get_rect()
            self.rect.center = pos
            # uncomment to add some randomness to the reset interval
            self.reset_time = pygame.time.get_ticks() # + random.randint(0, 5_000)
        
        def update(self):
            """Randomize the color after fifteen seconds"""
            if pygame.time.get_ticks() - self.reset_time > 15_000:
                self.color = random.choice(my_colors)
                self.image.fill(self.color)
                self.reset_time = pygame.time.get_ticks()
            
    
    class Player(Shape):
        """A moving shape"""
        def __init__(self, pos):
            super().__init__((10, 10), pos, "firebrick")
            self.stop()  # initialised the speed to zero
    
        def stop(self):
            self.speedx = 0
            self.speedy = 0
        
        def move(self, key):
            """adjust speed in the direction of the key press"""
            match key:
                case pygame.K_LEFT | pygame.K_a:
                    self.speedx -= 1
                case pygame.K_RIGHT | pygame.K_d:
                    self.speedx += 1
                case pygame.K_UP | pygame.K_w:
                    self.speedy -= 1
                case pygame.K_DOWN | pygame.K_s:
                    self.speedy += 1
                case pygame.K_SPACE:
                    self.stop()
                case _:
                    print(f"Unsupported move key {key}")
                
        def update(self):
            """move the sprite according to its speed, bouncing off walls"""
            width, height = screen.get_size()
            if self.rect.left < 0:
                self.speedx *= -1  # reverse direction
                self.rect.left = 0
            elif self.rect.right > width:
                self.speedx *= -1
                self.rect.right = width
            self.rect.x += self.speedx
    
            if self.rect.top < 0:
                self.speedy *= -1  # reverse direction
                self.rect.top = 0
            elif self.rect.bottom > height:
                self.speedy *= -1
                self.rect.bottom = height
            self.rect.y += self.speedy
    
    def spawn_shapes(sprite_group, n=10):
        """Create a number of sprites at a random position and add them to the group"""
        for _ in range(n):
            position = random.randint(50, SCREEN_WIDTH-100) , random.randint(50, SCREEN_HEIGHT-100)
            shape = Shape(shape_size, position)
            sprite_group.add(shape)
        
    
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    
    clock = pygame.time.Clock()
    scores = Counter()
    
    shape_size = 50, 50
    shapes = pygame.sprite.Group()
    spawn_shapes(shapes)
    
    # create the player sprite
    player = pygame.sprite.GroupSingle()
    player.add(Player(screen.get_rect().center))
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key in move_keys:
                    player.sprite.move(event.key)
                    
        # refresh background
        screen.fill("black")
        # update game state
        shapes.update()
        player.update()
        # check for collisions, destroying collided Shape sprites
        collisions = pygame.sprite.groupcollide(player, shapes, False, True)
        if collisions:
            for collided_shapes in collisions.values():
                for collided_shape in collided_shapes:
                    # print(f"Collided with {collided_shape.color} shape")
                    scores[collided_shape.color] += 1
            player.sprite.stop()
        # update score # TODO: draw on the screen instead of the title bar.
        score_texts = [f"Score: {scores.total()}"]
        for color, score in scores.most_common():  # use most_common for sorted results
            score_texts.append(f"{color} : {score}")
        pygame.display.set_caption(" ".join(score_texts))
        if len(shapes) == 0:
            spawn_shapes(shapes)
        # draw sprites
        shapes.draw(screen)
        player.draw(screen)
        # update screen
        pygame.display.update()
        clock.tick(FPS)  # limit FPS
    
    pygame.quit()
    

    Python has a bunch of named colors, so you can use names in your code that can be more readable than a bunch of numbers.