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