Skip to content
Advertisement

Creating a Asteroid type game, problem is when player waits to press play button many asteroids spawn instead of controlled amount

About 500 lines of code, so to make it easier to read I believe the bug comes from _update_asteroids(self) function. Simply put, when the user is in the paused game state and must press play, if the player decides to wait lets say a minute. A minutes worth of asteroids will spawn at the exact same time. I believe this is because the pygame.time.get_ticks() function continues to gain ticks while game is paused. Is there any way to reset the ticks or make it so that 100s of asteroids do not spawn when the user decides to wait before pressing play button?

import pygame
import sys
from pygame.sprite import Sprite
import random
from time import sleep
import pygame.font


 
class Game(Sprite):
    """ a class the creates a window with a blue screen """
    def __init__(self):
        pygame.init()
        super().__init__()


        #--------------------------------------------------------------------------------------------
        #screen size, color, caption
        self.screen = pygame.display.set_mode((1200,800))   #create attribute to hold display settings
        self.bg_color = (0,0,255)                           #create attribute to hold RGB color (blue)
        pygame.display.set_caption("Blue Screen")
        #--------------------------------------------------------------------------------------------


        #--------------------------------------------------------------------------------------------
        #tank drawing
        self.screen_rect = self.screen.get_rect()               #get the screen rect dim
        self.image = pygame.image.load('images/tank.gif')       #load the image from directory
        self.rect = self.image.get_rect()                       #get the image rect dim
        self.rect.center = self.screen_rect.center              #store the screens center x/y coord 

        self.x = float(self.rect.x)
        self.y = float(self.rect.y)

        #tank movement
        self.tank_moving_left = False
        self.tank_moving_right = False
        self.tank_moving_up = False
        self.tank_moving_down = False
        self.tank_speed = 0.5                                                       #tank pixels
        self.direction_right = self.image                                       #holds right image
        self.direction_left = pygame.transform.flip(self.image, True, False)    #holds left image
        #--------------------------------------------------------------------------------------------


        #--------------------------------------------------------------------------------------------
        #UI
        self.health = 100
        self.visable = True
        self.sb = Scoreboard(self)
        #--------------------------------------------------------------------------------------------


        #--------------------------------------------------------------------------------------------
        #bullet
        self.bullets = pygame.sprite.Group()
        self.current_direction = self.direction_right
        #--------------------------------------------------------------------------------------------

        #--------------------------------------------------------------------------------------------
        #asteroids
        self.asteroids = pygame.sprite.Group()
        self.next_object_time = 0
        self.time_interval = 250
        #self._create_asteroids()
        #--------------------------------------------------------------------------------------------


        #--------------------------------------------------------------------------------------------
        #button
        self.game_active = False
        self.play_button = Button(self, "Play")
        #--------------------------------------------------------------------------------------------


    def _create_asteroids(self):
        """ create the asteroid shower """
        #create a asteroid
        number_of_aliens = 1
        for asteroid_num in range(number_of_aliens):
            self._create_asteroid()



    def _create_asteroid(self):
        asteroid = Asteroid(self)
        if asteroid.x <= 0:
            asteroid.direction = 1
        elif asteroid.x >= 1160:
            asteroid.direction = -1
        self.asteroids.add(asteroid)

        

    def move(self):
        """ move tnak tank_speed based on direction of movement (key pressed)
            also detect collision """ 
        if self.tank_moving_right and self.rect.right < self.screen_rect.right:
            self.x += self.tank_speed
            self.rect.x = self.x
        if self.tank_moving_left and self.rect.left > self.screen_rect.left:
            self.x -= self.tank_speed
            self.rect.x = self.x
        if self.tank_moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.y += self.tank_speed
            self.rect.y = self.y
        if self.tank_moving_up and self.rect.top > self.screen_rect.top:
            self.y -= self.tank_speed
            self.rect.y = self.y



    def draw_healthbar(self):
        pygame.draw.rect(self.screen, (255,0,0), (self.rect.x, self.rect.y - 20, 100, 10))
        pygame.draw.rect(self.screen, (0,255,0), (self.rect.x, self.rect.y - 20, 100 - (100 - self.health), 10))



    def blitme(self):
        """ draw the image of the tank """
        self.screen.blit(self.image, self.rect)



    def tank_asteroid_collision(self):
        #get rid of asteroids that collide with tank
        for asteroid in self.asteroids.copy():
            if pygame.Rect.colliderect(self.rect, asteroid.rect):
                if self.health > 25:
                    self.health -= 25  
                    self.asteroids.remove(asteroid)
                    print(self.health)
                else:
                    self._tank_death()
                    self.sb.reset_score()
                    self.game_active = False
                    pygame.mouse.set_visible(True)                  



    def _update_screen(self):
        """ update screen """
        self.screen.fill(self.bg_color)
        self.blitme()
        for bullet in self.bullets.sprites():
            self.bullets.draw(self.screen)
            
        collisions = pygame.sprite.groupcollide(self.bullets, self.asteroids, True, True)
        if collisions:
            for asteroids in collisions.values():
                self.sb.score += 100 * len(asteroids)
            self.sb.prep_score()
            self.sb.check_high_score()
        #draw healthbar if game active
        if self.game_active:
            self.draw_healthbar()
            self.asteroids.draw(self.screen)

        #draw the play button and other buttons if game is paused
        if not self.game_active:
            self.play_button.draw_button()

        self.sb.show_score()

        pygame.display.flip()



    def _tank_death(self):
        sleep(0.5)
        self.bullets.empty()
        self.asteroids.empty()
        self.center_ship()
        self.health = 100       



    def _check_KEYDOWN(self, event):
        """ when key is press either quit, or move direction of arrow pressed and flip image """
        if event.key == pygame.K_q:
            sys.exit()
        elif event.key == pygame.K_RIGHT:
            self.tank_moving_right = True
            self.image = self.direction_right
            self.current_direction = self.direction_right
        elif event.key == pygame.K_LEFT:
            self.tank_moving_left = True
            self.image = self.direction_left
            self.current_direction = self.direction_left
        elif event.key == pygame.K_UP:
            self.tank_moving_up = True
        elif event.key == pygame.K_DOWN:
            self.tank_moving_down = True
        elif event.key == pygame.K_SPACE:
            self._fire_bullet() 
    


    def _check_KEYUP(self, event):
        """ when key is let go stop moving """
        if event.key == pygame.K_RIGHT:
            self.tank_moving_right = False
        elif event.key == pygame.K_LEFT:
            self.tank_moving_left = False
        elif event.key == pygame.K_UP:
            self.tank_moving_up = False
        elif event.key == pygame.K_DOWN:
            self.tank_moving_down = False



    def _fire_bullet(self):
        """ create a bullet and add it to the bullets group """
        if self.current_direction == self.direction_left:
            #create new bullet and set bullet path
            new_bullet = Bullet(self)
            new_bullet.bullet_shootRight = False
            new_bullet.bullet_shootLeft = True

            #change direction of bullet starting point
            new_bullet.x -= 100

            #add bullet to sprite list
            self.bullets.add(new_bullet)

        elif self.current_direction == self.direction_right:
            #create new bullet and set bullet path
            new_bullet = Bullet(self)
            new_bullet.bullet_shootRight = True
            new_bullet.bullet_shootLeft = False
            
            #add bullet to sprite list
            self.bullets.add(new_bullet)    



    def center_ship(self):
        """ centers the ship in middle of screen """
        self.rect.center = self.screen_rect.center
        self.x = float(self.rect.x)
        self.y = float(self.rect.y)



    def _update_tank(self):
        """ move tank or quit game based on keypress """
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                self._check_KEYDOWN(event)
            elif event.type == pygame.KEYUP:
                self._check_KEYUP(event)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = pygame.mouse.get_pos()
                self._check_play_button(mouse_pos)



    def _check_play_button(self, mouse_pos):
        button_clicked = self.play_button.rect.collidepoint(mouse_pos)
        if button_clicked and self.play_button.rect.collidepoint(mouse_pos):
            self.game_active = True
            pygame.mouse.set_visible(False) 



    def _update_asteroids(self):
        """ afer 'x' milliseconds create a new asteroid """
        current_time = pygame.time.get_ticks()
        if current_time > self.next_object_time:
            self.next_object_time += self.time_interval
            self._create_asteroids()        



    def run_game(self):
        """ loops the game/ updates screen/ checks for key clicks"""
        while True:
            self._update_tank()
            if self.game_active:
                self.move()
                self.bullets.update()
                self.tank_asteroid_collision()
                #after certain number of time create a asteroid
                self._update_asteroids()
                self.asteroids.update()

                #get rid of asteroids that have disapeared
                for asteroid in self.asteroids.copy():
                    if asteroid.x >= 1200 or asteroid.x <= 0:
                        self.asteroids.remove(asteroid)
                
                #get rid of bullets that have disapeared
                for bullet in self.bullets.copy():
                    if bullet.rect.left >= 1200 or bullet.rect.right <= 0:
                        self.bullets.remove(bullet)
            self._update_screen()




class Bullet(Sprite):
    """ A class to manage bullets fired from the ship """
    def __init__(self, game):
        """ create a bullet object at the ships current position """
        super().__init__()
        self.screen = game.screen
        self.bullet_speed = 2.0
        self.bullet_width = 20
        self.bullet_height = 5
        self.bullet_color = (0, 200, 200)
        self.bullet_shootRight = False
        self.bullet_shootLeft = False

        self.image = pygame.Surface((self.bullet_width, self.bullet_height))
        self.image.fill(self.bullet_color)
        self.rect = self.image.get_rect()
        self.rect.midright = game.rect.midright
        self.rect.y -= 5 #the tanks barrel is 5 pixels above center
        self.rect.x += 15
    
        #store the bullets position as a decimal value
        self.y = float(self.rect.y)
        self.x = float(self.rect.x)



    def update(self):
        """ move the bullet up the screen """
        #update the decimal position of the bullet.
        if self.bullet_shootRight:
            self.x += self.bullet_speed
            self.rect.x = self.x
        elif self.bullet_shootLeft:
            self.x -= self.bullet_speed
            self.rect.x = self.x




class Asteroid(Sprite):
    """ a class that represents a single asteroid """
    def __init__(self, game):
        """ initialize the asteroid and set its starting position """
        super().__init__()
        self.screen = game.screen
        self.speed = 0.1
        self.direction = 1

        #load the asteroid image onto the screen
        self.image = pygame.image.load('images/asteroid.gif')
        self.rect = self.image.get_rect()

        #start each new asteroid in a random part just outside the map on either the left or right
        self.rect.x = random.randint(*random.choice([(0, 0), (1160, 1160)]))
        self.rect.y = random.randint(0, 760)

        #store the asteroid's exact horizontal positon
        self.x = float(self.rect.x)
        self.y = float(self.rect.y)



    def update(self):
        """ move the asteroid to the right or left """
        self.x += self.speed * self.direction
        self.rect.x = self.x

        self.y += (self.speed / 3) * self.direction
        self.rect.y = self.y




class Button:

    def __init__(self, game, msg):
        """ Initialize button """
        self.screen = game.screen
        self.screen_rect = self.screen.get_rect()

        #set the dimensions and properties of the button
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)

        #Build the button's rect object and center it.
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        self._prep_msg(msg)



    def _prep_msg(self, msg):
        """ Turn msg into a rendered image and center text on the button """
        self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center



    def draw_button(self):
        """ draw blank button then draw message """
        self.screen.fill(self.button_color, self.rect)
        self.screen.blit(self.msg_image, self.msg_image_rect)




class Scoreboard:
    """ A class to report scoring information """

    def __init__(self, game):
        """ initialize scorekeeping attributes """
        self.screen = game.screen
        self.screen_rect = self.screen.get_rect()
        self.score = 0
        self.highscore = 0

        # Font settings for scoring information
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        self.prep_score()
        self.prep_highscore()



    def reset_score(self):
        self.score = 0
        self.prep_score()



    def prep_highscore(self):
        """ Turn the high score into a rendered image. """
        high_score = round(self.highscore, -1)
        high_score_str = "{:,}".format(high_score)
        self.high_score_image = self.font.render(high_score_str, True, self.text_color, (255, 255, 255))

        #center the high score at the top of the screen
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.screen_rect.centerx
        self.high_score_rect.top = self.score_rect.top



    def check_high_score(self):
        """ check to see if there's a new high score """
        if self.score > self.highscore:
            self.highscore = self.score
            self.prep_highscore()

    def prep_score(self):
        """ Turn the score into a renered image """
        rounded_score = round(self.score, -1)

        score_str = "{:,}".format(rounded_score)
        self.score_image = self.font.render(score_str, True, self.text_color, (255, 255, 255))

        #Display the score at the top of the screen
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right
        self.score_rect.top = 0

    def show_score(self):
        """ draw the score to the screen """
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)


if __name__ == '__main__':
    a = Game()
    a.run_game()

Advertisement

Answer

The spawning of the asteroids depends on the next_object_time. next_object_time is initialized with 0. You need to set self.next_object_time when the play button is pressed:

class Game(Sprite):
    # [...]

    def _check_play_button(self, mouse_pos):
        if self.play_button.rect.collidepoint(mouse_pos):
            self.game_active = True
            pygame.mouse.set_visible(False)
 
            self.next_object_time = pygame.time.get_ticks() + self.time_interval

To make the algorithm more robust you can set next_object_time depending on the current time when an asteroid spawns (this is optional):

class Game(Sprite):
    # [...]

    def _update_asteroids(self):
        """ afer 'x' milliseconds create a new asteroid """
        current_time = pygame.time.get_ticks()
        if current_time > self.next_object_time:
 
            # self.next_object_time += self.time_interval
            self.next_object_time = current_time + self.time_interval 
 
            self._create_asteroids()
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement