5

I'm making an arcade game using pygame and I'm trying to have a sprite change positions every few seconds.

I've tried using time.sleep(1) and changing the frame rate to .5 (clock.tick(.5)).

Both worked to make the object change position only after the time interval has passed, however they also make the sprite following my mouse update coordinates at the same rate.

I've been researching and can't seem to find another way to make the sprite move without making my program refresh slower or 'sleep' every time it runs.

sloth
  • 99,095
  • 21
  • 171
  • 219
user3525120
  • 63
  • 1
  • 1
  • 3
  • you could just skip updates for the sprite and could the skips (within the sprite). After 5 skips of so move the sprite and reset that counter. But I think a really (game-)time based aproach would be better. – kratenko Apr 29 '14 at 15:32

2 Answers2

10

You can use an Event for this together with 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


Here's a simple, complete example. Note how the enemies move every 1000ms sideways, every 3500ms downwards, and you can shoot every 450ms (all using events).

enter image description here


import pygame

# you'll be able to shoot every 450ms
RELOAD_SPEED = 450

# the foes move every 1000ms sideways and every 3500ms down
MOVE_SIDE = 1000
MOVE_DOWN = 3500

screen = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()

pygame.display.set_caption("Micro Invader")

# create a bunch of events 
move_side_event = pygame.USEREVENT + 1
move_down_event = pygame.USEREVENT + 2
reloaded_event  = pygame.USEREVENT + 3

move_left, reloaded = True, True

invaders, colors, shots = [], [] ,[]
for x in range(15, 300, 15):
    for y in range(10, 100, 15):
        invaders.append(pygame.Rect(x, y, 7, 7))
        colors.append(((x * 0.7) % 256, (y * 2.4) % 256))

# set timer for the movement events
pygame.time.set_timer(move_side_event, MOVE_SIDE)
pygame.time.set_timer(move_down_event, MOVE_DOWN)

player = pygame.Rect(150, 180, 10, 7)

while True:
    clock.tick(40)
    if pygame.event.get(pygame.QUIT): break
    for e in pygame.event.get():
        if e.type == move_side_event:
            for invader in invaders:
                invader.move_ip((-10 if move_left else 10, 0))
            move_left = not move_left
        elif e.type == move_down_event:
            for invader in invaders:
                invader.move_ip(0, 10)
        elif e.type == reloaded_event:
            # when the reload timer runs out, reset it
            reloaded = True
            pygame.time.set_timer(reloaded_event, 0)

    for shot in shots[:]:
        shot.move_ip((0, -4))
        if not screen.get_rect().contains(shot):
            shots.remove(shot)
        else:
            hit = False
            for invader in invaders[:]:
                if invader.colliderect(shot):
                    hit = True
                    i = invaders.index(invader)
                    del colors[i]
                    del invaders[i]
            if hit:
                shots.remove(shot)

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_LEFT]: player.move_ip((-4, 0))
    if pressed[pygame.K_RIGHT]: player.move_ip((4, 0))

    if pressed[pygame.K_SPACE]: 
        if reloaded:
            shots.append(player.copy())
            reloaded = False
            # when shooting, create a timeout of RELOAD_SPEED
            pygame.time.set_timer(reloaded_event, RELOAD_SPEED)

    player.clamp_ip(screen.get_rect())

    screen.fill((0, 0, 0))

    for invader, (a, b) in zip(invaders, colors): 
        pygame.draw.rect(screen, (150, a, b), invader)

    for shot in shots: 
        pygame.draw.rect(screen, (255, 180, 0), shot)

    pygame.draw.rect(screen, (180, 180, 180), player)    
    pygame.display.flip()
sloth
  • 99,095
  • 21
  • 171
  • 219
  • I understand all of this apart from the line `player.clamp_ip(screen.get_rect())` - could you explain, or is there a good explanation of why to use this? – marienbad Oct 27 '18 at 10:51
  • @marienbad If the Rect player is outside the Rect of the screen (e.g. the x coordinate is negative or the x coordinate + the width is greater than the screen width), the clamp function will "move it back" so it's inside the screen. Most functions of the Rect class will return a new Rect, but they have an _ip (in-place) version that will mutate the original Rect instead. So this line basically just ensures the player can't move outside the screen. – sloth Oct 27 '18 at 16:43
  • Thanks, I get it now! – marienbad Oct 27 '18 at 23:26
  • @marienbad I get this is an old question but I'm still interested. Can you explain the for loops? basically everything inside this line ```for x in range(15, 300, 15):```. Why are there 3 values inside the range function and what do they do? – d'arby May 09 '20 at 08:12
  • `for x in range(15, 300, 15)` loops from 15 to 300 in steps of 15, like 15, 30, 45, 60, 75, 90 etc. – sloth May 09 '20 at 10:22
1

How about

var = 0

while True:
    event_handling()

    game_logic()

    if var == 5:
        sprite.update.position()
        var = 0

    pygame.display.flip()
    var += 1

Obviously, this is just pseudo code, but you get the idea.

DJMcMayhem
  • 7,285
  • 4
  • 41
  • 61
  • Forgot to mention, I tried that as well. It makes it change with a good time interval, but it only displays the sprite for a split second. – user3525120 Apr 29 '14 at 18:48
  • Which makes sense. With this code it's displaying it once, when it gets to 60 (since I have it at 60 fps so the mouse sprite moves at a decent speed) at the rate of 60 fps. – user3525120 Apr 29 '14 at 19:00
  • Ok, then you need to blit the sprite at the end of every loop, but only update the position if var == 5. or 60, or whatever number you end up using. – DJMcMayhem Apr 30 '14 at 17:41