如何在 pygame 中制作波浪计时器

How to make a wave timer in pygame

所以,我对编程完全陌生(已经做了几个月)并决定尝试编写游戏代码。 在这一点上,非常感谢 Chris Bradfield 提供的 pygame 编码系列教程,它们绝对很棒! 但是,既然我已经完成了教程并且需要自己动手,我遇到了一个问题。我正在制作一个自上而下的射击游戏并使其基于波浪。所以,当一波僵尸死亡时,我想展示一个倒计时直到下一波开始的计时器。我认为我在正确的道路上 atm,让我向您展示我正在使用的东西。

def new(self)
'''
    self.timer_flag = False
    self.x = threading.Thread(target=self.countdown, args=(TIME_BETWEEN_WAVES,))
'''

def countdown(self, time_between_waves):
    self.wave_timer = time_between_waves
    for i in range(TIME_BETWEEN_WAVES):
        while self.timer_flag:
            self.wave_timer -= 
            time.sleep(1)

def update(self)
'''
    self.countdown_has_run = False
    if len(self.mobs) == 0:
        self.timer_flag = True
        if not self.countdown_has_run:
            self.countdown_has_run = True
            self.x.start()
'''

现在,当 timer_flag 为 True 时,我也绘制了我的计时器,但它没有递减,所以我认为问题出在 calling/starting 线程倒计时函数中?

此外,这是我第一次在这里发帖,所以请让我知道如何做才能更好地格式化等,以便您能够提供帮助

在我看来,您缺少检查屏幕上剩余多少生物的机制。我想它可能是这样的:

class CountdownClock:
    def __init__(self):
        self.start_no = 1
        self.time_between_waves = 5
        self.t = threading.Thread(target=self.check_mobs_left)
        self.t.start()

    def check_mobs_left(self):
        self.mobs = ["mob" for _ in range(randint(2, 7))]  #generate 2-7 mobs per level
        print (f"Wave {self.start_no} : Total {len(self.mobs)} mobs found!")
        while self.mobs:
            print (f"Still {len(self.mobs)} mobs left!")
            time.sleep(1)
            del self.mobs[-1] #simulate mob kill - remove this line from your actual setting
        self.next_wave(self.time_between_waves)
        self.time_between_waves +=2 #increased time for each wave

    def next_wave(self,time_between_waves):
        self.time_left = time_between_waves
        print(f"Wave {self.start_no} cleared!")
        self.start_no += 1
        while self.time_left:
            print (f"Next wave in...{self.time_left}")
            self.time_left -=1
            time.sleep(1)
        self.t = threading.Thread(target=self.check_mobs_left)
        self.t.start()

a = CountdownClock()

您将不断进行此操作,而无需每一轮都调用该方法并设置标志和内容。

不要为线程而烦恼。不需要让你的生活变得复杂。

通常,您无论如何都会在游戏中使用 Clock(如果没有,您应该开始使用它)来限制帧率,并确保您的世界以恒定的速度移动(如果没有,您应该开始这样做了)。

因此,如果您想在 5 秒内触发某些东西,只需创建一个变量来保存值 5000,然后减去处理最后一帧所花费的时间(由 Clock 返回.tick):

clock = pygame.time.Clock()
dt = 0
timer = 5000
while True:
    ...
    timer -= dt
    if timer <= 0:
       do_something()
    dt = clock.tick(60)

我在下面拼凑了一个简单的例子。在那里,我使用一个简单的 class 也是一个 Sprite 将剩余时间绘制到屏幕上。当计时器用完时,它会调用一个函数来创建新一波僵尸。

在主循环中,我检查是否没有计时器 运行 和僵尸,如果是,则创建一个新计时器。

代码如下:

import pygame
import pygame.freetype
import random

# a dict that defines the controls
# w moves up, s moves down etc
CONTROLS = {
    pygame.K_w: ( 0, -1),
    pygame.K_s: ( 0,  1),
    pygame.K_a: (-1,  0),
    pygame.K_d: ( 1,  0)
}

# a function that handles the behaviour a sprite that
# should be controled with the keys defined in CONTROLS
def keyboard_controlled_b(player, events, dt):

    # let's see which keys are pressed, and create a 
    # movement vector from all pressed keys.
    move = pygame.Vector2()
    pressed = pygame.key.get_pressed()

    for vec in (CONTROLS[k] for k in CONTROLS if pressed[k]):
        move += vec

    if move.length():
        move.normalize_ip()

    move *= (player.speed * dt/10)

    # apply the movement vector to the position of the player sprite
    player.pos += move
    player.rect.center = player.pos

# a function that let's a sprite follow another one
# and kill it if they touch each other
def zombie_runs_to_target_b(target):
    def zombie_b(zombie, events, dt):

        if target.rect.colliderect(zombie.rect):
            zombie.kill()
            return

        move = target.pos - zombie.pos

        if move.length():
            move.normalize_ip()

        move *= (zombie.speed * dt/10)
        zombie.pos += move
        zombie.rect.center = zombie.pos

    return zombie_b

# a simple generic sprite class that displays a simple, colored rect
# and invokes the given behaviour
class Actor(pygame.sprite.Sprite):

    def __init__(self, color, pos, size, behavior, speed, *grps):
        super().__init__(*grps)
        self.image = pygame.Surface(size)
        self.image.fill(color)
        self.rect = self.image.get_rect(center=pos)
        self.pos = pygame.Vector2(pos)
        self.behavior = behavior
        self.speed = speed

    def update(self, events, dt):
        self.behavior(self, events, dt)

# a sprite class that displays a timer
# when the timer runs out, a function is invoked
# and this sprite is killed
class WaveCounter(pygame.sprite.Sprite):

    font = None

    def __init__(self, time_until, action, *grps):
        super().__init__(grps)
        self.image = pygame.Surface((300, 50))
        self.image.fill((3,2,1))
        self.image.set_colorkey((3, 2, 1))
        self.rect = self.image.get_rect(topleft=(10, 10))

        if not WaveCounter.font:
            WaveCounter.font = pygame.freetype.SysFont(None, 32)

        WaveCounter.font.render_to(self.image, (0, 0), f'new wave in {time_until}', (255, 255, 255))
        self.timer = time_until * 1000
        self.action = action

    def update(self, events, dt):
        self.timer -= dt

        self.image.fill((3,2,1))
        WaveCounter.font.render_to(self.image, (0, 0), f'new wave in {int(self.timer / 1000) + 1}', (255, 255, 255))

        if self.timer <= 0:
            self.action()
            self.kill()

def main():
    pygame.init()
    screen = pygame.display.set_mode((600, 480))
    screen_rect = screen.get_rect()
    clock = pygame.time.Clock()
    dt = 0
    sprites_grp = pygame.sprite.Group()
    zombies_grp = pygame.sprite.Group()
    wave_tm_grp = pygame.sprite.GroupSingle()

    # the player is controlled with the keyboard
    player = Actor(pygame.Color('dodgerblue'), 
                   screen_rect.center, 
                   (32, 32), 
                   keyboard_controlled_b, 
                   5, 
                   sprites_grp)

    # this function should be invoked once the timer runs out
    def create_new_wave_func():
        # let's create a bunch of zombies that follow the player
        for _ in range(15):
            x = random.randint(0, screen_rect.width)
            y = random.randint(-100, 0)
            Actor((random.randint(180, 255), 0, 0), 
                  (x, y), 
                  (26, 26), 
                  zombie_runs_to_target_b(player), 
                  random.randint(2, 4), 
                  sprites_grp, zombies_grp)

    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        # no timer, no zombies => create new timer
        if len(wave_tm_grp) == 0 and len(zombies_grp) == 0:
            WaveCounter(5, create_new_wave_func, sprites_grp, wave_tm_grp)

        sprites_grp.update(events, dt)

        screen.fill((80, 80, 80))
        sprites_grp.draw(screen)
        pygame.display.flip()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()