Pygame元组对象没有属性

时间:2020-05-12 17:40:06

标签: python pygame

我正在学习如何在pygame中制作游戏,然后使用AI进行游戏。我正在尝试通过看一些YouTube教程来构建一个飞扬的小鸟AI游戏

我已经在类Bird中定义了此方法

 def get_mask(self):
       return pygame.mask.from_surface(self.img)

我在Pipe类中叫它

def collide(self, bird, win):
        bird_mask=bird.get_mask(self.img)

但是,出现以下错误

AttributeError: 'tuple' object has no attribute 'get_mask'

我尝试了几种方法来解决此问题,但是我似乎无法理解我做错了什么。

这是完整的代码

import pygame
import random
import pickle
import neat
import time
import os

pygame.font.init()

WIN_WIDTH = 600
WIN_HEIGHT = 800

WIN = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption("Flappy Bird AI")


PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs","pipe.png")).convert_alpha())
BG_IMG = pygame.transform.scale(pygame.image.load(os.path.join("imgs","bg.png")).convert_alpha(), (600, 900))
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs","bird" + str(x) + ".png"))) for x in range(1,4)]
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs","base.png")).convert_alpha())
STAT_FONT = pygame.font.SysFont("comicsans",50)

class Bird:
    IMGS = BIRD_IMGS
    MAX_ROTATION = 25
    ROT_VEL = 20
    ANIMATION_TIME = 5

    def __init__(self,x,y):
        self.x = x #Starting position
        self.y = y #Starting position
        self.tilt = 0 #Amount of image tilt. Start at 0
        self.tick_count = 0 #Jumping and falling of bird
        self.vel = 0 #Start at 0 because bird is not moving
        self.height = self.y
        self.img_count = 0 #Choose which image to show
        self.img = self.IMGS[0] #Reference BIRD_IMGS to show image. Start with the 1st

    def jump(self):
        self.vel = -10.5 #Negative velocity makes the bird go up
        self.tick_count = 0 #Keeps track of last jump
        self.height = self.y #Keep track of where the bird last jumped

    def move(self):
        self.tick_count += 1 #Number of times moved since the last jump
        #Displacement
        d = self.vel*self.tick_count + 1.5*self.tick_count**2

        if d >= 16: #Don't move down by more than 16
            d = 16
        if d < 0: #Move up a little bit more if already moving up
            d -= 2
        #Change y position based on displacement
        self.y = self.y + d

        if d < 0 or self.y < self.height + 50: #If bird is moving upwards
            if self.tilt < self.MAX_ROTATION:
                self.tilt=self.MAX_ROTATION
        else: #If bird is moving downwards
            if self.tilt > -90: #Tilt the bird 90 degrees
                self.tilt -= self.ROT_VEL

    def draw(self, win):
        self.img_count +=1 #Number of times one image is shown

        #Choose which image to show
        if self.img_count < self.ANIMATION_TIME:
            self.img=self.IMGS[0]
        elif self.img_count < self.ANIMATION_TIME*2:
            self.img=self.IMGS[1]
        elif self.img_count < self.ANIMATION_TIME*3:
            self.img=self.IMGS[2]
        elif self.img_count < self.ANIMATION_TIME*4:
            self.img=self.IMGS[1]
        elif self.img_count < self.ANIMATION_TIME*4 + 1:
            self.img=self.IMGS[0]
            self.img_count=0

        if self.tilt <= -80: #Bird going down
            self.img=self.IMGS[1] #Show the img with wings leveled
            self.img_count=self.ANIMATION_TIME*2 #Don't skip a frame while jumping

        rotated_image=pygame.transform.rotate(self.img, self.tilt)
        new_rect=rotated_image.get_rect(center=self.img.get_rect(topleft=(self.x, self.y)).center)
        win.blit(rotated_image,new_rect.topleft)

    def get_mask(self):
       return pygame.mask.from_surface(self.img)


class Pipe():
    GAP=200
    VEL=5 #Velocity of pipe

    def __init__(self,x): #No y because the height will be random
        self.x=x
        self.height=0
        self.gap=100
        self.top=0
        self.bottom=0
        self.PIPE_TOP=pygame.transform.flip(PIPE_IMG,False,True)
        self.PIPE_BOTTOM=PIPE_IMG
        self.passed=False
        self.set_height()
        self.img = self.IMGS[0]

    def set_height(self): #Randomly define the top and bottom of the pipe
        self.height=random.randrange(50,450)
        self.top=self.height-self.PIPE_TOP.get_height()
        self.bottom=self.height+self.GAP

    def move(self):
        self.x -= self.VEL

    def draw(self,win):
        win.blit(self.PIPE_TOP,(self.x,self.top))
        win.blit(self.PIPE_BOTTOM,(self.x,self.bottom))

    def collide(self, bird, win):
        bird_mask=bird.get_mask()
        top_mask=pygame.mask.from_surface(self.PIPE_TOP)
        bottom_mask=pygame.mask.from_surface(self.PIPE_BOTTOM)

        top_offset = (self.x - bird.x, self.top - round(bird.y))
        bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
        #Check collision of bird with top and bottom pipe
        b_point=bird_mask.overlap(bottom_mask,bottom_offset)
        t_point=bird_mask.overlap(top_mask,top_offset)

        if t_point or b_point:
            return True
        return False

class Base:
    VEL=5
    WIDTH=BASE_IMG.get_width()
    IMG=BASE_IMG

    def __init__(self,y): #No x becase it moves to the left
        self.y=y
        self.x1=0 #Start first image at 0
        self.x2=self.WIDTH #Start second image behind the first

    def move(self):
        self.x1 -= self.VEL #Image 1
        self.x2 -= self.VEL #Image 2

        #Cycle two images one after another if it goes off the screen
        if self.x1 + self.WIDTH < 0:
            self.x1=self.x2 + self.WIDTH

        if self.x2 + self.WIDTH < 0:
            self.x2 = self.x1 + self.WIDTH

    def draw(self,win):
        win.blit(self.IMG,(self.x1,self.y))
        win.blit(self.IMG,(self.x2,self.y))

def draw_window(win,birds,pipes,base,score):
    win.blit(BG_IMG, (0,0))

    for pipe in pipes:
        pipe.draw(win)

    text=STAT_FONT.render("Score: " + str(score),1,(255,255,255))
    win.blit(text,(WIN_WIDTH - 10 - text.get_width(),10))

    base.draw(win)

    for bird in birds:
        bird.draw(win)

    #bird.draw(win)
    pygame.display.update()

def main(genomes,config):

    global WIN, gen
    win = WIN
    #gen += 1

    nets=[] #Keep track of the neural network that controls each bird
    ge=[] #Keep track of each bird
    #bird=Bird(230,350)
    birds=[] #Create multiple birds

    for _, g in genomes:
        net=neat.nn.FeedForwardNetwork.create(g,config) #Create the neural network
        nets.append(net) #Append NN to the list
        birds.append(Bird(230,350)) #Create a bird object that starts at the same position as other birds
        g.fitness=0 #Initial fitness
        ge.append(g)

    base=Base(730)
    pipes=[Pipe(650)]
    win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
    clock = pygame.time.Clock()
    score=0

    run=True
    while run:
        clock.tick(30)
        for event in pygame.event.get():
            if event.type==pygame.QUIT:
                run=False
                pygame.quit()
                quit()

        #In case there are 3 or more pipes on the screen at the same time, we have to still consider
        #the distance of the first 2 pipes
        pipe_ind=0
        if len(birds) > 0:
            #Change the pipe the bird is looking at to  the second pipe in the list
            if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
                pipe_ind=1
        else:
            run = False
            break

        for x, bird in enumerate(birds):
            bird.move()
            ge[x].fitness += 0.1 #Give the bird some fitness so it survives for a little while

            #Activate the NN with inputs
            output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))

            if output[0] > 0.5:
                bird.jump()

        #bird.move()
        add_pipe=False
        rem=[]
        for pipe in pipes:
            for bird in enumerate(birds):
                if pipe.collide(bird,win):
                    ge[x].fitness -= 1 #If the bird hits the pipe, deduct the score
                    #Remove underperforming birds and don't use them in the next interation
                    birds.pop(x)
                    nets.pop(x)
                    ge.pop(x)

                #Check if bird has passed the pipe
                if not pipe.passed and pipe.x < bird.x:
                    pipe.passed=True
                    add_pipe=True

            #If pipe moves off screen append them to the removed list
            if pipe.x + pipe.PIPE_TOP.get_width() < 0:
                rem.append(pipe)

            pipe.move()

        if add_pipe:
            score += 1
            #Increse the fitness score
            for g in ge:
                g.fitness += 5
            pipes.append(Pipe(650)) #Create new pipe

        #Remove off screen pipes
        for r in rem:
            pipes.remove(r)

        for x, bird in enumerate(birds):
            #Check if bird hits the ground or flies all the way up
            if bird.y + bird.img.get_height() >= 730 or bird.y < 0:
                birds.pop(x)
                nets.pop(x)
                ge.pop(x)

        base.move()
        draw_window(win,birds,pipes,base,score)

def run(config_path):
    config=neat.config.Config(neat.DefaultGenome,neat.DefaultReproduction,neat.DefaultSpeciesSet,neat.DefaultStagnation,config_path)

    p=neat.Population(config)

    p.add_reporter(neat.StdOutReporter(True))
    stats=neat.StatisticsReporter()
    p.add_reporter(stats)

    winner=p.run(main,50)

if __name__ == "__main__":
    local_dir=os.path.dirname(__file__)
    config_path=os.path.join(local_dir,"config-feedforward.txt")
    run(config_path)

感谢您的帮助。谢谢!

1 个答案:

答案 0 :(得分:2)

您在这里丢失了一些东西

for pipe in pipes:
    for bird in enumerate(birds):

当您从enumerate()仅收到一个变量时,您收到的是一个元组

bird =(0,bird)

所以,当您执行

if pipe.collide(bird,win):-> bird_mask = bird.get_mask()

您实际上正在做bird_mask = (0, bird<object>).get_mask()

解决方案将更改for结构,以像您在此处一样从enumerate()接收两个vars

for x, bird in enumerate(birds):

或更改碰撞功能

bird_mask=bird[1].get_mask()