不明白应该从父 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似乎有一个合理的描述。如果您不熟悉面向对象的概念,那么值得花时间阅读它。