-1

So I currently have an Enemy Class that uses sprite groups and a projectile class that does not use sprite groups.

I check the collision of my projectiles against the enemies in the sprite group. If an enemy is hit, the enemy sprite gets removed, the score is incremented and the bullet gets removed from the screen. At the moment only the enemy sprite gets removed.

Projectile Class:

class Projectile():
    def __init__(self, x, y, width, height, speed, damage, image):
        # Variables for the player attacks (ranged)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.speed = speed
        self.damage = damage
        self.image = image
        self.rect = self.image.get_rect()

    def draw(self, window):
        window.blit(self.image, (self.x, self.y))

    def checkCollision(self, bullet, enemy):
        col = pygame.sprite.spritecollide(bullet, enemy, True)
        if col == True:
            return True

Enemy Class

class Enemy(pygame.sprite.Sprite):

    def __init__(self, width, height, mov_speed):
        # Variables for the various enemies
        pygame.sprite.Sprite.__init__(self)
        self.width = width
        self.height = height
        self.mov_speed = mov_speed
        self.image = speedboat_sprite
        self.rect = self.image.get_rect()
        self.rect.x = random.randint(64, 734)
        self.rect.y = random.randrange(-1000, -100)
        self.hitbox = (
        self.rect.x + 22, self.rect.y + 15, 19, 45)  # Dimensions of the hitbox to make it close to the model
        self.end_reached = False
        self.alive = True

    def update(self):
        # This function lets the enemy go forward until the end of the screen
        max_distance = 800 - self.height
        if self.rect.y < max_distance:
            self.rect.y += self.mov_speed

        if self.rect.y >= max_distance:
            self.end_reached = True
            self.kill()

Code in game loop that does not work:

# Game logic here
game = True
whale = Player(334, 650, 128, 128)  # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
pirate_boss = Boss(1, 1, 256, 256, 1, 50)
beams = []
cooldown = 0
wave_1 = 5
lives = 3
score = 0

while game:

    for event in pygame.event.get():  # this for-loop makes sure the game exits when clicking x
        if event.type == pygame.QUIT:
            game = False

    # Collision of attacks
    for beam in beams:
        hit = beam.checkCollision(beam, speedboats)
        if hit == True:
            score += 25
            beams.pop(beams.index(beam))

        if beam.y > 0:
            beam.y -= beam.speed  # This makes sure the bullet moves forward as long as it is not of the screen
            beam.rect.center = (beam.x, beam.y)
        else:
            beams.pop(beams.index(beam))  # If the bullet goes of the screen it gets removed from the list

Full code:

import pygame
import random

# Initialize game window here
pygame.init()

# Variables for the game window
window = pygame.display.set_mode((800, 800))
pygame.display.set_caption('Sea Invaders')
clock = pygame.time.Clock()  # Adds the clock for spawn timers
message_font = pygame.font.SysFont('Calibri', 36)

# Sprites, background and music
background = pygame.image.load('Background.jpg')
whale_sprite = pygame.image.load('Whale.png')
speedboat_sprite = pygame.image.load('Speedboat.png')
beam_sprite = pygame.image.load('Beam.png')
life_sprite = pygame.image.load('Life.png')
pirate_boss_sprite_right = pygame.image.load('Pirateboss_right.png')


# Classes
class Player():

    def __init__(self, x, y, width, height):
        # Variables for the Whale go here
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.mov_speed = 5
        self.hitbox = (self.x, self.y, self.width, self.height)

    def draw(self, window):
        # This function draws the whale using the sprite and the defined location in the class parameters
        window.blit(whale_sprite, (self.x, self.y))
        self.hitbox = (self.x, self.y, self.width, self.height)
        # pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2) # Hitbox check


class Enemy(pygame.sprite.Sprite):

    def __init__(self, width, height, mov_speed):
        # Variables for the various enemies
        pygame.sprite.Sprite.__init__(self)
        self.width = width
        self.height = height
        self.mov_speed = mov_speed
        self.image = speedboat_sprite
        self.rect = self.image.get_rect()
        self.rect.x = random.randint(64, 734)
        self.rect.y = random.randrange(-1000, -100)
        self.hitbox = (
        self.rect.x + 22, self.rect.y + 15, 19, 45)  # Dimensions of the hitbox to make it close to the model
        self.end_reached = False
        self.alive = True

    def update(self):
        # This function lets the enemy go forward until the end of the screen
        max_distance = 800 - self.height
        if self.rect.y < max_distance:
            self.rect.y += self.mov_speed

        if self.rect.y >= max_distance:
            self.end_reached = True
            self.kill()

    def hit(self):
        self.kill()
        self.alive = False


class Boss():
    def __init__(self, x, y, width, height, mov_speed, hitpoints):
        # Variables for the boss objects
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.mov_speed = mov_speed
        self.hitpoints = hitpoints
        self.hitbox = (self.x + 30, self.y + 50, 225, 160)  # Dimensions of the hitbox to make it close to the model
        self.alive = True
        self.end_reached = False

    def draw(self, window):
        if self.alive:
            window.blit(pirate_boss_sprite_right, (self.x, self.y))
            self.hitbox = (self.x + 30, self.y + 50, 225, 160)
            pygame.draw.rect(window, (255, 0, 0),
                             (self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, 50, 10))
            pygame.draw.rect(window, (0, 255, 0),
                             (self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, self.hitpoints, 10))

            # pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2)  # hitbox check

    def move(self):
        max_distance = 800 - self.height
        if self.x < (800 + self.width):
            self.x += self.mov_speed

        else:
            self.y += 100
            self.x = -51

        if self.y >= max_distance:
            self.alive = False
            self.end_reached = True

    def hit(self):
        print('hit')
        if self.hitpoints <= 0:
            self.alive = False


class Projectile():
    def __init__(self, x, y, width, height, speed, damage, image):
        # Variables for the player attacks (ranged)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.speed = speed
        self.damage = damage
        self.image = image
        self.rect = self.image.get_rect()

    def draw(self, window):
        window.blit(self.image, (self.x, self.y))

    def checkCollision(self, bullet, enemy):

        bullet.rect.x = int(self.x)
        bullet.rect.y = int(self.y)

        return pygame.sprite.spritecollide(bullet, enemy, True)




class Hud():
    def __init__(self, x, y, sprite):
        self.x = x
        self.y = y
        self.sprite = sprite

    def draw(self, window):
        window.blit(self.sprite, (self.x, self.y))


# Methods
def redrawGameWindow():
    window.blit(background, (0, 0))  # Loads in the background
    whale.draw(window)  # Draws the Whale in the game
    scoreboard = message_font.render(str(score), True, (255, 255, 255))
    window.blit(scoreboard, (700, 760))

    x_life = 0

    for life in range(lives):
        life = Hud(x_life, 760, life_sprite)
        x_life += 25
        life.draw(window)

    if lives == 0:
        text = f'Game over! Your score: {score}.'
        game_over = message_font.render(text, True, (0, 0, 0))
        window.blit(game_over, (200, 200))

    if score >= 75:
        pirate_boss.draw(window)
        pirate_boss.move()

    speedboats.draw(window)
    speedboats.update()

    for fired_beam in beams:
        fired_beam.draw(window)
    pygame.display.update()  # This updates the above changes to the game window


# Game logic here
game = True
whale = Player(334, 650, 128, 128)  # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
pirate_boss = Boss(1, 1, 256, 256, 1, 50)
beams = []
cooldown = 0
wave_1 = 5
lives = 3
score = 0

while game:

    for event in pygame.event.get():  # this for-loop makes sure the game exits when clicking x
        if event.type == pygame.QUIT:
            game = False

    # Life check
    for speedboat in speedboats:
        if speedboat.end_reached:  # if this is true we subtract a life
            lives -= 1  # We subtract lives with the enemies that reached the end

        if not speedboat.alive:
            speedboats.remove(speedboat)

    if pirate_boss.end_reached:
        lives = 0

    # Basic cooldown for the projectiles of the player
    if cooldown > 0:
        cooldown += 1
    if cooldown > 50:
        cooldown = 0

    # Collision of attacks
    for beam in beams:
        hit = beam.checkCollision(beam, speedboats)
        if hit == True:
            score += 25
            beams.pop(beams.index(beam))
        if pirate_boss.alive:
            if beam.y - beam.width < pirate_boss.hitbox[1] + pirate_boss.hitbox[3] and beam.y + beam.width > \
                    pirate_boss.hitbox[1]:
                if beam.x + beam.height > pirate_boss.hitbox[0] and beam.x - beam.height < pirate_boss.hitbox[0] + \
                        pirate_boss.hitbox[2]:
                    pirate_boss.hitpoints -= beam.damage
                    beams.pop(beams.index(beam))
                    pirate_boss.hit()

        if beam.y > 0:
            beam.y -= beam.speed  # This makes sure the bullet moves forward as long as it is not of the screen
            beam.rect.center = (beam.x, beam.y)
        else:
            beams.pop(beams.index(beam))  # If the bullet goes of the screen it gets removed from the list

    # --- PLAYER CONTROLS ---
    keys = pygame.key.get_pressed()

    if keys[pygame.K_LEFT] and whale.x > whale.mov_speed:
        # Makes sure the whale can move left
        # and prevents the whale from exiting the screen
        whale.x = whale.x - whale.mov_speed

    if keys[pygame.K_RIGHT] and whale.x < 800 - whale.mov_speed - whale.width:
        # Makes sure the whale can move right
        # and prevents the whale from exiting the screen
        whale.x = whale.x + whale.mov_speed

    if keys[pygame.K_SPACE] and cooldown == 0:
        # This block of code takes care of the beam-projectile the player can shoot
        if len(beams) < 3:
            beams.append(
                Projectile(round(whale.x + (whale.width // 2) - (32 // 2)), round(whale.y - (32 // 2)), 32, 32, 2,
                           10, beam_sprite))
            # The beam gets spawned at the whale X/Y Coordinate. To make the beam appear in the middle and at the
            # nose we add half the sprites width - half the width of the projectile to the for the x coordinate
            # and we use the y coordinate - half the length of the projectile to make the attack spawn at the top
        cooldown = 1

    # --- ENEMY SPAWNING ---
    # This block of code spawns the first wave of enemies
    for n in range(wave_1):
        speedboats.add(Enemy(64, 64, 1))
        wave_1 -= 1

    redrawGameWindow()

pygame.quit()
de_l3ns
  • 25
  • 7
  • Please provide the expected see [MRE - Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). Show where the intermediate results deviate from the ones you expect. We should be able to paste a single block of your code into file, run it, and reproduce your problem. This also lets us test any suggestions in your context. – Prune May 08 '21 at 17:19
  • Your posted code doesn't run due to various undefined symbols. You haven't shown a trace of execution and values at and before the point of error; this initial diagnosis is your job before posting. Please complete the post. – Prune May 08 '21 at 17:19
  • What I *do* notice is that there are at least two places where the beam is removed, but only one where the score is updated. You need to trace these -- include tracing the score. Since your code is incomplete, it's also possible that you *do* increment the score, but then lose the value due to a scoping error. Again, refer to the MRE guidelines. – Prune May 08 '21 at 17:21
  • @Prune Apoligies! I figured I had pasted the bits needed, I'v now posted my full code. Al my classes were drawn manually but I ran into a problem that I could not delete my sprites with a regular class, making the game laggy after x amount of spawned enemies. I now changed my Enemy class to use the sprite groups so I can .kill my sprites after certain condition are met. The problem I'm encountering is that the beam does not get removed and the score does not increment when hitting a speedboat. – de_l3ns May 09 '21 at 09:32

2 Answers2

0

I can't see the drawing code so I am guessing that you are still drawing the beam. Make beams a spritegroup and do this in the loop (not in the beams class)

pygame.sprite.groupcollide(beams, speedboats, True, True)

Which will remove both from their respective groups.

marienbad
  • 1,461
  • 1
  • 9
  • 19
0

pygame.sprite.spritecollide uses the rect attribute of the objects to detect the collision. However toe position of the Projectile object is not stored in rect, but in the x and y attributes.

Before running the collision test, update the position of therect attribute with the x and y attributes:

class Projectile():
    # [...]

    def checkCollision(self, enemy):

        self.rect.x = int(self.x)
        self.rect.y = int(self.y)

        return any(pygame.sprite.spritecollide(self, enemy, True))
for beam in beams[:]:
    if beam.checkCollision(speedboats):
        score += 25
        beams.pop(beams.index(beam))

Additionally I recommend reading How to remove items from a list while iterating?.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • 1
    Thank you @Rabbid76 My IDE does give me a warning at the last line. after changing to self. It states Expected type Sprite, got Projectile instead. – de_l3ns May 09 '21 at 19:13