Reputation: 71
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
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
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