HaggisBishop
HaggisBishop

Reputation: 39

Removing Sprite from Group

I am making a game in pygame, so far the character can jump, run, attack with a sword and bow and I can spawn snakes either side of him, to chase him. Now I want the player to be able to attack a snake and have it die/disappear. I did this by spawning a sprite (attack_area) where the player attacks while he is attacking. Every frame the snakes check if they collided with the attack area. The error occurs after the collision has been detected and when I want to remove the snake from the list.

This is an example of the error, this occured after I had spawned two snakes and one had collided with attack_area.

3.6.6 (v3.6.6:4cf1f54eb7, Jun 27 2018, 03:37:03) [MSC v.1900 64 bit (AMD64)]
Python Type "help", "copyright", "credits" or "license" for more information.
[evaluate game_v1.2 (Debug).py]
Traceback (most recent call last):
  File "C:/Users/Haig/Desktop/game_v1.2 (Debug).py", line 167, in <module>
    sanke_list.update()
  File "C:\Program Files\Python36\Lib\site-packages\pygame\sprite.py", line 462, in update
    s.update(*args)
  File "C:/Users/Haig/Desktop/game_v1.2 (Debug).py", line 88, in <module>
    sanke_list.remove(self)
  File "C:\Program Files\Python36\Lib\site-packages\pygame\sprite.py", line 398, in remove
    sprite.remove_internal(self)
  File "C:\Program Files\Python36\Lib\site-packages\pygame\sprite.py", line 166, in remove_internal
    del self.__g[group]
builtins.KeyError: <Group(1 sprites)>

Where the error occurs:

col = pygame.sprite.collide_rect(self, attack_area)
        if col == True:
            #BUG OCCURS HERE
            snake_list.remove(self)

Since my game is over 1000 lines long I removed all that wasn't relevant to this bug, it's now under 200 lines,(the same error still occurs). The shortened program simply allows the user to spawn snakes either side of the screen, they move toward x 900. when they collide with attack_area which is always at x 600 the bug occurs.

Here is the entire program

import pygame
SCREEN_SIZE = (1280, 720)
SNAKE_SPEED = 2

# Define some colors
BLACK    = (   0,   0,   0)
WHITE    = ( 255, 255, 255)

#CREATE LIST:
snake_list = pygame.sprite.Group()

#SANKE SLITHER animation
animation_index_snake = 0
animation_frame_snake = 0

# Initialize the game engine
pygame.init()

#Create Screen
screen = pygame.display.set_mode(SCREEN_SIZE)

#LOAD IMAGES AS VARIABLES:
background_image = pygame.image.load("game/gamefolder/images/backgrounds/background_1.png").convert()
#snakes
snake_left_1 = pygame.image.load("game/gamefolder/images/enemies/sanke/sanke_left_1.png").convert_alpha()
snake_left_2 = pygame.image.load("game/gamefolder/images/enemies/sanke/sanke_left_2.png").convert_alpha()
snake_right_1 = pygame.image.load("game/gamefolder/images/enemies/sanke/sanke_right_1.png").convert_alpha()
snake_right_2 = pygame.image.load("game/gamefolder/images/enemies/sanke/sanke_right_2.png").convert_alpha()

#MAKE SNAKE CLASS
class Snake(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.change_x = 0
        self.change_y = 0
        #transitions from 0 to 1:
        self.frame = 0
        #direction snake is facing
        self.direction = "left"
        self.image = snake_left_1
        self.rect = self.image.get_rect()   

    def update(self):
        super().__init__()        
        sanke_dimensions = self.image.get_rect()     
        #y coord so snake is on ground
        self.rect.y = 472    
        #Move the snake
        #if the snake is to the left of x 900 then
        if self.rect.x <= 900:
            #move right
            self.change_x = SNAKE_SPEED
            #face right
            self.direction = "right"
        #if the snake is to the right of x 900 then
        if self.rect.x > 900:
            #move left
            self.change_x = -1*SNAKE_SPEED
            #face left
            self.direction = "left"

        #UPDATE
        self.rect.x += self.change_x
        self.rect.y += self.change_y

        #set sprite according to direction and frame
        if self.direction == "left":
            if animation_index_snake == 0:
                self.image = snake_left_1
            if animation_index_snake == 1:
                self.image = snake_left_2
        if self.direction == "right":
            if animation_index_snake == 0:
                self.image = snake_right_1
            if animation_index_snake == 1:
                self.image = snake_right_2

        #check if any snakes were hurt    
        col = pygame.sprite.collide_rect(self, attack_area)
        if col == True:
            #BUG OCCURS HERE
            snake_list.remove(self)

class Attack_area(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        #make a surface
        self.image = pygame.Surface([72, 118])
        self.image.fill(WHITE) 
        self.rect = self.image.get_rect()

    def move(self):
        #move surface to center of screen (simplified for debugging)
        self.rect.x = 600
        self.rect.y = 472

#--------main program----------                
attack_area = Attack_area()

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# -------- Main Program Loop ---------------------------------------------------
while not done: 
    # --- Main event loop-------------------------------------------------------
    for event in pygame.event.get(): # User did something
        if event.type == pygame.QUIT: # If user clicked close
            done = True # Flag that we are done so we exit this loop
        # User pressed down on a key
        elif event.type == pygame.KEYDOWN:

            #spawn enemy snake (either side of screen:)
            if event.key == pygame.K_LEFT:
                #make snake instance on left side of screen facing right
                snake = Snake()
                snake.rect.x = -96
                snake.rect.y = 0
                snake.direction = "right"
                snake.frame = 0
                snake.change_x = 0
                snake.change_y = 0   
                snake.sprite = "snake_right_1"
                #add to snake list
                snake_list.add(snake)


            elif event.key == pygame.K_RIGHT:
                #make snake instance on right side of screen facing left
                snake = Snake()
                snake.rect.x = 1376
                snake.rect.y = 0
                snake.direction = "left"
                snake.frame = 0
                snake.change_x = 0
                snake.change_y = 0    
                snake.sprite = "sanke_left_1"
                #add to snake list
                snake_list.add(snake)                 
            elif event.key == pygame.K_w:
                print(snake_list.sprites())
    # --- Game logic --------------------------------------------- 


    #move attack in actual program this method would be more complex/useful
    attack_area.move()

    # --- Drawing code -------------------------------------------
    screen.fill(BLACK)

    #blit background 
    screen.blit(background_image, [0, 0])
    #move/update all snakes in list
    snake_list.update()
    #draw all snakes in list
    snake_list.draw(screen)

    # --- Go ahead and update the screen with what we've drawn------------------
    pygame.display.flip()

    # --- Clock Work------------------------------------------------------------
    #Limit to 60 frames per second
    clock.tick(60)
    #one frame passed:
    animation_frame_snake += 1
    #(18 is how many frames the animation frame should be shown for)
    if animation_frame_snake >= 18:
        #reset
        animation_frame_snake = 0
        #alternate index between 0 & 1
        if animation_index_snake == 0:
            animation_index_snake = 1
        elif animation_index_snake == 1:
            animation_index_snake = 0

#now left main program loop (because user pressed X), so quit
pygame.quit()

I have spent hours on this, but i bet it's something stupid. At this point I just what the damn snake removed from it's group! Help would be much appreciated. Even if someone just tells me what this is:

builtins.KeyError: <Group(1 sprites)>

Upvotes: 2

Views: 1343

Answers (1)

skrx
skrx

Reputation: 20468

The problem is that you call super().__init__() each frame in the update method. Just remove this line and the program will work correctly.

When you call super().__init__(), the _Sprite__g attribute of your sprite, a dict of sprite groups which contain the sprite, will be set to an empty dict, even though it's still in the snake_list. So, the snake_list group still contains the sprite, but the sprite thinks it's in no group.

Now when you call snake_list.remove(self), the sprite group calls the remove_internal method of the sprite with self (the group) as an argument, so that the sprite can remove itself from this group by deleting that key of its group dict del self.__g[group], but the dict is empty and therefore a KeyError gets raised.

Here's the sprite module if you want to take a look at the source code.

Upvotes: 1

Related Questions