I am currently working on a game on Pygame in Python. It is a player vs player battle game. When the program is run the value error subsurface rectangle outside surface area is shown. I searched about the error but could not understand about it as I am new to Pygame.
My program-
import pygame
from Players import Characters
pygame.init()
#Game window
swidth = 1000
sheight= 600
s=pygame.display.set_mode((swidth, sheight))
pygame.display.set_caption("Brawler")
#Framerate
clock = pygame.time.Clock()
FPS = 60
#Game variables
count=3
last_count_update=pygame.time.get_ticks()
score = [0, 0]#player scores. [P1, P2]
round_over = False
round_cooldown = 2000
#Player variables
Knight_size = 278
Knight_scale = 4
Knight_offset = [72, 56]
Knight_data = [Knight_size, Knight_scale, Knight_offset]
Assassin_size = 288
Assassin_scale = 3
Assassin_offset = [112, 107]
Assassin_data =[ Assassin_size, Assassin_scale, Assassin_offset]
#load background image
bg_image = pygame.image.load("G1/Assets/Bg.png").convert_alpha()
#load spritesheets
Knight_sheet = pygame.image.load("G1/Assets/Knight/Sprites.png").convert_alpha()
Assassin_sheet = pygame.image.load("G1/Assets/Assassin/Sprites.png").convert_alpha()
#load vicory image
victory_img = pygame.image.load("G1/Assets/victory.png").convert_alpha()
#define number of steps in each animation
Knight_steps=[8, 8, 20, 8, 11, 19, 10, 6, 13]
Assassin_steps=[8, 8, 6, 6, 8, 18, 7, 6, 19]
#Font
count_font = pygame.font.Font("G1/assets/fonts/turok.ttf", 80)
score_font = pygame.font.Font("G1/assets/fonts/turok.ttf", 30)
#Text
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
s.blit(img, (x, y))
#Background
def draw_bg():
scaled_bg = pygame.transform.scale(bg_image, (swidth, sheight))
s.blit(scaled_bg, (0, 0))
#Health bars
red=(255, 0, 0)
yellow=(255, 255, 0)
white=(255, 255, 255)
def draw_health_bar(health, x, y):
ratio = health / 100
pygame.draw.rect(s, white, (x - 2, y - 2, 404, 34))
pygame.draw.rect(s, red, (x, y, 400, 30))
pygame.draw.rect(s, yellow, (x, y, 400 * ratio, 30))
#Player avatars
Knight=Characters(1, 200, 310, False, Knight_data, Knight_sheet, Knight_steps)
Assassin=Characters(2, 700, 310, True, Assassin_data, Assassin_sheet, Assassin_steps)
#game loop
run = True
while run:
clock.tick(FPS)
#Display background
draw_bg()
#Display health bar
draw_health_bar(Knight.health, 20, 20)
draw_health_bar(Assassin.health, 580, 20)
draw_text("P1: " + str(score[0]), score_font, red, 20, 60)
draw_text("P2: " + str(score[1]), score_font, red, 580, 60)
#countdown
if intro_count <= 0:
#Move fighters
Knight.move(swidth, sheight, s, Assassin, round_over)
Assassin.move(swidth, sheight, s, Knight, round_over)
else:
#Display count timer
draw_text(str(intro_count), count_font, red, swidth/2, sheight/3)
#update count timer
if (pygame.time.get_ticks()-last_count_update)>= 1000:
intro_count -= 1
last_count_update = pygame.time.get_ticks()
#Update fighters
Knight.update()
Assassin.update()
#Display fighters
Knight.draw(s)
Assassin.draw(s)
#check for player defeat
if round_over == False:
if Knight.alive == False:
score[1] += 1
round_over = True
round_over_time = pygame.time.get_ticks()
elif Assassin.alive == False:
score[0] += 1
round_over = True
round_over_time = pygame.time.get_ticks()
else:
#display victory image
s.blit(victory_img, (360, 150))
if pygame.time.get_ticks()-round_over_time>round_cooldown:
round_over = False
intro_count = 3
Knight= Knight(1, 200, 310, False, Knight_data, Knight_sheet, Knight_steps)
Assassin= Assassin(2, 700, 310, True, Assassin_data, Assassin_sheet, Assassin_steps)
#event handler
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#update display
pygame.display.update()
#exit pygame
pygame.quit()`
My Module-
import pygame
class Characters():
def __init__(self, player, x, y, flip, data, sheet, steps):
self.player = player
self.size = data[0]
self.image_scale = data[1]
self.offset = data[2]
self.flip = flip
self.animation_list = self.load_images(sheet, steps)
self.action = 0#0:idle #1:run #2:jump #3:attack1 #4: attack2 #5:hit #6:death
self.frame_index = 0
self.image = self.animation_list[self.action][self.frame_index]
self.update_time = pygame.time.get_ticks()
self.rect = pygame.Rect((x, y, 80, 180))
self.vel_y = 0
self.running = False
self.jump = False
self.attacking = False
self.attack_type = 0
self.attack_cooldown = 0
self.hit = False
self.health = 100
self.alive = True
def load_images(self, sheet, steps):
#extract images from spritesheet
animation_list = []
for y, animation in enumerate(steps):
temp_img_list = []
for x in range(animation):
temp_img = sheet.subsurface(x * self.size, y * self.size, self.size, self.size)
temp_img_list.append(pygame.transform.scale(temp_img, (self.size * self.image_scale, self.size * self.image_scale)))
animation_list.append(temp_img_list)
return animation_list
def move(self, screen_width, screen_height, surface, target, round_over):
SPEED = 10
GRAVITY = 2
dx = 0
dy = 0
self.running = False
self.attack_type = 0
#get keypresses
key = pygame.key.get_pressed()
#can only perform other actions if not currently attacking
if self.attacking == False and self.alive == True and round_over == False:
#check player 1 controls
if self.player == 1:
#movement
if key[pygame.K_a]:
dx = -SPEED
self.running = True
if key[pygame.K_d]:
dx = SPEED
self.running = True
#jump
if key[pygame.K_w] and self.jump == False:
self.vel_y = -30
self.jump = True
#attack
if key[pygame.K_r] or key[pygame.K_t]:
self.attack(target)
#determine which attack type was used
if key[pygame.K_r]:
self.attack_type = 1
if key[pygame.K_t]:
self.attack_type = 2
#check player 2 controls
if self.player == 2:
#movement
if key[pygame.K_LEFT]:
dx = -SPEED
self.running = True
if key[pygame.K_RIGHT]:
dx = SPEED
self.running = True
#jump
if key[pygame.K_UP] and self.jump == False:
self.vel_y = -30
self.jump = True
#attack
if key[pygame.K_KP1] or key[pygame.K_KP2]:
self.attack(target)
#determine which attack type was used
if key[pygame.K_KP1]:
self.attack_type = 1
if key[pygame.K_KP2]:
self.attack_type = 2
#apply gravity
self.vel_y += GRAVITY
dy += self.vel_y
#ensure player stays on screen
if self.rect.left + dx < 0:
dx = -self.rect.left
if self.rect.right + dx > screen_width:
dx = screen_width - self.rect.right
if self.rect.bottom + dy > screen_height - 220:
self.vel_y = 0
self.jump = False
dy = screen_height - 220 - self.rect.bottom
#ensure players face each other
if target.rect.centerx > self.rect.centerx:
self.flip = False
else:
self.flip = True
#apply attack cooldown
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
#update player position
self.rect.x += dx
self.rect.y += dy
#handle animation updates
def update(self):
#check what action the player is performing
if self.health <= 0:
self.health = 0
self.alive = False
self.update_action(6)#6:death
elif self.hit == True:
self.update_action(5)#5:hit
elif self.attacking == True:
if self.attack_type == 1:
self.update_action(3)#3:attack1
elif self.attack_type == 2:
self.update_action(4)#4:attack2
elif self.jump == True:
self.update_action(2)#2:jump
elif self.running == True:
self.update_action(1)#1:run
else:
self.update_action(0)#0:idle
animation_cooldown = 50
#update image
self.image = self.animation_list[self.action][self.frame_index]
#check if enough time has passed since the last update
if pygame.time.get_ticks() - self.update_time > animation_cooldown:
self.frame_index += 1
self.update_time = pygame.time.get_ticks()
#check if the animation has finished
if self.frame_index >= len(self.animation_list[self.action]):
#if the player is dead then end the animation
if self.alive == False:
self.frame_index = len(self.animation_list[self.action]) - 1
else:
self.frame_index = 0
#check if an attack was executed
if self.action == 3 or self.action == 4:
self.attacking = False
self.attack_cooldown = 20
#check if damage was taken
if self.action == 5:
self.hit = False
#if the player was in the middle of an attack, then the attack is stopped
self.attacking = False
self.attack_cooldown = 20
def attack(self, target):
if self.attack_cooldown == 0:
#execute attack
self.attacking = True
self.attack_sound.play()
attacking_rect = pygame.Rect(self.rect.centerx - (2 * self.rect.width * self.flip), self.rect.y, 2 * self.rect.width, self.rect.height)
if attacking_rect.colliderect(target.rect):
target.health -= 10
target.hit = True
def update_action(self, new_action):
#check if the new action is different to the previous one
if new_action != self.action:
self.action = new_action
#update the animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def draw(self, surface):
img = pygame.transform.flip(self.image, self.flip, False)
surface.blit(img, (self.rect.x - (self.offset[0] * self.image_scale), self.rect.y - (self.offset[1] * self.image_scale)))
The error occurred-
File "c:\Users\User\Desktop\G1\Battle game V1.py", line 73, in <module>
Knight=Characters(1, 200, 310, False, Knight_data, Knight_sheet, Knight_steps)
File "c:\Users\User\Desktop\G1\Players.py", line 10, in __init__
self.animation_list = self.load_images(sheet, steps)
File "c:\Users\User\Desktop\G1\Players.py", line 33, in load_images
temp_img = sheet.subsurface(x * self.size, y * self.size, self.size, self.size)
ValueError: subsurface rectangle outside surface area