Ryan_C
Ryan_C

Reputation: 21

How to have multiple timers in an event for different objects in Pygame?

So i'm creating a bullet-hell style game in Pygame inspired by Touhou, involving the player firing bullets in multiple different directions and at different fire rates. I've managed to borrow some code which allowed me to be able to implement the rapid fire shooting of a bullet, and then added onto that by creating multiple different bullet objects for a spread-shot. However, although i would like to be able to adjust the reload speed for each individual stream of bullet objects, i'm honestly stumped at how. Can anyone help me out here?

Here is the code involved for the bullets to shoot as intended, all with the same reload speed:

reloadSpeed = 50
shootingEvent = pygame.USEREVENT + 1
reloaded = True

(later on in the while loop):

if key[pygame.K_z]:
        if reloaded:
            bulletSound.stop()
            player.shoot()
            bulletSound.play()
            reloaded = False
            pygame.time.set_timer(shootingEvent, reloadSpeed)

Here is the shoot function in my Player class, and the Bullet class for context:

def shoot(self):
        bullet1N = Bullet(self.rect.centerx, self.rect.top, -4, -10, lb)
        bulletCentreN = Bullet(self.rect.centerx, self.rect.top, 0, -10,db)
        bullet3N = Bullet(self.rect.centerx, self.rect.top, 4, -10, lb)
        bullet1N2 = Bullet(self.rect.centerx, self.rect.top, -2, -10, db)
        bullet2N2 = Bullet(self.rect.centerx, self.rect.top, -1, -10, db)
        bullet3N2 = Bullet(self.rect.centerx, self.rect.top, 1, -10, db)
        bullet4N2 = Bullet(self.rect.centerx, self.rect.top, 2, -10, db)

(etc, etc, a lot of different types of bullets. The first number represents the number stream in the respective 'power' threshold, as i want different bullet streams as the power increases throughout the game; 'N' or 'S' represents whether the stream is for during normal fire or while holding shift, and the second number represents the power level this stream is used for.)

        keystate = pygame.key.get_pressed()
        if power < 16:
            if keystate[pygame.K_LSHIFT]:
                Group(bulletCentreS)

            else:
                Group(bullet1N)
                Group(bulletCentreN)
                Group(bullet3N)

        if power >= 16 and power < 48:
            if keystate[pygame.K_LSHIFT]:
                Group(bullet1S2)
                Group(bullet2S2)


            else:
                Group(bullet1N2)
                Group(bullet2N2)
                Group(bullet3N2)
                Group(bullet4N2)

(Group is just a function to add the bullets to a few sprite groups slightly more efficiently)

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, speedx, speedy, col):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((6, 6))
        self.image.set_alpha(100)
        self.image.fill(col)
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.speedy = speedy
        self.speedx = speedx

    def update(self):
        self.rect.y += self.speedy
        self.rect.x += self.speedx
        # kill if it moves off the top of the screen
        if self.rect.bottom < 25:
            self.kill()
        if self.rect.x > WIDTH/2:
            self.kill()

As shown this results in every bullet of any pattern all firing at the same rate, albeit at different movement speeds in some occasions.

Edit: So thanks to Kingsley I have come to realise that I would somehow need to implement the shooting function into my Bullet class such that the fire rate of different streams can be adjusted through each Bullet object having a firerate attribute... but how would I implement this?

Upvotes: 2

Views: 795

Answers (1)

Kingsley
Kingsley

Reputation: 14906

A simple technique is to record the real-time of the "firing" event, and not allow further actions of that type until a time-delay is expired.

An easy way to get a usable timestamp is the PyGame function pygame.time.getticks(), which returns the ever-increasing number of milliseconds since startup.

Whenever the user actuates the firing mechanism, store the time.

fire_time_type1 = pygame.time.getticks()  # time of last shot

Also decide what the firing rate delay should be, for example:

RELOAD_DELAY_TYPE1 = 300   # milliseconds

Modify the user interface such that the firing procedure only completes after the delay has elapsed:

now = pygame.time.getticks()
if ( now > fire_time_type1 + RELOAD_DELAY_TYPE1 ):
    fire_weapon_type1()
    fire_time_type1 = now
else:
    # still waiting for re-load time delay
    # play weapon-too-hot sound, etc.
    pass

Obviously this mechanism can be worked into classes so the delay happens within the object, rather than externally like this example.

This method is easily implemented for different delays by simply storing more use-time records.

EDIT:

Perhaps a triggering class could maintain the state for the different types of bullets.

### Gun object encapsulates enough detail to track the number of shots
### from a given magazine size, enforcing a reload-delay once the
### magazine is empty
class Gun:
    def __init__( self, name, magazine_size, reload_delay=300 ):
        self.name     = name
        self.shots    = 0
        self.mag_size = magazine_size
        self.reload   = reload_delay    # milliseconds
        self.use_time = 0

    def hasBullet( self ):
       """ Check that either a bullet is available, or that the
           reload delay has expired """
       now = pygame.time.getticks()
       if ( self.shots >= self.mag_size and now > self.use_time + self.reload ):
           self.shots = 0    # reload
           print( "Debug: [%s] reloaded after %d millisecs" % (self.name, self.reload ) )
       return ( self.shots < self.mag_size )

    def trackFiring( self ):
        """ keep track of the magazine limit, and when the shot was fired """
        self.shots    += 1
        self.use_time  = pygame.time.getticks()  # time of last shot             
        print( "Debug: [%s] fired, %d rounds left" % (self.name, self.mag_size - self.shots ) )



### ...

nail_gun = Gun( 'nail', 200, 500 )  
rockets  = Gun( 'rocket', 3, 1000 )
bfg      = Gun( 'BFG (oh yes!)', 1, 5000 )

### ...

# Player activates rocket launcher~
if ( rockets.hasBullet() ):
    rockets.trackFiring()
    shoot( ... )

Obviously the bullet shooting could be worked into the Gun class, but this is left as an exercise for the reader.

NOTE: I have not test-compiled this code, minor errors and omissions expected.

Upvotes: 2

Related Questions