Do something every x (milli)seconds in pygame
Asked Answered
Y

3

8

I'm learing Python and Pygame, and my first thing I'm making is a simple Snake game. I'm trying to make it so that the snake moves once every 0.25 seconds. Here is the part of my code that loops:

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()

I want all of the other function to run normally, but move_snake() to only occur once every 0.25 seconds. I've looked it up and found a few answers but they all seem too complicated for someone who's making their first ever Python script.

Would it be possible to actually get an example of what my code should look like rather than just telling me which function I need to use? Thanks!

Youngs answered 22/9, 2013 at 21:34 Comment(1)
dont use sleep it will stop the whole game instead of just the snakeCrinkly
M
18

There are several approaches, like keeping track of the system time or using a Clock and counting ticks.

But the simplest way is to use the event queue and creating an event every x ms, using pygame.time.set_timer():

pygame.time.set_timer()

repeatedly create an event on the event queue

set_timer(eventid, milliseconds) -> None

Set an event type to appear on the event queue every given number of milliseconds. The first event will not appear until the amount of time has passed.

Every event type can have a separate timer attached to it. It is best to use the value between pygame.USEREVENT and pygame.NUMEVENTS.

To disable the timer for an event, set the milliseconds argument to 0.

Here's a small, running example where the snake moves every 250 ms:

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


Since pygame 2.0.1, it's quite easy to schedule things since pygame.time.set_timer now allows custom events to be fired once.

Here's a simple example of a way to run a function X seconds in the future once:

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()

Minimal example: repl.it@queueseven/Simple-Scheduler

Moir answered 23/9, 2013 at 8:35 Comment(4)
Good example program!Holocaust
Is there a way to have the program read a new command from a command_list every 1250 ms? Move_North ... Move_East ... Move_South ?Holocaust
@Holocaust Sure. You could create some constants like MOVE_EAST = (1, 0) etc, then create the list of commands like command_list = [MOVE_EAST, MOVE_NORTH, ....] and in the event handler do something like dir = command_list.pop(0) to get the next command before the player.move_ip... line.Moir
Sounds good! Could you write a small example program that would illustrate your idea? I'm a noob!Holocaust
P
8

Use the Clock module of Pygame to keep track of time. Specifically the method tick of the Clock class will report to you the number of milliseconds since the last time you called tick. Therefore you can call tick once at the beginning (or at the end) of every iteration in your game loop and store its return value in a variable called dt. Then use dt to update your time-dependent game state variables.

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
Polyamide answered 22/9, 2013 at 21:43 Comment(0)
F
3

If you want to control something over time in Pygame you have two options:

  1. Use pygame.time.get_ticks() to measure time and and implement logic that controls the object depending on the time.

  2. Use the timer event. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. Change object states when the event occurs.

The examples in the answers to the following questions show how to use this:


Minimal example for option 1: PYGBAG demo

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.5 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()

Minimal example for option 2: PYGBAG demo

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.5 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()

Fulton answered 16/7, 2023 at 7:30 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.