Menu
CoddyTech

Một tảng đá sau mỗi 800 ms

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

Từng viên đá một tạo nên trò chơi yên ắng. Khi có bộ hẹn giờ, một viên đá mới có thể rơi xuống sau mỗi 800 ms, trong khi những viên khác vẫn đang rơi. Hãy lưu các viên đá trong một danh sách:

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

Mỗi sự kiện có số riêng, vì vậy SPAWN là USEREVENT + 2, lớn hơn POINT một đơn vị. Khởi động bộ hẹn giờ này cùng với bộ hẹn giờ kia, rồi đặt rocks = [] trong reset().

Ở mỗi khung hình, di chuyển tất cả các viên đá, rồi chỉ giữ lại những viên vẫn còn trong cửa sổ:

for rock in rocks:
    rock.y += 4
rocks = [rock for rock in rocks if rock.top <= 360]

Nếu thiếu dòng thứ hai, mọi viên đá từng rơi xuống sẽ vẫn nằm trong danh sách. Trò chơi sẽ di chuyển và kiểm tra hàng nghìn viên đá mà không ai nhìn thấy, khiến trò chơi chậm dần khi bạn chơi lâu hơn.

Phần vẽ và kiểm tra va chạm cũng trở thành các vòng lặp trên rocks.

challenge icon

Thử thách

Trung bình

Cứ mỗi 800 ms, thả một tảng đá mới.

  1. Tạo danh sách rocks rỗng trong reset() để chứa các rock.
  2. Thêm SPAWN = pygame.USEREVENT + 2; reset() khởi động bộ hẹn giờ với khoảng thời gian 800 ms.
  3. Khi đang chơi, sự kiện SPAWN thêm một tảng đá kích thước 24 x 24 tại y -24, với x ngẫu nhiên từ 0 đến 456.
  4. Di chuyển các tảng đá xuống 4 đơn vị, giữ lại những tảng có cạnh trên không vượt quá 360, vẽ và kiểm tra va chạm với từng tảng.

Nhấn Chơi: nhiều tảng đá cùng rơi xuống. 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


def reset():
    global player, rock, score, lives, hit_time
    player = pygame.Rect(220, 300, 40, 40)
    rock = pygame.Rect(random.randint(0, 456), -24, 24, 24)
    score = 0
    lives = 3
    hit_time = -2000
    pygame.time.set_timer(POINT, 1000)


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

    rock.y += 4
    if rock.top > 360:
        rock.y = -24
        rock.x = random.randint(0, 456)
    now = pygame.time.get_ticks()
    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 == 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