如何在 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()
所以,我对编程完全陌生(已经做了几个月)并决定尝试编写游戏代码。 在这一点上,非常感谢 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()