不明白应该从父 class 继承的这个 AttributeError 的原因
Don't understand cause of this AttributeError which should be inherited from parent class
我对 OOP 和 Pygame 比较陌生,一直在尝试编写一款塔防游戏,但遇到了障碍。我不断收到此错误:
AttributeError: type object 'Game' has no attribute 'path'
我尝试阅读有同样问题的人的帖子,但是 none 的修复似乎对我有用。
我正在尝试在我的游戏中设置几个不同的关卡,这些关卡具有不同的背景和不同的路径。我试图通过创建一个父级 Game
class 然后为每个级别创建一个子级 class 来做到这一点。 (我程序中的每个 class 都有一个不同的 .py 文件。)理想情况下,每个级别 subclass 都有自己的 path
属性,该属性将覆盖 path
中的属性游戏class。然后 path
被传递到我的 Enemy
class 那里有让敌人跟随路径的代码。
我可以通过将 self.path
(在我的游戏中 class)放在构造函数上方并将其定义为 path
来修复我的错误。但是在这样做时,我无法或不知道如何覆盖 subclass.
中的属性
此外,在我的敌人 class 中,我试图通过将它放在较低的位置来规避与游戏 class 循环导入的问题,我认为这可能有与此有关,但是,我不确定。
如果是这种情况,是否有更好的方法让我的敌人 class 访问该路径?
这是我关卡的相关代码select 文件:
# 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()
这是我的相关代码 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
我的代码 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()
我的 level_1
子代码class:
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)]
问题是 Game
对象从未被实例化——也就是说,只有 Game
的定义,而不是 variable-version “复制” Game.__init__()
函数已被调用。显然,在调用该游戏初始化程序之前,成员变量 game.path
不存在(因为它是在 __init__()
中定义的)。
有两种解决方法。第一个是使 Game
对象的成员成为纯静态的:
class Game:
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)]
def __init__(self, screen):
self.path =
self.enemies = None
self.towers = None
这允许独立于任何初始化自由访问Game.path
。但是,看看您 class 的其余部分,这似乎不是它设计的工作方式。
因此,更好的方法是简单地实例化一个 Game
对象:
import Game
...
game = Game() # Create an instantiated Game object.
...
# Sets starting x and y as the first co-ordinates of the path
self.x = game.path[0][0]
self.y = game.path[0][1]
Python对象实例化here似乎有一个合理的描述。如果您不熟悉面向对象的概念,那么值得花时间阅读它。
我对 OOP 和 Pygame 比较陌生,一直在尝试编写一款塔防游戏,但遇到了障碍。我不断收到此错误:
AttributeError: type object 'Game' has no attribute 'path'
我尝试阅读有同样问题的人的帖子,但是 none 的修复似乎对我有用。
我正在尝试在我的游戏中设置几个不同的关卡,这些关卡具有不同的背景和不同的路径。我试图通过创建一个父级 Game
class 然后为每个级别创建一个子级 class 来做到这一点。 (我程序中的每个 class 都有一个不同的 .py 文件。)理想情况下,每个级别 subclass 都有自己的 path
属性,该属性将覆盖 path
中的属性游戏class。然后 path
被传递到我的 Enemy
class 那里有让敌人跟随路径的代码。
我可以通过将 self.path
(在我的游戏中 class)放在构造函数上方并将其定义为 path
来修复我的错误。但是在这样做时,我无法或不知道如何覆盖 subclass.
此外,在我的敌人 class 中,我试图通过将它放在较低的位置来规避与游戏 class 循环导入的问题,我认为这可能有与此有关,但是,我不确定。
如果是这种情况,是否有更好的方法让我的敌人 class 访问该路径?
这是我关卡的相关代码select 文件:
# 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()
这是我的相关代码 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
我的代码 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()
我的 level_1
子代码class:
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)]
问题是 Game
对象从未被实例化——也就是说,只有 Game
的定义,而不是 variable-version “复制” Game.__init__()
函数已被调用。显然,在调用该游戏初始化程序之前,成员变量 game.path
不存在(因为它是在 __init__()
中定义的)。
有两种解决方法。第一个是使 Game
对象的成员成为纯静态的:
class Game:
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)]
def __init__(self, screen):
self.path =
self.enemies = None
self.towers = None
这允许独立于任何初始化自由访问Game.path
。但是,看看您 class 的其余部分,这似乎不是它设计的工作方式。
因此,更好的方法是简单地实例化一个 Game
对象:
import Game
...
game = Game() # Create an instantiated Game object.
...
# Sets starting x and y as the first co-ordinates of the path
self.x = game.path[0][0]
self.y = game.path[0][1]
Python对象实例化here似乎有一个合理的描述。如果您不熟悉面向对象的概念,那么值得花时间阅读它。