[Python] Making an Obstacle System for an Endless Runner Game in Pygame

Discussão em 'Python' iniciado por Stack, Junho 30, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to make a clone of google chrome dinosaur game in Python pygame. I don't have much experience with pygame. I managed to create all of the dinosaur sprite animations (walking, jumping and bending) and now I'm trying to add the obstacles. So what I want to do is when the user's score surpasses 35, obstacles will start being randomly generated. The problem is that when the score surpasses 35, obstacles appear on top of each other, some appear and disappear constantly and it just looks bad and very laggy. How can I render the obstacles more smoothly? The relevant parts of the code:

    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()


    I also made a video so you can get a better understanding of what the problem is.

    Full code:

    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()


    The problem was solved thanks to @qouify. You can look at his answer to see what solved the problem. Full updated code:

    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()

    Continue reading...

Compartilhe esta Página