Menu
CoddyTech

Menu cho Người chạy trên trời

Thuộc phần Luật chơi trong hành trình Pygame của Coddy. Bài học 29 trên 30.

Sky Runner lấy luật chơi từ phần này, bắt đầu bằng một menu. Một ván mới cần đưa mọi thứ về trạng thái ban đầu, vậy nên hãy đặt phần đó vào reset(): một người chạy mới, một nhóm trống, tốc độ và điểm số, cùng cả hai bộ hẹn giờ được khởi động lại.

Giờ đây, phím cách đảm nhiệm hai việc, mỗi việc ứng với một trạng thái:

if state == "play":
    runner.jump()
elif state == "menu":
    reset()
    state = "play"

Menu cần hai dòng được căn giữa, còn màn hình kết thúc trò chơi sẽ cần ba dòng, vì vậy một hàm nhỏ sẽ vẽ một dòng căn giữa tại x 320:

def show(text, text_font, y):
    picture = text_font.render(text, True, INK)
    screen.blit(picture, picture.get_rect(center=(320, y)))

Điểm số thuộc về một ván chơi, vì vậy chỉ hiển thị điểm sau khi ván chơi bắt đầu, không hiển thị trên menu.

challenge icon

Thử thách

Trung bình

Thêm menu cho Sky Runner.

  1. Viết reset() cho nhân vật chạy, nhóm, speed, score và cả hai bộ hẹn giờ; gọi hàm này trước vòng lặp và đặt state = "menu".
  2. Trong menu, dùng show() để hiển thị Sky Runner bằng big_font và Press Space to start; chỉ hiển thị điểm số khi không ở trong menu.
  3. Nhấn Space trong menu sẽ gọi reset() và đặt state = "play".

Nhấn Chơi: Space bắt đầu lượt chơi. Sau đó, nhấn Kiểm tra.

Tự làm thử

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((640, 320))
pygame.display.set_caption("Sky Runner")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 32)

SKY = (140, 200, 240)
GROUND = (90, 160, 80)
GROUND_Y = 260
GRAVITY = 1
SPAWN = pygame.USEREVENT + 1
POINT = pygame.USEREVENT + 2


class Runner(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.frames = [
            pygame.image.load("assets/images/player.png").convert_alpha(),
            pygame.image.load("assets/images/player_walk.png").convert_alpha(),
        ]
        self.image = self.frames[0]
        self.step = 0
        self.rect = self.image.get_rect(midbottom=(100, GROUND_Y))
        self.vy = 0

    def jump(self):
        if self.rect.bottom == GROUND_Y:
            self.vy = -16

    def update(self):
        self.vy += GRAVITY
        self.rect.y += self.vy
        if self.rect.bottom >= GROUND_Y:
            self.rect.bottom = GROUND_Y
            self.vy = 0
        if self.rect.bottom == GROUND_Y:
            self.step += 1
            self.image = self.frames[self.step // 8 % 2]
        else:
            self.image = self.frames[1]


class Obstacle(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = random.choice(obstacle_images)
        self.rect = self.image.get_rect(midbottom=(664, GROUND_Y))

    def update(self):
        self.rect.x -= speed
        if self.rect.right < 0:
            self.kill()


obstacle_images = [
    pygame.image.load("assets/images/spike_ball.png").convert_alpha(),
    pygame.image.load("assets/images/blob.png").convert_alpha(),
]
runner = Runner()
obstacles = pygame.sprite.Group()
speed = 6
pygame.time.set_timer(SPAWN, 1500)
pygame.time.set_timer(POINT, 100)
score = 0
state = "play"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN and event.key in (pygame.K_SPACE, pygame.K_UP):
            if state == "play":
                runner.jump()
        if event.type == SPAWN and state == "play":
            obstacles.add(Obstacle())
            pygame.time.set_timer(SPAWN, random.randint(1000, 2000))
        if event.type == POINT and state == "play":
            score += 1

    if state == "play":
        speed = min(14, 6 + score // 100)
        runner.update()
        obstacles.update()
        if pygame.sprite.spritecollideany(runner, obstacles, pygame.sprite.collide_mask):
            state = "over"

    screen.fill(SKY)
    pygame.draw.rect(screen, GROUND, (0, GROUND_Y, 640, 60))
    screen.blit(runner.image, runner.rect)
    obstacles.draw(screen)
    score_text = font.render(f"{score:05}", True, (40, 50, 80))
    screen.blit(score_text, score_text.get_rect(topright=(630, 10)))
    if state == "over":
        message = font.render("Game over", True, (40, 50, 80))
        screen.blit(message, message.get_rect(center=(320, 140)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Tất cả bài học trong Luật chơi

Tự luyện tập: Playground Pygame