tomasw
tomasw

Reputation: 11

Pygame: How to wait a certain amount of time to do something after a method has been called

I started learning python around 6 weeks ago and started playing around with pygame around 2 weeks ago. I'm working my way through a good book (Python Crash Course) and for an exercise am making a simple game.

On the left hand side of the screen I have a picture of a bow and arrow, that can be moved up and down and 'shot' by pressing the space bar. When the arrow is shot, the image changes to a non-drawn bow without the arrow so it kind of looks like the bow's string has been released.

I have this all working so far. What I want though is for the bow to be reloaded a certain amount of time after the arrow has been fired. So the player presses space, the arrow is shot, then a second or so later the bow is reloaded and the player can shoot again.

In particular, after the fire_bow() method has been called, after 2 seconds I want the bow_drawn flag to be set back to True.

Does anyone know how I could do this? I've read the documentation but I can't seem to find anything that would give me the desired effect. In particular I don't want to pause the program. Ideally I am imagining a method that says 'wait 1 second'.

Here's my code so far if it would be useful.

import sys

import pygame

from target_practise_settings import Settings
from bow_and_arrow import Bow


class TargetPractise:
    """ Overall class to manage game assets and behaviour. """

    def __init__(self):
        """ Overall class to manage game assets and behavior. """
        pygame.init()
        self.settings = Settings()

        self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
        self.screen_width = self.screen.get_rect().width
        self.screen_height = self.screen.get_rect().height
        pygame.display.set_caption("Target Practise")

        self.bow = Bow(self)

    def run_game(self):
        """ Start the main loop for the game. """
        while True:
            # Watch for keyboard and mouse events.
            self._check_events()

            self.bow.update()

            self._update_screen()

    def _check_events(self):
        """ Respond to keypresses and mouse events. """
        for event in pygame.event.get():

            if event.type == pygame.QUIT:
                sys.exit()

            elif event.type == pygame.KEYDOWN:
                self._check_keydown_events(event)

            elif event.type == pygame.KEYUP:
                self._check_keyup_events(event)

    def _check_keydown_events(self, event):
        """ Respond to keypresses """
        if event.key == pygame.K_UP:
            self.bow.moving_up = True
        elif event.key == pygame.K_DOWN:
            self.bow.moving_down = True
        elif event.key == pygame.K_q:
            sys.exit()
        elif event.key == pygame.K_SPACE:
            self.bow.fire_bow()

    def _check_keyup_events(self, event):
        """ Respond to key releases """
        if event.key == pygame.K_UP:
            self.bow.moving_up = False
        if event.key == pygame.K_DOWN:
            self.bow.moving_down = False

    def _update_screen(self):
        """ Update images on the screen, and flip to the new screen. """
        # Redraw the screen during each pass through the loop.
        self.screen.fill(self.settings.bg_colour)
        self.bow.blitme()

        # Make the most recently drawn screen visible.
        pygame.display.flip()


if __name__ == '__main__':
    # Make a game instance, and run the game.
    tp = TargetPractise()
    tp.run_game()

and the bow class.

import pygame


class Bow:
    """ A class to manage the bow. """

    def __init__(self, tp_game):
        """ Initialise the bow and its starting position """
        self.screen = tp_game.screen
        self.settings = tp_game.settings
        self.screen_rect = tp_game.screen.get_rect()

        # Load the bow and arrow image
        self.bow_arrow_image = pygame.image.load(
                            'images/FINAL/bow_and_arrow_rotcrop_resize.bmp')

        # Load the bow (no arrow) image and get its rect
        self.bow_image = pygame.image.load(
                                'images/FINAL/bow_no_arrow_rotcrop_resize.bmp')

        self.rect = self.bow_arrow_image.get_rect()

        # Start each new bow at the centre right of the screen
        self.rect.midleft = self.screen_rect.midleft

        # Store a decimal value for the ship's vertical position
        self.y = float(self.rect.y)

        # Movement flags
        self.moving_up = False
        self.moving_down = False

        # Bow drawn flag
        self.bow_drawn = True

    def fire_bow(self):
        self.bow_drawn = False

    def update(self):
        """ Update the bow's position based on the movement flags. """
        # Update the bow's y value, not the rect
        if self.moving_up:
            self.y -= self.settings.bow_speed
        if self.moving_down:
            self.y += self.settings.bow_speed

        # Update rect object from self.y
        self.rect.y = self.y

    def blitme(self):
        """
        Draw the bow at its current location.
        Bow will be drawn depending on the bow_drawn flag
        """
        if self.bow_drawn:
            self.screen.blit(self.bow_arrow_image, self.rect)
        elif not self.bow_drawn:
            self.screen.blit(self.bow_image, (self.rect.x + 75, self.rect.y))

and finally the settings.

class Settings:
    """ A class to store all settings for Target Practise """

    def __init__(self):
        """ Initialise the game's settings. """
        # Screen Settings
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_colour = (255, 255, 255)

        # Bow Settings
        self.bow_speed = 1

Bow with arrow

Bow without arrow

Thanks a lot for reading this!

Upvotes: 1

Views: 2958

Answers (3)

Glenn Mackintosh
Glenn Mackintosh

Reputation: 2779

I would recommend using pygame.time.set_timer() and pygames event mechanics. The docs on it are here.

I have an answer to a similar question here, but will include enough info here to help with this specific situation.

You set a timer to expire after a certain time. When it does, it triggers an event that you can look for in your event loop. That allows you to add it into your existing event mechanics pretty simply.

After firing the bow you would add this and it starts a timer that will trigger an event after 2 seconds have elapsed:

    pygame.time.set_timer(pygame.USEREVENT, 2000)

To catch that, in your event loop inside _check_events() you would have something like:

            if event.type == pygame.USEREVENT:
                # Timer expired. Bow can be fired again
                self.bow.pull_bow()

In the bow class you would have to add that pull_bow() call, something like:

class Bow:
    ...
    def pull_bow(self):
        self.bow_drawn = True

Upvotes: 2

billschmidt626
billschmidt626

Reputation: 121

You have to add a timer variable. This is simply and integer that counts down from a certain number after you release the bow. When the timer variable reaches 0, then you know to allow firing again.

First add this to the init function:

self.bow_fire_wait = 100
self.bow_fire_timer = 0

Then modify the fire_bow function tot this:

def fire_bow(self):
    if(self.bow_fire_timer < 0):
        self.bow_drawn = False
        self.bow_fire_timer = self.bow_fire_wait

Finally add this line to the end of the blitme function:

self.bow_fire_timer -= 1

You can make "self.bow_fire_wait" larger to increase the wait time between shots. The value in there represents the number of frames to wait. So if you're doing 60 fps, self.bow_fire_wait = 60 will wait 1 second.

Upvotes: 0

ernToTo
ernToTo

Reputation: 96

If you don't want to pause the only thread you are using. You should probably use threading.

Python Time Delays

Try something like this:

from threading import Timer

def bow():
    bow_drawn = True

t = Timer(2.0, bow)
t.start() # after 2 seconds, function "bow" will run

Upvotes: 1

Related Questions