小能豆

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

py

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

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 时,我也绘制我的计时器,但它不会减少,所以我认为问题出在调用/启动线程倒计时函数的某个地方?

另外,这是我第一次在这里发帖,所以请告诉我该怎么做才能更好地格式化等等,以便你能够提供帮助


阅读 18

收藏
2024-12-24

共1个答案

小能豆

不用为线程烦恼。没必要让你的生活变得复杂。

通常,您会在游戏中使用某种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)

下面我拼凑了一个简单的示例。在那里,我使用一个简单的类,它也是一个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()

q17dn.gif

2024-12-24