A.Martin
A.Martin

Reputation: 71

Delaying an enemies shot

I'm trying to delay one of my enemies shots in my pygame code that is a space invaders/bullet hell game. Unfortunately, I seem to be having a problem with delaying the shot of an enemy.

import pygame
import random
import time
import os
import threading

pygame.time.set_timer ( pygame.USEREVENT , 450 )
event_500ms = pygame.USEREVENT + 1
pygame.time.set_timer(event_500ms, 500)

class Alien2 (pygame.sprite.Sprite):
    def __init__(self):
        # Call the parent class (Sprite) constructor
        super().__init__()

        self.image = pygame.Surface([20, 15])
        self.image = pygame.image.load("alien3.png").convert()
        self.image.set_colorkey(WHITE)


        self.rect = self.image.get_rect()
    def update(self):

        pos = pygame.mouse.get_pos()

        dx = pos[0] - self.rect.x + 18
        dx *= .05
        self.rect.x += dx
def BossPowers():
    bullet3 = Bullet3()
    # Set the bullet so it is where the player is
    bullet3.rect.x = alien2.rect.x + 47
    bullet3.rect.y = alien2.rect.y + 57
    # Add the bullet to the lists
    all_sprites_list.add(bullet3)
    bullet2_list.add(bullet3)

def BossPowers2():
    bullet4 = Bullet4()
    # Set the bullet so it is where the player is
    bullet4.rect.x = alien2.rect.x + 47
    bullet4.rect.y = alien2.rect.y + 57
    # Add the bullet to the lists
    all_sprites_list.add(bullet4)
    bullet2_list.add(bullet4)


def delay():
    #threading.Timer(5.0, delay).start()
    bullet2 = Bullet2()
    # Set the bullet so it is where the player is
    bullet2.rect.x = alien2.rect.x + 47
    bullet2.rect.y = alien2.rect.y + 57
    # Add the bullet to the lists
    all_sprites_list.add(bullet2)
    bullet2_list.add(bullet2)
    BossPowers()
    BossPowers2()

Ignore my spaghetti code here, I've been working on this game for a bit, and sometimes being efficient slips my mind

while not done:
    # --- Event Processing
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    if level % 2 == 0:
        all_sprites_list.add(alien2)
        alien2_list.add(alien2)
        for block in block_list:
            block_list.remove(block)
            all_sprites_list.remove(block)
       #t = threading.Timer(2, delay)
       #t.start()    
        #if event.type == event_500ms:
            #delay()



    if level % 2 == 1:
        block_list.add(block)
        all_sprites_list.add(block) 

alright so here is my problem. The enemy ship does delay firing... sorta, but it only stops firing if the ship is moving. When the ship stops moving it fires it a burst of streams, instead of the single bullet I want it to fire. I need it to delay the shot, only fire 1 bullet every n second, and actually fire while the ship is in motion. As of right now the enemy is pretty much useless as if you just slightly move in place then it won't fire, and it's really easy to beat. Any help is appreciated!

Upvotes: 1

Views: 429

Answers (2)

skrx
skrx

Reputation: 20438

I usually use the delta time (passed time since last tick) returned by clock.tick(fps) and pass it to the update methods of the sprites to increase or decrease a timer variable. When the timer is below 0, I create an instance of the Bullet class, add it to the sprite groups and reset the timer.

import pygame as pg
from pygame.math import Vector2


pg.init()
ALIEN_IMAGE = pg.Surface((20, 15))
ALIEN_IMAGE.fill((40, 200, 90))
BULLET_IMAGE = pg.Surface((5, 5))
BULLET_IMAGE.fill((240, 120, 0))


class Alien(pg.sprite.Sprite):

    def __init__(self, pos, all_sprites, bullets):
        super().__init__()
        self.image = ALIEN_IMAGE
        self.rect = self.image.get_rect(center=pos)
        self.vel = Vector2(1, 0)
        self.pos = Vector2(pos)
        self.bullet_timer = 1  # 1 second.
        self.all_sprites = all_sprites
        self.bullets = bullets

    def update(self, dt):
        self.pos += self.vel
        self.rect.center = self.pos

        # Decrease the countdown timer.
        self.bullet_timer -= dt
        if self.bullet_timer < 0:  # Time is up so create a bullet.
            bullet = Bullet(self.rect.center, Vector2(0, 5))
            # Add the bullet to the sprite groups.
            self.all_sprites.add(bullet)
            self.bullets.add(bullet)
            self.bullet_timer = 1  # Reset timer after firing.


class Bullet(pg.sprite.Sprite):

    def __init__(self, pos, velocity):
        super().__init__()
        self.image = BULLET_IMAGE
        self.rect = self.image.get_rect(center=pos)
        self.vel = velocity
        self.pos = Vector2(pos)

    def update(self, dt):
        self.pos += self.vel
        self.rect.center = self.pos


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()

    all_sprites = pg.sprite.Group()
    bullets = pg.sprite.Group()
    alien =  Alien((0, 30), all_sprites, bullets)
    alien2 = Alien((60, 30), all_sprites, bullets)
    all_sprites.add(alien, alien2)

    done = False

    while not done:
        # dt = delta time (passed time since last clock.tick call).
        dt = clock.tick(30) / 1000  # / 1000 to convert to seconds.

        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        all_sprites.update(dt)
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)
        pg.display.flip()


if __name__ == '__main__':
    main()
    pg.quit()

Upvotes: 1

jsbueno
jsbueno

Reputation: 110208

The problem is that spaghetti code is spaghetti. :-)

I myself can't figure out exactly what you want - if all the bullet functions create equal bullets or if they are actually different, and so on.

There are two problems there that cause your main problem, however - they could be fixed keeping the code in the same form as is, but I'd suggest some heavy rewriting there, towards a more gnocchi approach (rather than spaghetti).

The main source of your problem is that you register one event to be fired after 500ms, you react on it and call your delay function. But then, that function calls both the BossPower function, to fire the other bullets, without any delay itself.

And no, of couse you could not just call time.sleep or pygame.time.delay inside delay before calling the functions to fire the other bullets, as that would suspend the whole game.

So, one approach is when calling delay to register other custom events to fire the next bullets, and just return from the function. The main loop then would fire the next bullets upon finding those events.

Which brings us to the second big problem in this code: at each frame you fetch all available events, but for all those events, the only thing you do is to check for pygame.QUIT. It is jsut by coincidence that the last event fetched in each frame is referenced by the event variable and is available in the line you check for your custom event (if event.type == event_500ms:). Other events that arrive in the same frame are simply discarded awa. This also means that your custom event could be discarded away.

So, it is important that only at one point in your code yo u fetch all the events for each frame, and that any event-related action or comparison is made at the same point.

Now, Pygame allows a minimal number of user-defined events to be used (just 8 in default build, as they recommend just the events avaliable betweem pygame.USEREVENT). If you would use one event type for each bullet class you want to fire, you'd ran out of event numbers before getting your game to have 3 levels.

So, since the code to fireing your bullets is exactly the same, and you just change the bullet class, you could make a sequence of the bullets classes to fire, and use itertools.cycle just to fetch always the next bullet type:

...
import itertools


fire_bullet_event = pygame.USEREVENT + 1

...

bullets = itertools.cycle([Bullet2, Bullet3, Bullet4)

def fire_bullet():
    bullet_type = next(bullets)
    bullet2 = bullet_type()

    # Set the bullet so it is where the player is
    bullet2.rect.x = alien2.rect.x + 47
    bullet2.rect.y = alien2.rect.y + 57
    # Add the bullet to the lists
    all_sprites_list.add(bullet2)
    bullet2_list.add(bullet2)

    delay = 500
    # Schedule to call this function again in 500ms. 
    # use an "if" to change the 500 to another number like:
    # if bullet_type == Bullet4:
    #     delay = 2000
    pygame.time.set_timer(fire_bullet_event, delay)


...

# Schedule the first bullet close to entering the loop - 
# it is easier to see than putting it with the game setup code:
pygame.time.set_timer(fire_bullet_event, 500)

while not done:
    # --- Event Processing
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
        # all code dealing with events must be inside this `for` loop.
        if event.type == fire_bullet_event:
            fire_bullet()
    if level % 2 == 0:
        all_sprites_list.add(alien2)
        alien2_list.add(alien2)
        for block in block_list:
            block_list.remove(block)
            all_sprites_list.remove(block)

Upvotes: 1

Related Questions