I am relatively new to OOP and Pygame and have been trying to code a tower defense game and have hit a roadblock. I keep getting this error:
AttributeError: type object 'Game' has no attribute 'path'
I have tried reading posts from people with the same problem, however none of the fixes seem to work for me.
I am attempting to have several different levels in my game that have different backgrounds and therefore different paths. I'm trying to do this by creating a parent Game
class and then having a subclass for each level. (Each class in my program has a different .py file.) Ideally, each level subclass would have its own path
attribute which would override the path
attribute in the game class. Then the path
is passed into my Enemy
class where there is code that makes enemies follow the path.
I can fix my error by putting self.path
(in my game class) above the constructor and just defining it as path
. However in doing this, I am unable or don't know how to override the attribute in the subclass.
Additionally, in my enemy class, I have tried to circumvent the problem of having a circular import with the game class by placing it lower down and I'm thinking that this may have something to do with it, however, I'm unsure.
If this is the case, is there a better way of allowing my enemy class to access the path?
This is the relevant code for my level select file:
# If button is pressed then execute its corresponding function
if event.type == pygame.MOUSEBUTTONDOWN:
# If level 1 button is pressed then instantiate Level 1
if level1.buttonPress(pos):
level1_class = Level1(self.screen)
# Runs the main game loop for the instantiated level
level1_class.run()
This is the relevant code for my Enemy
class:
import pygame
lightGreen = (0, 255, 0)
red = (200, 0, 0)
# Creates Enemy class
class Enemy(pygame.sprite.Sprite):
imgs = []
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = 150
self.height = 150
self.max_health = 100
self.health = 100
self.path = [(0, 0)]
self.x = self.path[0][0]
self.y = self.path[0][1]
self.img = None
self.animation = 0
self.speed = 1
self.i = 1
self.pos_check = 0
# Draws the sprite to the screen
def draw(self, screen):
# Only works for the first time it is called
if self.pos_check == 0:
self.pos_check += 1
# Sets starting x and y as the first co-ordinates of the path
from Game import Game
self.x = Game.path[0][0]
self.y = Game.path[0][1]
# Chooses an image from a list of images based on the number of self.animation
self.img = self.imgs[self.animation]
# Draws image
screen.blit(self.img, (self.x - self.width / 2, self.y - self.height / 2))
# Draws health bar
self.draw_health_bar(screen)
def update(self):
# Increments self.animation each call
self.animation += 1
# Resets the animation count to 0 if the animation count exceeds the length of the list of images
if self.animation >= len(self.imgs):
self.animation = 0
# Calls the moving method a number of times depending on speed
for i in range(self.speed):
self.follow_path()
def draw_health_bar(self, screen):
# Draws the red portion of the health bar depending on max health
pygame.draw.rect(screen, red, (self.x - self.width / 2, self.y - self.height / 2, self.max_health, 10))
# Draws the green portion of the health bar depending on the enemies current health
pygame.draw.rect(screen, lightGreen, (self.x - self.width / 2, self.y - self.height / 2, self.health, 10))
def follow_path(self):
# Imports game class
from Game import Game
# self.path = path passed from Game class
self.path = Game.path
# If the x co-ord and y co-ord == the x and y co-ord of the next path position then add 1 to counter
if (self.x, self.y) == (self.path[self.i][0], self.path[self.i][1]):
self.i += 1
# If x < than next x co-ord in path then increase x by 1 pixel
if self.x < self.path[self.i][0]:
self.x += 1
# If x > than next x co-ord in path then decrease x by 1 pixel
elif self.x > self.path[self.i][0]:
self.x -= 1
# If y < than next x co-ord in path then increase y by 1 pixel
if self.y < self.path[self.i][1]:
self.y += 1
# If y > than next x co-ord in path then decrease y by 1 pixel
elif self.y > self.path[self.i][1]:
self.y -= 1
Code for my Game
class:
import pygame
import os
from Wizard import Wizard
from Button import Button
import sys
# Creates Game class
class Game:
def __init__(self, screen):
self.path = [(-30, 783), (0, 783), (271, 767), (369, 471), (566, 414), (625, 352), (699, 138), (856, 93),
(1206, 93),
(1400, 46), (1500, 97), (1759, 97), (1784, 311), (1622, 434), (1487, 734), (1670, 789),
(1756, 842), (1782, 1016), (1782, 1200)]
self.enemies = None
self.towers = None
self.game_button_list = None
self.pause_button_list = None
self.lives = 10
self.money = 100
self.width = 1920
self.height = 1080
self.background = pygame.image.load(os.path.join("Images", "game_background_3.png"))
self.background = pygame.transform.scale(self.background, (self.width, self.height))
self.screen = screen
self.pause_button = Button(1800, 20, "button_pause.png")
self.table = pygame.image.load(os.path.join("Images", "s_table.png"))
self.table_size = self.table.get_size()
self.play_button = Button(720, 465, "button_play_scaledDown.png")
self.restart_button = Button(890, 465, "button_restart.png")
self.close_button = Button(1055, 465, "button_close.png")
self.fast_forward_button = Button(1670, 20, "button_quick.png")
self.running = True
self.paused = False
self.paused_check = True
self.remove_coordinate = 1100
self.fastForward = False
self.fastForward_counter = 1
self.clock = pygame.time.Clock()
self.fps = 60
self.dt = self.clock.tick(self.fps)
self.spawn_timer = 0
self.spawn_frequency = 1000
self.original_speed = None
self.fastForward_check = 1
def new(self):
# Creates sprite groups for enemies, towers, game buttons and pause buttons
self.enemies = pygame.sprite.Group()
self.towers = pygame.sprite.Group()
self.game_button_list = pygame.sprite.Group()
self.pause_button_list = pygame.sprite.Group()
# Adds instantiated objects to the sprite groups
self.game_button_list.add(self.pause_button)
self.game_button_list.add(self.fast_forward_button)
self.pause_button_list.add(self.play_button)
self.pause_button_list.add(self.restart_button)
self.pause_button_list.add(self.close_button)
# Main Game loop
def run(self):
self.new()
while self.running:
# Sets fps to 60
self.clock.tick(self.fps)
# Calls events
self.events()
# If not paused then update
if not self.paused:
self.update()
# Draws everything to screen
self.draw()
# Events method
def events(self):
for event in pygame.event.get():
# Gets mouse position (x, y)
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# If the mouse is clicked then
if event.type == pygame.MOUSEBUTTONDOWN:
# If pause button is pressed pause the game
if self.pause_button.buttonPress(pos):
self.paused = True
# Loaded when pause button is pressed. Resumes the game
if self.play_button.buttonPress(pos):
self.paused = False
# Loaded when pause button is pressed. Restarts the game
if self.restart_button.buttonPress(pos):
self.paused = False
self.run()
# Loaded when pause button is pressed. Closes the game and returns to level select
if self.close_button.buttonPress(pos):
self.running = False
# Sets everything to 2x speed
if self.fast_forward_button.buttonPress(pos):
self.fastForward_counter += 1
if self.fastForward_counter % 2 == 0:
self.fastForward_check = 2
for en in self.enemies:
self.original_speed = en.speed
en.speed = en.speed * 2
self.spawn_frequency = self.spawn_frequency // 2
else:
self.fastForward_check = 1
for en in self.enemies:
en.speed = self.original_speed
self.spawn_frequency = self.spawn_frequency * 2
# Update method
def update(self):
# Counts the time since the main loop was loaded
self.spawn_timer += self.dt
# If 1 second has passed then
if self.spawn_timer >= 60:
# Removes one second from the current time
self.spawn_timer -= self.spawn_frequency
# Instantiates a wizard. CURRENTLY UNFINISHED. NEED TO MAKE A WAY TO ONLY SPAWN A SET AMOUNT
wizard1 = Wizard(5 * self.fastForward_check)
# Adds the new object to the sprite group
self.enemies.add(wizard1)
# Calls the method in Enemy class for updating the sprites in the sprite group
self.enemies.update()
def draw(self):
# If the game is not paused
if not self.paused:
self.paused_check = True
# Draws the background to the screen
self.screen.blit(self.background, (0, 0))
# Draws the enemies in the sprite group
for en in self.enemies:
en.draw(self.screen)
# If an enemy reaches the end of the path then remove the enemy.
if en.y > self.remove_coordinate:
self.enemies.remove(en)
# Draws the UI buttons for the game
for buttons in self.game_button_list:
buttons.draw(self.screen)
pygame.display.update()
# If paused
elif self.paused and self.paused_check:
self.paused_check = False
# Darkens the background
rectangle = pygame.Surface((1920, 1080))
rectangle.set_alpha(200) # alpha level
rectangle.fill((0, 0, 0)) # this fills the entire surface
self.screen.blit(rectangle, (0, 0))
# Draws a table to the middle of the screen
self.screen.blit(self.table, (960 - self.table_size[0] / 2, 540 - self.table_size[1] / 2))
# Draws the buttons on the table
for buttons in self.pause_button_list:
buttons.draw(self.screen)
pygame.display.update()
Code for my level_1
subclass:
from Game import Game
# Subclass Level 1 inherits Game's methods and attributes
class Level1(Game):
def __init__(self, screen):
super().__init__(screen)
self.path = [(-30, 783), (0, 783), (271, 767), (369, 471), (566, 414), (625, 352), (699, 138), (856, 93),
(1206, 93),
(1400, 46), (1500, 97), (1759, 97), (1784, 311), (1622, 434), (1487, 734), (1670, 789),
(1756, 842), (1782, 1016), (1782, 1200)]