Я создал игру Flappy Bird с системами NEAT, созданными python, следуя этому уроку с YouTube: Технология с Тимом
Когда я закончил весь код, где он запускал игра была запущена нормально, у меня на экране не было труб. Мой код указан ниже:
import pygame, neat, time, os, random
##import all of the variables we'll need for AI running
pygame.font.init()
##enables using fonts/text on screen.
WIN_WIDTH = 500
WIN_HEIGHT = 800
##define the size of the window. We're working with a GUI for flappy bird AI.
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]
##import the images of the bird from the images directory. There are three as to make up the states of flapping.
PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "pipe.png")))
##import the texture for the pipes from the same directory.
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "base.png")))
BG_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bg.png")))
##repeat for the ground and the background. They are seperate pieces as to allow for handling the collision with the ground being death.
STAT_FONT = pygame.font.SysFont("comicsans", 50)
class Bird: ##define the bird and its system as a class.
IMGS = BIRD_IMGS ##easier system for calling back to our list of bird images
MAX_ROTATION = 25
ROT_VEL = 20
ANIMATION_TIME = 5
##some definitions to do with making sure the bird can't just start spinning around aimlessly instead of looking like it should be going forward.
def __init__(self, x, y): ##essentially telling the AI/game what the bird starts out as, and how to define each thing.
self.x = x ##the distance forward on the screen
self.y = y ##how high up on the screen the bird is
self.tilt = 0 ##the angle the bird is at
self.tick_count = 0 ##essentially handles all of the physics
self.vel = 0 ##speed.
self.height = self.y ##a bit more about the height
self.img_count = 0 ##which image of the bird to be automatically loading (pre-flight)
self.img = self.IMGS[0] ##references back to the IMGS variable at the start of the class, defining to the system that the 0 on the previous line means bird1.png
def jump(self):
self.vel = -10.5 ##tells the game where to start. 0,0 is in the top left. This is near to half way down.
self.tick_count = 0 ##keeps track of when the last jump was. All for physics of bird.
self.height = self.y ##where bird started from, where the bird is now.
def move(self):
self.tick_count += 1 ##it's jumped one, now the tick registers that.
d = self.vel*self.tick_count + 1.5*self.tick_count**2 ##this is literally just a physics equation to calculate where the bird is going. Tells game how much player is moving up/down and how long player has been moving for.
if d >= 16: ##assigning terminal velocity. Going much faster can make the game unplayable. If going faster than 16 pixels per second, just start going 16 pixels per second.
d = 16 ##if d has gone higher than 16, just make it 16 again. Terminal velocity.
if d < 0: ##if for some reason, there's no velocity, such as at the very start of the game, give a very slight jump boost of -2 pixels (upwards 2)
d -= 2
self.y = self.y + d ##make sure the new Y position is calculated with the variable we just made.
if d < 0 or self.y < self.height + 50: ##assign bird tilting for model
if self.tilt < self.MAX_ROTATION:
self.tilt = self.MAX_ROTATION
else:
if self.tilt > -90: ##eventually, nosedive when going down.
self.tilt -= self.ROT_VEL
def draw(self, win): ##this one draws the bird and window onto the screen. It creates the window, adds the bird to it, etc. Kind of confusing, but I will try to keep the comments simple enough.
self.img_count += 1 ##this just checks how many times/how long the current image of the bird has been on screen.
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 + 1:
self.img = self.IMGS[0]
self.img_count = 0 ##essentially, this just does a check for how long each anim has been playing, and if it should change the image. It gets really complicated to actually explain though, so sorry.
if self.tilt <= -80:
self.img = self.IMGS[1]
self.img_count = self.ANIMATION_TIME*2 ##this prevents frames being skipped.
rotated_image = pygame.transform.rotate(self.img, self.tilt) ##this will rotate the bird using all of the logic we just defined above.
new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center) ##changes the center of rotation for the bird from the top left to the center of the screen.
win.blit(rotated_image, new_rect.topleft)
def get_mask(self):
return pygame.mask.from_surface(self.img) ##to be used for accurate collision later.
class Pipe: ##now we make the pipes.
GAP = 200 ##distance between each other.
VEL = 5 ##how fast they're moving towards the left.
def __init__(self, x):
self.x = x ##position as last time.
self.height = 0 ##height to be defined by a random system later.
self.gap = 100 ##gap again to be updated by a random system later.
self.top = 0 ##where the top pipe is. To be defined by random system later.
self.bottom = 0 ##same as prior, but for the bottom pipe instead.
self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True) ##flipping the top pipe. By default the model is upright, we need one going the other way as well.
self.PIPE_BOTTOM = PIPE_IMG ##here's the bottom pipe now imported.
self.passed = False ##has bird passed pipe? Default false
self.set_height() ##set the height for the pipe, dealt with later.
def set_height(self):
self.height = random.randrange(50, 450) ##random height for the pipes.
self.top = self.height - self.PIPE_TOP.get_height() ##defining the top pipe's height using the bottom pipe.
self.bottom = self.height + self.GAP ##defining the distance between the pipes
def move(self): ##defining the movement, just moving the pipes to the left.
self.x -= self.VEL
def draw(self, win): ##drawing the pipes as we want them to appear on screen
win.blit(self.PIPE_TOP, (self.x, self.top))
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
def collide(self, bird): ##this is all the code for PIXEL PERFECT collision. Instead of box collision, it checks to see if coloured pixels are overlapping to avoid deaths that didn't deserve to be deaths.
bird_mask = bird.get_mask()
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM) ##masks the images, allowing to spot pixels from blank space.
top_offset = (self.x - bird.x, self.top - round(bird.y)) ##checks distance from bird, round is because we cannot have decimal numbers, so it rounds to nearest whole
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
b_point = bird_mask.overlap(bottom_mask, bottom_offset) ##checks the first point of overlapping for bird and bottom pipe
t_point = bird_mask.overlap(top_mask, top_offset) ##repeat for top pipe
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): ##same as pipe and bird, defining all of the variables.
self.y = y
self.x1 = 0
self.x2 = self.WIDTH
def move(self): ##defining movement speed to velocity.
self.x1 -= self.VEL
self.x2 -= self.VEL
if self.x1 + self.WIDTH < 0: ##it creates two versions of the image, one directly behind the other, therefore it looks like one continuous image. If one has left the screen completely, move it back to behind the other one and get going again.
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)) ##set up the background image for the game
for pipe in pipes:
pipe.draw(win) ##draw the pipes
base.draw(win) ##draw the floor into the window
for bird in birds:
bird.draw(win) ##draw the bird into the window
pygame.display.update() ##update the display
text = STAT_FONT.render("Score: " + str(score), 1, (255,255,255))
win.blit(text, (WIN_WIDTH - text.get_width() - 15, 10))
def main(genomes, config):
nets = []
ge = []
birds = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
birds.append(Bird(230, 350))
g.fitness = 0
ge.append(g)
base = Base(730) ##place the base of the window at 730
pipes = [Pipe(600)]
score = 0
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) ##define the window.
clock = pygame.time.Clock() ##start a clock so that the game runs at a consistent speed and is not relative to FPS(*COUGH* BETHESDA AND FALLOUT 76 *COUGH*)
run = True ##by default of course, the game is running.
while run: ##while the game is running. Pygame doesn't like to close without being informed prior.
clock.tick(30) ##define the speed of that clock.
for event in pygame.event.get(): ##keeps track of whenever something is triggered, such as user clicking mouse, or AI activating output
if event.type == pygame.QUIT: ##check if the active event is a quit-based event to close the game.
run = False ##tell run it is now false as to allow the while loop to stop.
pygame.quit() ##when any quit function, such as clicking the red X is triggered (windows) it will close the game, and tell pygame and python to shut off.
quit()
pipe_ind = 0
if len(birds) > 0:
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
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()
add_pipe = False
base.move() ##moves the base.
rem = []
for pipe in pipes: ##it gets each pipe option in the list of pipes
for x, bird in enumerate(birds):
if pipe.collide(bird): ##if the pipe collides with the bird using specified collision in that section, kill player
ge[x].fitness -= 1
birds.pop(x)
nets.pop(x)
ge.pop(x)
if not pipe.passed and pipe.x < bird.x: ##once the pipe has been passed by the bird, add a new one.
pipe.passed = True
add_pipe = True
if pipe.x + pipe.PIPE_TOP.get_width() < 0: ##once the pipes has left the screen, remove the pipe, add it to a list of removed pipes
rem.append(pipe)
pipe.move() ##enable pipe movement.
if add_pipe: ##add one score to the player once the pipe has been passed.
score += 1
for g in ge:
g.fitness += 5
pipes.append(Pipe(600))
for r in rem:
pipes.remove(r)
for x, bird in enumerate(birds):
if bird.y + bird.img.get_height() >= 730 or bird.y < 0: ##handling for bird hitting floor.
birds.pop(x)
nets.pop(x)
ge.pop(x)
draw_window(win, birds, pipes, base, score) ##calls the above draw window stuff
def run(config_path): ##start running the new config.
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) ##pull in a ton of stuff for NEAT to work with
p = neat.Population(config) ##set our population (how many AIs will go per generation/how many birds are on screen at once. The config is 100, as this is best for evolution-performance.)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main,50)
if __name__ == "__main__":##loading in the config file to create the network.
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config-feedforward.txt")
run(config_path)
Есть идеи, почему это не работает? Спасибо.