Building a Side-Scrolling Runner Game Using Pygame

import pygame
import random
from itertools import cycle

# Window dimensions
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 400
GROUND_LEVEL = 280

class Player:
    def __init__(self):
        self.animation_cycle = cycle([0, 1, 2])
        self.frames = (
            pygame.image.load("image/zyp1.png").convert_alpha(),
            pygame.image.load("image/zyp1.png").convert_alpha(),
            pygame.image.load("image/zyp1.png").convert_alpha(),
        )
        self.jump_sound = pygame.mixer.Sound('audio/zyp.mp3')
        self.rect = self.frames[0].get_rect()
        self.x_pos = 50
        self.y_pos = GROUND_LEVEL
        self.rect.topleft = (self.x_pos, self.y_pos)
        self.is_jumping = False
        self.velocity_y = 0
        self.jump_power = 13
        self.gravity = 0.8

    def start_jump(self):
        if not self.is_jumping:
            self.is_jumping = True
            self.velocity_y = -self.jump_power
            self.jump_sound.play()

    def update_position(self):
        if self.is_jumping:
            self.rect.y += self.velocity_y
            self.velocity_y += self.gravity
            if self.rect.y >= GROUND_LEVEL:
                self.rect.y = GROUND_LEVEL
                self.is_jumping = False
                self.velocity_y = 0

    def render(self, surface):
        frame_index = next(self.animation_cycle)
        surface.blit(self.frames[frame_index], (self.x_pos, self.rect.y))


class ScrollingBackground:
    def __init__(self, offset_x, offset_y):
        self.texture = pygame.image.load("image/bg.png").convert_alpha()
        self.x = offset_x
        self.y = offset_y

    def shift(self, speed):
        self.x -= speed
        if self.x <= -DISPLAY_WIDTH:
            self.x = DISPLAY_WIDTH

    def render(self, surface):
        surface.blit(self.texture, (self.x, self.y))


class Hazard:
    SPEED_BASE = 5
    GROUND_OFFSET = 150

    def __init__(self):
        self.dino_sprite = pygame.image.load("image/konglong1.png").convert_alpha()
        self.beast_sprite = pygame.image.load("image/daguaishou.png").convert_alpha()
        self.digit_sprites = tuple(
            pygame.image.load(f"image/{n}.png").convert_alpha() for n in range(10)
        )
        self.collect_audio = pygame.mixer.Sound("audio/zyp22.mp3")
        variant = random.randint(0, 1)
        if variant == 0:
            self.sprite = self.dino_sprite
            self.speed = 15
            self.y_offset = 100
        else:
            self.sprite = self.beast_sprite
            self.speed = self.SPEED_BASE
            self.y_offset = self.GROUND_OFFSET
        self.rect = self.sprite.get_rect()
        self.rect.center = (DISPLAY_WIDTH + 50, self.y_offset)

    def advance(self):
        self.rect.x -= self.speed

    def render(self, surface):
        surface.blit(self.sprite, (self.rect.x, self.rect.y))

    @property
    def off_screen(self):
        return self.rect.x < -self.rect.width


def run_game():
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
    pygame.display.set_caption("Dino Runner")

    player = Player()
    bg_layer1 = ScrollingBackground(0, 0)
    bg_layer2 = ScrollingBackground(DISPLAY_WIDTH, 0)
    obstacles = []
    spawn_timer = 0
    score = 0
    game_active = True
    scrolling_speed = 5

    while True:
        dt = clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            if game_active and event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    player.start_jump()
                if event.key == pygame.K_r and not game_active:
                    # restart
                    obstacles.clear()
                    player.rect.y = GROUND_LEVEL
                    player.is_jumping = False
                    player.velocity_y = 0
                    score = 0
                    spawn_timer = 0
                    game_active = True

        if game_active:
            bg_layer1.shift(scrolling_speed)
            bg_layer2.shift(scrolling_speed)
            bg_layer1.render(screen)
            bg_layer2.render(screen)

            player.update_position()
            player.render(screen)

            spawn_timer += dt
            if spawn_timer >= 1300 and random.randint(0, 100) > 40:
                obstacles.append(Hazard())
                spawn_timer = 0

            for hazard in obstacles[:]:
                hazard.advance()
                hazard.render(screen)
                if hazard.off_screen:
                    obstacles.remove(hazard)
                    score += 1
                if player.rect.colliderect(hazard.rect):
                    game_active = False

            font = pygame.font.Font(None, 36)
            score_surf = font.render(f"Score: {score}", True, (255, 255, 255))
            screen.blit(score_surf, (10, 10))

        else:
            screen.fill((30, 30, 30))
            font_large = pygame.font.Font(None, 64)
            msg = font_large.render("GAME OVER", True, (200, 0, 0))
            screen.blit(msg, (DISPLAY_WIDTH // 2 - 120, DISPLAY_HEIGHT // 2 - 30))
            prompt = pygame.font.Font(None, 28).render("Press R to restart", True, (200, 200, 200))
            screen.blit(prompt, (DISPLAY_WIDTH // 2 - 80, DISPLAY_HEIGHT // 2 + 30))

        pygame.display.flip()


if __name__ == "__main__":
    run_game()

Tags: pygame game development python 2D Game Side-Scroller

Posted on Thu, 13 Aug 2026 16:26:27 +0000 by SCook