在pygame中每x(毫)秒执行某个操作

7

我正在学习Python和Pygame,我的第一个项目是一个简单的贪吃蛇游戏。我试图让贪吃蛇每0.25秒移动一次。下面是我的代码循环部分:

while True:
    check_for_quit()

    clear_screen()

    draw_snake()
    draw_food()

    check_for_direction_change()

    move_snake() #How do I make it so that this loop runs at normal speed, but move_snake() only executes once every 0.25 seconds?

    pygame.display.update()

我希望所有其他功能都能正常运行,但是move_snake()只发生一次,每0.25秒。我查了一些资料,找到了一些答案,但对于第一次编写Python脚本的人来说,它们似乎都太复杂了。
有没有可能真正得到一个示例,展示我的代码应该是什么样子的,而不仅仅是告诉我需要使用哪个函数?谢谢!

不要使用 sleep,这会停止整个游戏,而不仅仅是蛇。 - Serial
3个回答

14
有几种方法可以实现,比如跟踪系统时间或使用一个Clock并计算滴答数。
但最简单的方法是使用事件队列,并在每个x毫秒创建一个事件,使用pygame.time.set_timer()函数:

pygame.time.set_timer()

重复在事件队列上创建一个事件

set_timer(eventid, milliseconds) -> None

设置一个事件类型,使其每隔一定的毫秒数出现在事件队列中。直到经过了指定的时间量,第一个事件才会出现。

每个事件类型都可以附加一个单独的计时器。最好使用介于pygame.USEREVENT和pygame.NUMEVENTS之间的值。

要禁用事件的计时器,请将milliseconds参数设置为0。

下面是一个小的运行示例,蛇每250毫秒移动一次:

import pygame
pygame.init()
screen = pygame.display.set_mode((300, 300))
player, dir, size = pygame.Rect(100,100,20,20), (0, 0), 20
MOVEEVENT, t, trail = pygame.USEREVENT+1, 250, []
pygame.time.set_timer(MOVEEVENT, t)
while True:
    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]: dir = 0, -1
    if keys[pygame.K_a]: dir = -1, 0
    if keys[pygame.K_s]: dir = 0, 1
    if keys[pygame.K_d]: dir = 1, 0
    
    if pygame.event.get(pygame.QUIT): break
    for e in pygame.event.get():
        if e.type == MOVEEVENT: # is called every 't' milliseconds
            trail.append(player.inflate((-10, -10)))
            trail = trail[-5:]
            player.move_ip(*[v*size for v in dir])
            
    screen.fill((0,120,0))
    for t in trail:
        pygame.draw.rect(screen, (255,0,0), t)
    pygame.draw.rect(screen, (255,0,0), player)
    pygame.display.flip()

enter image description here


自从pygame 2.0.1版本以来,使用pygame.time.set_timer非常容易安排事务,因为现在可以触发自定义事件一次。
下面是一个简单的示例,展示了如何在未来X秒内运行一个函数:
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
ACTION = pygame.event.custom_type() # our custom event that contains an action
clock = pygame.time.Clock()


def main():
    shield = 0

    # function to activate the shield
    # note that shield is NOT just a boolean flag,
    # but an integer. Everytime we activate the shield,
    # we increment it by 1. If we want to deactivate the 
    # shield later, we can test if the deactivate event
    # is the latest one. If not, then nothing should happen.
    def activate_shield():
        nonlocal shield
        shield += 1
        return shield

    # function that creates the function that is triggered
    # after some time. It's nested so we can pass and capture
    # the id variable to check if the event is actually the
    # last deactivate event. If so, we reset shield to 0.
    def remove_shield(id):
        def action():
            nonlocal shield
            if shield == id:
                shield = 0
        return action

    while True:
        for e in pygame.event.get():
            if e.type == pygame.QUIT: return
            
            if e.type == pygame.KEYDOWN: 
                # activate the shield
                id = activate_shield()
                # deactivate it 2000ms in the future
                # pygame will post this event into the event queue at the right time
                pygame.time.set_timer(pygame.event.Event(ACTION, action=remove_shield(id)), 2000, 1)
                
            if e.type == ACTION:
                # if there's an ACTION event, invoke its action!!!
                e.action()
                
        screen.fill('black')
          
        # is the shield active?
        if shield:
            pygame.draw.circle(screen, 'red', (320, 220), 35, 4)
            
        # our green little guy
        pygame.draw.rect(screen, 'green', (300, 200, 40, 40))
        pygame.display.flip()
        clock.tick(60)

main()

最简单的例子: repl.it@queueseven/Simple-Scheduler


好的示例程序! - user1839239
有没有办法让程序每1250毫秒从命令列表中读取一个新的命令? 向北移动...向东移动...向南移动? - user1839239
@user1839239 当然可以。您可以创建一些常量,例如 MOVE_EAST = (1, 0) 等,然后创建命令列表,例如 command_list = [MOVE_EAST, MOVE_NORTH, ....],在事件处理程序中执行 dir = command_list.pop(0) 来获取下一个命令,然后再执行 player.move_ip... 行。 - sloth
好的!你能写一个小示例程序来说明你的想法吗?我是新手! - user1839239

6
使用Pygame的时钟模块来跟踪时间。具体来说,Clock类的方法tick将向您报告自上次调用tick以来的毫秒数。因此,您可以在游戏循环的每个迭代的开头(或结尾)调用tick一次,并将其返回值存储在名为dt的变量中。然后使用dt来更新与时间有关的游戏状态变量。
time_elapsed_since_last_action = 0
clock = pygame.time.Clock()

while True: # game loop
    # the following method returns the time since its last call in milliseconds
    # it is good practice to store it in a variable called 'dt'
    dt = clock.tick() 

    time_elapsed_since_last_action += dt
    # dt is measured in milliseconds, therefore 250 ms = 0.25 seconds
    if time_elapsed_since_last_action > 250:
        snake.action() # move the snake here
        time_elapsed_since_last_action = 0 # reset it to 0 so you can count again

1

如果你想在Pygame中控制时间,有两个选项:

  1. 使用pygame.time.get_ticks()来测量时间,并根据时间来控制对象的逻辑。

  2. 使用定时器事件。使用pygame.time.set_timer()重复创建一个USEREVENT放入事件队列中。当事件发生时改变对象状态。

以下问题的答案示例展示了如何使用这些方法:


选项1的最小示例:
import pygame

pygame.init()
window = pygame.display.set_mode((400, 200))
clock = pygame.time.Clock()
rect = pygame.Rect(0, 80, 20, 20)

time_interval = 500 # 500 milliseconds == 0.1 seconds
next_step_time = 0

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    current_time = pygame.time.get_ticks()
    if current_time > next_step_time:
        next_step_time += time_interval
        rect.x = (rect.x + 20) % window.get_width()

    window.fill(0)
    pygame.draw.rect(window, (255, 0, 0), rect)
    pygame.display.flip()
    clock.tick(100)

pygame.quit()
exit()

选项2的最简示例:
import pygame

pygame.init()
window = pygame.display.set_mode((400, 200))
clock = pygame.time.Clock()
rect = pygame.Rect(0, 80, 20, 20)

time_interval = 500 # 500 milliseconds == 0.1 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval) 

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

        if event.type == timer_event:
            rect.x = (rect.x + 20) % window.get_width()

    window.fill(0)
    pygame.draw.rect(window, (255, 0, 0), rect)
    pygame.display.flip()
    clock.tick(100)

pygame.quit()
exit()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接