在 Pygame 中为无尽的跑酷游戏制作障碍系统

Making an Obstacle System for an Endless Runner Game in Pygame

我正在尝试在 Python pygame 中复制 google chrome dinosaur game。我对 pygame 没有太多经验。我设法创建了所有的恐龙精灵动画(行走、跳跃和弯曲),现在我正在尝试添加障碍物。所以我想做的是当用户的分数超过 35 时,障碍物将开始随机生成。问题是,当分数超过 35 时,障碍物层层叠叠,有的不断出现又消失,看起来很糟糕,非常滞后。我怎样才能更顺利地渲染障碍物?代码的相关部分:

import pygame
import random
import os
from threading import Thread
pygame.init()

w, h = pygame.display.get_surface().get_size()
imgX = w
def generate_obstacles():
    global imgX
    obstacles_tuple = (obstacle1, obstacle2, obstacle3, obstacle4, obstacle5)
    chosen_obstacle = random.choice(obstacles_tuple)
    try:
        if WINDOW.get_width() >= imgX > 0:
            WINDOW.blit(chosen_obstacle, (imgX, 323))
        else:
            imgX = w
    except pygame.error:
        quit()

    pygame.display.update()


def draw():
    WINDOW.fill("#FFFFFF")
    pygame.display.update()


r1, r2 = 5000, 7000
def timer(score):
    global r1, r2
    timer_running = True
    while timer_running:
        rand_time = random.randint(r1, r2)
        pygame.time.delay(rand_time)
        generate_obstacles()

        if r1 >= 500 and r2 >= 500:
            if score % 100 == 0:
                r1 -= 20
                r2 -= 20
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            break



game_intro(True)
run = True
my_sprite = Dino()
speed = 5
while run:
    pygame.time.delay(100)
    bgX -= speed
    bgX2 -= speed
    imgX -= speed
    if bgX < floor.get_width() * -1:
        bgX = floor.get_width()
    if bgX2 < floor.get_width() * -1:
        bgX2 = floor.get_width()
    for event in pygame.event.get():
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            with open("highscore.txt", "w") as f:
                f.write(str(my_sprite.high_score))
            run = False
            pygame.quit()
            quit()

        if event.type == pygame.USEREVENT+1:
            my_sprite.fall()

        if (keys[pygame.K_UP] or keys[pygame.K_SPACE]) and not my_sprite.is_jumping:
            my_sprite.jump()

        if keys[pygame.K_DOWN]:
            my_sprite.bend()

    draw()
    my_sprite.walk()

    if my_sprite.score >= 35:
        Thread(target=lambda: timer(my_sprite.score)).start()

我也做了一个video,这样你可以更好地理解问题是什么。

完整代码:

import pygame
import random
import os
from threading import Thread
pygame.init()

WINDOW = pygame.display.set_mode((500, 500))
pygame.display.set_caption("Dinosaur Game")
walk1_image = pygame.image.load("images/walk1.png")
walk1 = pygame.transform.scale(walk1_image, (64, 64))
bend1_image = pygame.image.load("images/bend1.png")
bend1 = pygame.transform.scale(bend1_image, (64, 64))
bend2_image = pygame.image.load("images/bend2.png")
bend2 = pygame.transform.scale(bend2_image, (64, 64))
walk2_image = pygame.image.load("images/walk2.png")
walk2 = pygame.transform.scale(walk2_image, (64, 64))
die_image = pygame.image.load("images/die.png")
die = pygame.transform.scale(die_image, (64, 64))
jump_image = pygame.image.load("images/jump.png")
jump = pygame.transform.scale(jump_image, (64, 64))

images_list = [walk1, walk2, die, jump, bend1]
floor_image = pygame.image.load("obstacles/floor-0.png").convert()
floor = pygame.transform.scale(floor_image, (500, 100))
bgX = 0
bgX2 = floor.get_width()
in_main = False

obstacle1_image = pygame.image.load("obstacles/obstacle1.png")
obstacle1 = pygame.transform.scale(obstacle1_image, (78, 78))
obstacle2_image = pygame.image.load("obstacles/obstacle2.png")
obstacle2 = pygame.transform.scale(obstacle2_image, (156, 78))
obstacle3_image = pygame.image.load("obstacles/obstacle3.png")
obstacle3 = pygame.transform.scale(obstacle3_image, (64, 64))
obstacle4_image = pygame.image.load("obstacles/obstacle4.png")
obstacle4 = pygame.transform.scale(obstacle4_image, (128, 64))
obstacle5_image = pygame.image.load("obstacles/obstacle5.png")
obstacle5 = pygame.transform.scale(obstacle5_image, (192, 64))

def game_intro(intro):
    while intro:
        WINDOW.fill("#FFFFFF")
        my_font = pygame.font.SysFont("comicsans", 40)
        label = my_font.render("Press space to play", True, (105, 105, 105))
        WINDOW.blit(jump, (125, 200))
        WINDOW.blit(label, (125, 300))
        my_font = pygame.font.SysFont("comicsans", 20)
        label2 = my_font.render("Made by: Roni", True, (0, 0, 0))
        WINDOW.blit(label2, (10, 480))
        pygame.display.update()
        for event2 in pygame.event.get():
            keys2 = pygame.key.get_pressed()
            if event2.type == pygame.QUIT or keys2[pygame.K_ESCAPE]:
                intro = False
                pygame.quit()
                quit()

            if keys2[pygame.K_SPACE]:
                if not os.path.isfile("highscore.txt"):
                    f = open("highscore.txt", "w")
                    f.write("0")
                    f.close()
                intro = False
                return


class Dino(pygame.sprite.Sprite):
    def __init__(self):
        super(Dino, self).__init__()
        self.images = [walk1, walk2]
        self.index = 0
        self.image = self.images[self.index]
        self.x = 200
        self.y = 323
        self.is_falling = False
        self.is_jumping = False
        self.is_bend = False
        self.is_down = False
        self.score = 0
        self.high_score = 0

    def walk(self):
        self.update_score()
        WINDOW.blit(floor, (bgX, 300))
        WINDOW.blit(floor, (bgX2, 300))
        self.score += 1

        if self.is_jumping:
            WINDOW.blit(jump, (self.x, self.y))
        elif self.is_bend:
            bend_images = [bend1, bend2]
            keys3 = pygame.key.get_pressed()
            self.index += 1
            if self.index >= len(self.images):
                self.index = 0
            bend = bend_images[self.index]
            if keys3[pygame.K_DOWN]:
                WINDOW.blit(bend, (self.x, self.y))
            else:
                self.is_bend = False
                self.walk()
        else:
            self.index += 1
            if self.index >= len(self.images):
                self.index = 0
            self.image = self.images[self.index]
            WINDOW.blit(self.image, (self.x, self.y))

        pygame.display.update()

    def jump(self):
        self.is_jumping = True
        for i in range(3):
            self.y -= 20
            self.walk()
            pygame.time.delay(10)
            WINDOW.fill("#FFFFFF")

        self.is_falling = True
        pygame.time.set_timer(pygame.USEREVENT+1, 1000)

    def fall(self):
        if self.is_falling:
            while self.y != 323:
                self.y += 20
                self.walk()
                WINDOW.fill("#FFFFFF")
                self.is_falling = False
                self.is_jumping = False
                pygame.display.update()

    def bend(self):
        self.is_bend = True
        self.walk()

    def update_score(self):
        my_font = pygame.font.SysFont("comicsans", 40)
        zeros = 5 - len(str(self.score))
        str_score = "0" * zeros + str(self.score)
        label = my_font.render(str_score, True, (0, 0, 0))
        WINDOW.blit(label, (410, 0))

        with open("highscore.txt", "r") as f:
            self.high_score = int(f.read())

        if self.high_score <= self.score:
            self.high_score = self.score

        zeros2 = 5 - len(str(self.high_score))
        str_score2 = "0" * zeros2 + str(self.high_score)
        label2 = my_font.render("HI: " + str_score2, True, (0, 0, 0))
        WINDOW.blit(label2, (250, 0))
        pygame.display.update()


w, h = pygame.display.get_surface().get_size()
imgX = w
def generate_obstacles():
    global imgX
    obstacles_tuple = (obstacle1, obstacle2, obstacle3, obstacle4, obstacle5)
    chosen_obstacle = random.choice(obstacles_tuple)
    try:
        if WINDOW.get_width() >= imgX > 0:
            WINDOW.blit(chosen_obstacle, (imgX, 323))
        else:
            imgX = w
    except pygame.error:
        quit()

    pygame.display.update()


def draw():
    WINDOW.fill("#FFFFFF")
    pygame.display.update()


r1, r2 = 5000, 7000
def timer(score):
    global r1, r2
    timer_running = True
    while timer_running:
        rand_time = random.randint(r1, r2)
        pygame.time.delay(rand_time)
        generate_obstacles()

        if r1 >= 500 and r2 >= 500:
            if score % 100 == 0:
                r1 -= 20
                r2 -= 20
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            break



game_intro(True)
run = True
my_sprite = Dino()
speed = 5
while run:
    pygame.time.delay(100)
    bgX -= speed
    bgX2 -= speed
    imgX -= speed
    if bgX < floor.get_width() * -1:
        bgX = floor.get_width()
    if bgX2 < floor.get_width() * -1:
        bgX2 = floor.get_width()
    for event in pygame.event.get():
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            with open("highscore.txt", "w") as f:
                f.write(str(my_sprite.high_score))
            run = False
            pygame.quit()
            quit()

        if event.type == pygame.USEREVENT+1:
            my_sprite.fall()

        if (keys[pygame.K_UP] or keys[pygame.K_SPACE]) and not my_sprite.is_jumping:
            my_sprite.jump()

        if keys[pygame.K_DOWN]:
            my_sprite.bend()

    draw()
    my_sprite.walk()

    if my_sprite.score >= 35:
        Thread(target=lambda: timer(my_sprite.score)).start()

感谢@qouify,问题已解决。你可以看看他的回答,看看是什么解决了问题。完整更新代码:

import pygame
import random
import os
from threading import Thread
pygame.init()

WINDOW = pygame.display.set_mode((500, 500))
pygame.display.set_caption("Dinosaur Game")
walk1_image = pygame.image.load("Dino/walk1.png")
walk1 = pygame.transform.scale(walk1_image, (64, 64))
bend1_image = pygame.image.load("Dino/bend1.png")
bend1 = pygame.transform.scale(bend1_image, (64, 64))
bend2_image = pygame.image.load("Dino/bend2.png")
bend2 = pygame.transform.scale(bend2_image, (64, 64))
walk2_image = pygame.image.load("Dino/walk2.png")
walk2 = pygame.transform.scale(walk2_image, (64, 64))
die_image = pygame.image.load("Dino/die.png")
die = pygame.transform.scale(die_image, (64, 64))
jump_image = pygame.image.load("Dino/jump.png")
jump = pygame.transform.scale(jump_image, (64, 64))

images_list = [walk1, walk2, die, jump, bend1]
floor_image = pygame.image.load("Others/floor-0.png").convert()
floor = pygame.transform.scale(floor_image, (500, 100))
bgX = 0
bgX2 = floor.get_width()
in_main = False

obstacle1_image = pygame.image.load("Obstacles/obstacle1.png")
obstacle1 = pygame.transform.scale(obstacle1_image, (78, 78))
obstacle2_image = pygame.image.load("Obstacles/obstacle2.png")
obstacle2 = pygame.transform.scale(obstacle2_image, (156, 78))
obstacle3_image = pygame.image.load("Obstacles/obstacle3.png")
obstacle3 = pygame.transform.scale(obstacle3_image, (64, 64))
obstacle4_image = pygame.image.load("Obstacles/obstacle4.png")
obstacle4 = pygame.transform.scale(obstacle4_image, (128, 64))
obstacle5_image = pygame.image.load("Obstacles/obstacle5.png")
obstacle5 = pygame.transform.scale(obstacle5_image, (192, 64))

def game_intro(intro):
    while intro:
        WINDOW.fill("#FFFFFF")
        my_font = pygame.font.SysFont("comicsans", 40)
        label = my_font.render("Press space to play", True, (105, 105, 105))
        WINDOW.blit(jump, (125, 200))
        WINDOW.blit(label, (125, 300))
        my_font = pygame.font.SysFont("comicsans", 20)
        label2 = my_font.render("Made by: Roni Meirom", True, (0, 0, 0))
        WINDOW.blit(label2, (10, 480))
        pygame.display.update()
        for event2 in pygame.event.get():
            keys2 = pygame.key.get_pressed()
            if event2.type == pygame.QUIT or keys2[pygame.K_ESCAPE]:
                intro = False
                pygame.quit()
                quit()

            if keys2[pygame.K_SPACE]:
                if not os.path.isfile("highscore.txt"):
                    f = open("highscore.txt", "w")
                    f.write("0")
                    f.close()
                intro = False
                return


class Dino(pygame.sprite.Sprite):
    def __init__(self):
        super(Dino, self).__init__()
        self.images = [walk1, walk2]
        self.index = 0
        self.image = self.images[self.index]
        self.x = 200
        self.y = 323
        self.is_falling = False
        self.is_jumping = False
        self.is_bend = False
        self.is_down = False
        self.score = 0
        self.high_score = 0

    def walk(self):
        self.update_score()
        WINDOW.blit(floor, (bgX, 300))
        WINDOW.blit(floor, (bgX2, 300))
        self.score += 1

        if self.is_jumping:
            WINDOW.blit(jump, (self.x, self.y))
        elif self.is_bend:
            bend_images = [bend1, bend2]
            keys3 = pygame.key.get_pressed()
            self.index += 1
            if self.index >= len(self.images):
                self.index = 0
            bend = bend_images[self.index]
            if keys3[pygame.K_DOWN]:
                WINDOW.blit(bend, (self.x, self.y))
            else:
                self.is_bend = False
                self.walk()
        else:
            self.index += 1
            if self.index >= len(self.images):
                self.index = 0
            self.image = self.images[self.index]
            WINDOW.blit(self.image, (self.x, self.y))


    def jump(self):
        self.is_jumping = True
        for i in range(3):
            self.y -= 20
            self.walk()
            pygame.time.delay(10)
            WINDOW.fill("#FFFFFF")

        self.is_falling = True
        pygame.time.set_timer(pygame.USEREVENT+1, 1000)

    def fall(self):
        if self.is_falling:
            self.y += 20
            if self.y != 323:
                self.y = 323
                self.is_falling = False
                self.is_jumping = False

    def bend(self):
        self.is_bend = True
        self.walk()

    def update_score(self):
        my_font = pygame.font.SysFont("comicsans", 40)
        zeros = 5 - len(str(self.score))
        str_score = "0" * zeros + str(self.score)
        label = my_font.render(str_score, True, (0, 0, 0))
        WINDOW.blit(label, (410, 0))

        with open("highscore.txt", "r") as f:
            self.high_score = int(f.read())

        if self.high_score <= self.score:
            self.high_score = self.score

        zeros2 = 5 - len(str(self.high_score))
        str_score2 = "0" * zeros2 + str(self.high_score)
        label2 = my_font.render("HI: " + str_score2, True, (0, 0, 0))
        WINDOW.blit(label2, (250, 0))


w, h = pygame.display.get_surface().get_size()
imgX = w
def generate_obstacles():
    global imgX
    obstacles_tuple = (obstacle1, obstacle2, obstacle3, obstacle4, obstacle5)
    chosen_obstacle = random.choice(obstacles_tuple)
    try:
        if WINDOW.get_width() >= imgX > 0:
            WINDOW.blit(chosen_obstacle, (imgX, 323))
        else:
            imgX = w
    except pygame.error:
        quit()


r1, r2 = 5000, 7000
def timer(score):
    global r1, r2
    timer_running = True
    while timer_running:
        rand_time = random.randint(r1, r2)
        pygame.time.delay(rand_time)
        generate_obstacles()

        if r1 >= 500 and r2 >= 500:
            if score % 100 == 0:
                r1 -= 20
                r2 -= 20
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            break



game_intro(True)
run = True
my_sprite = Dino()
speed = 5
while run:
    bgX -= speed
    bgX2 -= speed
    imgX -= speed
    if bgX < floor.get_width() * -1:
        bgX = floor.get_width()
    if bgX2 < floor.get_width() * -1:
        bgX2 = floor.get_width()
    for event in pygame.event.get():
        keys = pygame.key.get_pressed()
        if event.type == pygame.QUIT or keys[pygame.K_ESCAPE]:
            with open("highscore.txt", "w") as f:
                f.write(str(my_sprite.high_score))
            run = False
            pygame.quit()
            quit()

        if event.type == pygame.USEREVENT+1:
            my_sprite.fall()

        if (keys[pygame.K_UP] or keys[pygame.K_SPACE]) and not my_sprite.is_jumping:
            my_sprite.jump()

        if keys[pygame.K_DOWN]:
            my_sprite.bend()

    WINDOW.fill("#FFFFFF")
    my_sprite.walk()

    if my_sprite.score >= 35:
        Thread(target=lambda: timer(my_sprite.score)).start()

    pygame.time.delay(100)
    pygame.display.update()

我怀疑您的代码存在一个问题,即 pygame.display.update() 可以同时被调用:

  • 通过draw函数的主程序;
  • 以及当得分大于 35 时通过 draw_obstacles 函数启动的线程。

这可以解释为什么有些障碍物出现和消失得很快。也许,只需删除函数 draw_obstacles 中的 pygame.display.update() 就可以解决这个问题。

另一个可以解释滞后的问题与您启动线程的条件有关。在我看来,一旦分数超过 35,您将在主循环的每次迭代中启动另一个线程,这意味着 lot 个线程。因此,除非您的代码中有某些条件可以防止这种情况发生,否则您应该添加一个。

[编辑]看完你的完整代码后我注意到了一些问题。

首先,如前所述,您应该 pygame.display.update() 在你的主循环中只有 一次WINDOW.fill 也一样 put 在你的代码中有几个地方。这就是造成效果的原因 物体出现和消失的速度非常快。

您的程序(以及每个 pygame 程序)应该具有以下内容 结构。

while run:
    process_events_and_change_objects_status()
    WINDOW.fill("#FFFFFF")
    redraw_everything()
    pygame.display.update()
    wait()

并且不应该有任何其他调用 WINDOW.fillpygame.display.update() 其他任何地方。

二、看fall方法:

def fall(self):
    if self.is_falling:
        while self.y != 323:
            self.y += 20
            self.walk()
            WINDOW.fill("#FFFFFF")
            self.is_falling = False
            self.is_jumping = False
            pygame.display.update()

在这里你试图同时做两件事:更新 对象状态并使用 self.walk 重绘所有内容。那很糟 因为,例如,如果当前屏幕上有障碍物, 他们不会被绘制,因为 self.walk 不关心障碍物。此外,这意味着在恐龙坠落时无法处理事件。 所以你必须将它更改为只更新对象状态 类似的东西。

def fall(self):
    if self.is_falling:
        self.y += 20
        if self.y != 323:
            self.is_falling = False
            self.is_jumping = False

然后重绘将由主循环中的self.walk管理。相同的 对于 bendjump.

所以我认为你需要修改你的程序结构才能 在您的代码中将绘图部分与处理明确分开 部分(处理事件和更新恐龙状态)。除此以外 如果你把两者混在一起,你将面临你现在面临的那种问题 并且您的代码将无法维护和发展。