Search code examples
pythonpython-3.xtimerpygamegame-engine

Problems with time and the appearance of enemies (Python, Pygame)


I post this problem a second time, but with more information. My problem is with my code, for some reason a few lines do not work. I am making a tile game in python using the pygame library. If you run the code, you will see that on the screen 1 red square will fly from top to bottom endlessly. The red square is the enemy. I need that after 10 seconds it’s not 1 square that flies, but 2. The MobY class is responsible for this, which generates enemies, and in the Game class in the new() method there is a cycle that creates red squares:

for i in range(self.mobY):
    m = MobY()
    self.all_sprites.add(m)
    self.mobs.add(m)

Variable self.mobY which is stored in __init__ is equal to 1, that is, at the same time there can be only 1 red square on the screen. There is also a timer on the screen, the seconds of which are stored in a variable self.seconds that is in the method time().
So that after 10 seconds 2 red squares fly across the screen, in theory you need to put these lines of code in the new() method:

if self.seconds >= 10:
    self.mobY += 1

But I get an error "'Game' object has no attribute 'seconds'", although it is not.
In general, I need that after 10 seconds there will be 1 more squares on the screen.
Here is the code:

import sys
import pygame as pg
import random

#         R    G    B
DARKGREY = (40, 40, 40)
LIGHTGREY = (43, 43, 43)
RED = (255, 0, 0)

WIDTH = 1008
HEIGHT = 768
FPS = 60
TITLE = "TITLE GAME"
BGCOLOR = DARKGREY
TILESIZE = 48


class MobY(pg.sprite.Sprite):
    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((TILESIZE, TILESIZE))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.x = random.randrange(0, WIDTH, TILESIZE)
        self.rect.y = random.randrange(-100, -40)
        self.speedy = random.randrange(6, 9)

    def update(self):
        self.rect.y += self.speedy
        if self.rect.top > HEIGHT + TILESIZE:
            self.rect.x = random.randrange(0, WIDTH, TILESIZE)
            self.rect.y = random.randrange(-100, -40)
            self.speedy = random.randrange(6, 9)


class Game:
    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        pg.key.set_repeat(1, 15)
        self.frame_count = 0
        self.mobY = 1

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.mobs = pg.sprite.Group()

        for i in range(self.mobY):
            m = MobY()
            self.all_sprites.add(m)
            self.mobs.add(m)

    def run(self):
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        self.all_sprites.update()

    def time(self):
        self.font = pg.font.Font(None, 50)
        self.font_color = pg.Color('springgreen')
        self.total_seconds = self.frame_count // FPS
        self.seconds = self.total_seconds % 6000
        self.output_string = "TIME: {0}".format(self.seconds)
        self.text = self.font.render(self.output_string, True, self.font_color)
        self.screen.blit(self.text, [10, 10])
        self.frame_count += 1

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def draw(self):
        self.screen.fill(BGCOLOR)
        self.draw_grid()
        self.all_sprites.draw(self.screen)
        self.time()
        pg.display.flip()

    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()


g = Game()
while True:
    g.new()
    g.run()

Solution

  • But I get an error "'Game' object has no attribute 'seconds'"

    Yes of course, because self.seconds is defined in the method time rather than the constructor of the class Game. When new is invoked the 1st time, then the attribute self.seconds doe not exist, because time hasn't been invoked before.

    Anyway you approach won't work because, the condition if self.seconds >= 10: si fulfilled continuously and will cause that self.mobY is incremented many times.


    Use pg.time.get_ticks() to the number of milliseconds since pygame.init() was called.
    Define the time when the first MobY has to be created self.next_mob_time. when the time point has exceeded, then create an instance of MobY and increment the time by 10000.

    current_time = pg.time.get_ticks()
    if current_time > self.next_mob_time:
        self.next_mob_time += 10000
        self.mobY += 1
    

    Invoke new in time. Furthermore the groups have to be created in the constructor of Game rather than in new:

    class Game:
        def __init__(self):
            pg.init()
            self.screen = pg.display.set_mode((WIDTH, HEIGHT))
            pg.display.set_caption(TITLE)
            self.clock = pg.time.Clock()
            pg.key.set_repeat(1, 15)
            self.frame_count = 0
            self.mobY = 0
            self.next_mob_time = 0
            self.all_sprites = pg.sprite.Group()
            self.mobs = pg.sprite.Group()
    
        def new(self):
            current_time = pg.time.get_ticks()
            if current_time > self.next_mob_time:
                self.next_mob_time += 10000
                self.mobY += 1
                m = MobY()
                self.all_sprites.add(m)
                self.mobs.add(m)
    
        # [...]
    
        def time(self):
            self.new()
            self.seconds = pg.time.get_ticks() // 1000
    
            # [...]