2

I've recently tried to make enemy sprites fire bullets using pygame.USEREVENT + 1 and pygame.time.set_timer() but nothing I do seems to make the enemy actually shoot anything. The program still runs but nothing actually happens from the enemy perspective.

How can I make it so that the enemies will actually fire in any given direction, then eventually fire towards the player?

Working code is as follows:

Main Game module:

import pygame
from constants import *
from player import Player
from Projectile import Projectile
from pygame.math import Vector2
from enemy import Enemy

pygame.init()

screen = pygame.display.set_mode([500, 500])

pygame.display.set_caption('Labyrinth')


all_sprites_list = pygame.sprite.Group()
projectiles = pygame.sprite.Group()
enemy_sprites = pygame.sprite.Group()


# Spawn player

player = Player(50, 50)
all_sprites_list.add(player)

# Spawn enemy

enemy = Enemy(150, 150)
enemy_sprites.add(enemy)



clock = pygame.time.Clock()
previous_time = pygame.time.get_ticks()
speed = 12

keymap = {
    pygame.K_LEFT : Vector2(-speed, 0),
    pygame.K_RIGHT: Vector2(speed, 0),
    pygame.K_UP:    Vector2(0, -speed),
    pygame.K_DOWN:  Vector2(0, speed)
}


done = False


# ----- Event Loop

while not done:

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

        elif event.type == pygame.KEYDOWN:

            if event.key == pygame.K_LEFT:
                vel = Vector2(-speed, 0)
            elif event.key == pygame.K_RIGHT:
                vel = Vector2(speed, 0)
            elif event.key == pygame.K_UP:
                vel = Vector2(0, -speed)
            elif event.key == pygame.K_DOWN:
                vel = Vector2(0, speed)


    current_time = pygame.time.get_ticks()

    pressed = pygame.key.get_pressed()
    for key in keymap:
        if pressed[key]:
            if current_time - previous_time > 500:
                previous_time = current_time
                projectiles.add(Projectile(player.rect.x, player.rect.y, vel))


# ----- Game Logic

    all_sprites_list.update()
    projectiles.update()
    enemy_sprites.update()



    screen.fill(GREEN)

    all_sprites_list.draw(screen)
    projectiles.draw(screen)
    enemy_sprites.draw(screen)


    pygame.display.flip()

    clock.tick(60)

pygame.quit()

Player module

from constants import *
import pygame
import time
from datetime import datetime, timedelta

class Player(pygame.sprite.Sprite):


    def __init__(self, x, y):

        super().__init__()

        self.image = pygame.Surface([15, 15])
        self.image.fill(BLACK)

        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.fire_rate = 1

        self.change_x = 0
        self.change_y = 0

    def changespeed(self, x, y):
        self.change_x += x
        self.change_y += y

    def update(self):
        self.rect.x += self.change_x
        self.rect.y += self.change_y

Enemy module

from constants import *
import pygame
from Projectile import Projectile
from pygame.math import Vector2

CANSHOOT = pygame.USEREVENT + 1
pygame.time.set_timer(CANSHOOT, 2000)

speed = 12

projectiles = pygame.sprite.Group()

class Enemy(pygame.sprite.Sprite):


    def __init__(self, x, y):

        super().__init__()

        self.image = pygame.Surface([10, 10])
        self.image.fill(RED)

        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def can_shoot(self):

        for event in pygame.event.get():
            if pygame.event.get(CANSHOOT):
                vel = Vector2(-speed, 0)
                projectiles.add(Projectile(self.rect.x, self.rect.y, vel))


        projectiles.update()
        projectiles.draw(screen)

Projectile module

import pygame
from constants import *
from pygame.math import Vector2

BULLET_IMG = pygame.Surface((4, 4))
BULLET_IMG.fill(RED)

class Projectile(pygame.sprite.Sprite):

    def __init__(self, x, y, vel):

        super().__init__()

        self.image = BULLET_IMG
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.vel = Vector2(vel)


    def update(self):

        self.rect.move_ip(self.vel)
MarianD
  • 13,096
  • 12
  • 42
  • 54
Goose _
  • 267
  • 1
  • 9
  • Should the enemies all fire at the same time? – skrx Aug 27 '18 at 08:59
  • 1
    If the enemy sprites should fire at a moving target (the player), check out [this answer](https://stackoverflow.com/a/50350184/6220679). – skrx Aug 27 '18 at 09:19
  • well preferably down the track if there are multiple enemies in a room they will only start to fire if the player gets within a certain range of them. And thankyou, that appears to be the answer i was looking for!! – Goose _ Aug 27 '18 at 09:22

1 Answers1

3

You should never have two event loops (for event in pygame.event.get():) in your program, because pygame.event.get will consume all events in the queue and when you call it the second time in the same frame it will be empty.

If the enemy sprites should be able to fire independently, you need to give each of them its own timer.

import pygame
from pygame.math import Vector2


class Enemy(pygame.sprite.Sprite):

    def __init__(self, x, y, projectiles):
        super().__init__()
        self.image = pygame.Surface([10, 10])
        self.image.fill(RED)
        self.rect = self.image.get_rect(topleft=(x, y))
        # The previous time when the sprite fired.
        self.previous_time = pygame.time.get_ticks()
        self.shoot_delay = 1000  # milliseconds
        self.speed = 12
        self.projectiles = projectiles

    def update(self):
        now = pygame.time.get_ticks()
        if now - self.previous_time > self.shoot_delay:
            self.previous_time = now
            vel = Vector2(self.speed, 0)
            # Add the projectile to the group.
            self.projectiles.add(Projectile(self.rect.x, self.rect.y, vel))


class Projectile(pygame.sprite.Sprite):

    def __init__(self, x, y, vel):
        super().__init__()
        self.image = BULLET_IMG
        self.rect = self.image.get_rect(topleft=(x, y))
        self.vel = Vector2(vel)

    def update(self):
        self.rect.move_ip(self.vel)


pygame.init()
screen = pygame.display.set_mode([500, 500])

RED = pygame.Color('red')
GREEN = pygame.Color(40, 100, 0)
BULLET_IMG = pygame.Surface((4, 4))
BULLET_IMG.fill(RED)

all_sprites_list = pygame.sprite.Group()
projectiles = pygame.sprite.Group()
enemy_sprites = pygame.sprite.Group()
# Pass the projectiles group to the enemy, so that
# we can add the bullets in the update method later.
enemy = Enemy(150, 150, projectiles)
enemy_sprites.add(enemy)

clock = pygame.time.Clock()

done = False
while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    # ----- Game Logic
    all_sprites_list.update()
    projectiles.update()
    enemy_sprites.update()

    screen.fill(GREEN)
    all_sprites_list.draw(screen)
    projectiles.draw(screen)
    enemy_sprites.draw(screen)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
skrx
  • 19,980
  • 5
  • 34
  • 48
  • I understand what you mean about not using more than one event loop, that makes sense. However, my player, enemy, projectile and game modules are all separate files. How can I add the projectiles that I'm spawning in the enemy module to the projectiles group that is in the main game module?? – Goose _ Aug 27 '18 at 09:38
  • 1
    The simplest solution would be to add the `projectiles` group as an attribute to the sprites. I'll edit the answer. – skrx Aug 27 '18 at 09:40
  • 1
    thankyou again! I think I finally understand the whole concept behind passing a group to the main game in order to use it in the update method – Goose _ Aug 27 '18 at 09:49