Menu
CoddyTech

Các đợt ngày càng lớn

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

Nhiều trò chơi được chia thành các đợt: một nhóm kẻ địch, một khoảng lặng ngắn khi kẻ cuối cùng biến mất, rồi đến một nhóm lớn hơn. Cách này cho người chơi biết trò chơi đang khó hơn và giúp họ có chút thời gian nghỉ.

Hai con số điều khiển việc này: wave, đợt bạn đang chơi, và to_spawn, số tảng đá trong đợt này vẫn còn cần rơi xuống. Bộ đếm thời gian tạo vật thể chỉ thả một tảng đá khi to_spawn lớn hơn 0:

if event.type == SPAWN and state == "play" and to_spawn > 0:
    rocks.append(pygame.Rect(random.randint(0, 456), -24, 24, 24))
    to_spawn -= 1

Đợt kết thúc khi không còn tảng đá nào cần rơi xuống và cũng không còn tảng đá nào đang rơi. Đợt tiếp theo bắt đầu với nhiều hơn một tảng đá:

if to_spawn == 0 and not rocks:
    wave += 1
    to_spawn = wave + 2

not rocks là đúng khi danh sách trống. Đợt 1 thả 3 tảng đá, đợt 2 thả 4 tảng đá, đợt 3 thả 5 tảng đá.

challenge icon

Thử thách

Khó

Đưa đá ra theo từng đợt ngày càng lớn.

  1. reset() đặt wave = 1 và to_spawn = 3.
  2. SPAWN chỉ thả đá khi to_spawn lớn hơn 0, rồi giảm giá trị này xuống.
  3. Trong play_frame(), khi to_spawn bằng 0 và rocks trống, tăng wave thêm 1; đặt to_spawn = wave + 2.
  4. Hiển thị Wave 1, Wave 2... bằng midtop=(240, 10).

Nhấn Chơi: đợt 2 bắt đầu sau khi có ba hòn đá. Sau đó nhấn Kiểm tra.

Tự làm thử

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((480, 360))
pygame.display.set_caption("Meteor Dodge")
clock = pygame.time.Clock()
heart = pygame.image.load("assets/images/heart.png").convert_alpha()
heart_empty = pygame.image.load("assets/images/heart_empty.png").convert_alpha()
font = pygame.font.Font("assets/fonts/pixel.ttf", 24)
big_font = pygame.font.Font("assets/fonts/pixel.ttf", 48)
WHITE = (240, 240, 240)
PLATE = (60, 60, 90)
POINT = pygame.USEREVENT + 1
SPAWN = pygame.USEREVENT + 2


def reset():
    global player, rocks, score, lives, hit_time
    player = pygame.Rect(220, 300, 40, 40)
    rocks = []
    score = 0
    lives = 3
    hit_time = -2000
    pygame.time.set_timer(POINT, 1000)
    pygame.time.set_timer(SPAWN, 800)


def draw_menu():
    screen.fill((30, 30, 46))
    title = big_font.render("Meteor Dodge", True, WHITE)
    title_rect = title.get_rect(center=(240, 140))
    pygame.draw.rect(screen, PLATE, title_rect.inflate(24, 12))
    screen.blit(title, title_rect)
    hint = font.render("Press Enter to play", True, WHITE)
    screen.blit(hint, hint.get_rect(center=(240, 200)))


def draw_game():
    screen.fill((30, 30, 46))
    safe = pygame.time.get_ticks() - hit_time < 1000
    if not safe or pygame.time.get_ticks() // 100 % 2 == 0:
        pygame.draw.rect(screen, (255, 196, 0), player)
    for rock in rocks:
        pygame.draw.rect(screen, (235, 80, 80), rock)
    score_text = font.render(f"{score:05}", True, WHITE)
    score_rect = score_text.get_rect(topright=(470, 10))
    pygame.draw.rect(screen, PLATE, score_rect.inflate(12, 8))
    screen.blit(score_text, score_rect)
    for i in range(3):
        if i < lives:
            screen.blit(heart, (10 + i * 40, 10))
        else:
            screen.blit(heart_empty, (10 + i * 40, 10))


def play_frame():
    global score, lives, state, hit_time, rocks
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= 6
    if keys[pygame.K_RIGHT]:
        player.x += 6
    player.clamp_ip(screen.get_rect())

    for rock in rocks:
        rock.y += 4
    rocks = [rock for rock in rocks if rock.top <= 360]
    now = pygame.time.get_ticks()
    for rock in rocks:
        if rock.colliderect(player) and now - hit_time > 1000:
            hit_time = now
            lives -= 1
            if lives == 0:
                state = "over"
    draw_game()


def draw_over():
    draw_game()
    message = big_font.render("Game over", True, WHITE)
    screen.blit(message, message.get_rect(center=(240, 160)))
    hint = font.render("Press R to play again", True, WHITE)
    screen.blit(hint, hint.get_rect(center=(240, 200)))


reset()
state = "menu"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == POINT and state == "play":
            score += 1
        if event.type == SPAWN and state == "play":
            rocks.append(pygame.Rect(random.randint(0, 456), -24, 24, 24))
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN and state == "menu":
                state = "play"
            elif event.key == pygame.K_r and state == "over":
                reset()
                state = "play"

    if state == "menu":
        draw_menu()
    elif state == "play":
        play_frame()
    else:
        draw_over()
    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