Thoát khỏi pháo đài
Thuộc phần Dự án trong hành trình Pygame của Coddy. Bài học 31 trên 33.
Qua cánh cửa của căn phòng cuối cùng, hiệp sĩ thoát ra ngoài: trò chơi hiển thị You escaped!. Sau một trong hai kết thúc, nhấn R để bắt đầu lại ở căn phòng đầu tiên.
Giờ đây, kiểm tra cửa có hai kết quả:
if room.door_open and knight.rect.colliderect(room.door):
if level == len(ROOMS) - 1:
state = "won"
else:
level += 1
room = Room(ROOMS[level])
knight.rect.center = room.startBắt đầu lại rất nhanh vì các lớp quản lý trò chơi. Tạo Room mới sẽ dựng lại căn phòng đầu tiên với chìa khóa, cửa và mọi con slime trở về vị trí ban đầu. Knight mới có 3 trái tim, không bị thương và không vung kiếm.
elif event.key == pygame.K_r and state != "playing":
level = 0
room = Room(ROOMS[level])
knight = Knight(room.start)
state = "playing"Chơi lại từ đầu. Nếu căn phòng nào quá khó hoặc quá dễ, hãy thay đổi bản đồ: di chuyển một con slime, thêm một lọ thuốc, vẽ căn phòng mới rồi thêm nó vào ROOMS. Không cần thay đổi gì khác, vì mọi quy tắc đều dựa trên bản đồ. Không thể đi vòng qua slime trong hành lang chỉ rộng một ô, chỉ có thể chiến đấu với nó; còn slime trong sảnh rộng thì có thể tránh được.
Thử thách
Trung bìnhGiúp hiệp sĩ thoát khỏi pháo đài, rồi chơi lại.
- Thay đổi điều kiện kiểm tra cửa như trên: ở phòng cuối cùng, đặt trạng thái thành
"won", rồi hiển thịYou escaped!ở giữa tại (336, 240). - Sau một trong hai kết thúc, hiển thị
Press R to play againở giữa tại (336, 290). - Sự kiện
KEYDOWNcủa phím R khi trạng thái không phải"playing"sẽ bắt đầu lại như trên.
Nhấn "Chơi" và đi qua cả ba cánh cửa: dòng chữ You escaped! sẽ xuất hiện. Sau đó, nhấn "Kiểm tra".
Tự làm thử
import random
import pygame
TILE = 48
MAP = [
"##############",
"#............#",
"#.P......K...#",
"#............#",
"#....##......D",
"#....##......#",
"##.#######.###",
"#............#",
"#...S....H...#",
"##############",
]
MAP2 = [
"##############",
"#P...#...X...#",
"#....#.......#",
"#..S.#..###..#",
"#....#..#K...#",
"#.......#....#",
"##.######X####",
"#....S.......#",
"#.H..........#",
"###D##########",
]
MAP3 = [
"##############",
"#....#...X...#",
"#.P..#.......#",
"#....#..S....#",
"#..S.....###.D",
"#....#...#K.S#",
"###.###X.#...#",
"#........#...#",
"#..H.....#...#",
"##############",
]
ROOMS = [MAP, MAP2, MAP3]
pygame.init()
screen = pygame.display.set_mode((672, 480))
pygame.display.set_caption("Rune Keep")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 32)
def load(name):
return pygame.image.load("assets/images/" + name).convert_alpha()
floor_img = load("floor.png")
wall_img = load("wall.png")
knight_img = load("knight.png")
door_img = load("dungeon_door.png")
key_img = load("key.png")
slime_img = load("slime.png")
spider_img = load("spider.png")
heart_img = load("heart.png")
empty_img = load("heart_empty.png")
sword_img = load("sword.png")
potion_img = load("potion_red.png")
ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}
def draw(image, rect):
screen.blit(image, image.get_rect(center=rect.center))
def cell_rect(col, row):
rect = pygame.Rect(0, 0, 32, 32)
rect.center = (col * TILE + 24, row * TILE + 24)
return rect
class Enemy(pygame.sprite.Sprite):
def __init__(self, image, rect, dx, dy):
super().__init__()
self.image = image
self.rect = rect
self.dx = dx
self.dy = dy
def update(self, blocks):
self.rect.x += self.dx
self.rect.y += self.dy
if self.rect.collidelist(blocks) != -1:
self.rect.x -= self.dx
self.rect.y -= self.dy
self.dx = -self.dx
self.dy = -self.dy
class Room:
def __init__(self, lines):
self.walls = []
self.enemies = pygame.sprite.Group()
self.potions = []
self.start = (72, 72)
self.has_key = False
self.door_open = False
for row, line in enumerate(lines):
for col, char in enumerate(line):
if char == "#":
self.walls.append(pygame.Rect(col * TILE, row * TILE, TILE, TILE))
elif char == "P":
self.start = cell_rect(col, row).center
elif char == "K":
self.key = cell_rect(col, row)
elif char == "D":
self.door = pygame.Rect(col * TILE, row * TILE, TILE, TILE)
elif char == "S":
speed = random.choice([-2, 2])
self.enemies.add(Enemy(slime_img, cell_rect(col, row), speed, 0))
elif char == "X":
speed = random.choice([-2, 2])
self.enemies.add(Enemy(spider_img, cell_rect(col, row), 0, speed))
elif char == "H":
self.potions.append(cell_rect(col, row))
def blocks(self):
if self.door_open:
return self.walls
return self.walls + [self.door]
class Knight(pygame.sprite.Sprite):
def __init__(self, center):
super().__init__()
self.image = knight_img
self.rect = pygame.Rect(0, 0, 32, 32)
self.rect.center = center
self.hearts = 3
self.hurt = 0
self.facing = (1, 0)
self.swing = 0
def move(self, dx, dy, blocks):
self.rect.x += dx
for block in blocks:
if self.rect.colliderect(block):
if dx > 0:
self.rect.right = block.left
elif dx < 0:
self.rect.left = block.right
self.rect.y += dy
for block in blocks:
if self.rect.colliderect(block):
if dy > 0:
self.rect.bottom = block.top
elif dy < 0:
self.rect.top = block.bottom
def sword(self):
box = pygame.Rect(0, 0, 36, 36)
box.centerx = self.rect.centerx + self.facing[0] * 34
box.centery = self.rect.centery + self.facing[1] * 34
return box
level = 0
room = Room(ROOMS[level])
knight = Knight(room.start)
state = "playing"
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and state == "playing" and knight.swing == 0:
knight.swing = 12
if state == "playing":
keys = pygame.key.get_pressed()
dx = 0
dy = 0
if keys[pygame.K_LEFT]:
dx = -3
knight.facing = (-1, 0)
if keys[pygame.K_RIGHT]:
dx = 3
knight.facing = (1, 0)
if keys[pygame.K_UP]:
dy = -3
knight.facing = (0, -1)
if keys[pygame.K_DOWN]:
dy = 3
knight.facing = (0, 1)
knight.move(dx, dy, room.blocks())
knight.rect.clamp_ip(screen.get_rect())
if not room.has_key and knight.rect.colliderect(room.key):
room.has_key = True
if room.has_key and knight.rect.inflate(8, 8).colliderect(room.door):
room.door_open = True
if room.door_open and knight.rect.colliderect(room.door) and level < len(ROOMS) - 1:
level += 1
room = Room(ROOMS[level])
knight.rect.center = room.start
room.enemies.update(room.walls + [room.door])
if knight.swing > 0:
knight.swing -= 1
for enemy in room.enemies:
if enemy.rect.colliderect(knight.sword()):
enemy.kill()
if knight.hurt > 0:
knight.hurt -= 1
elif pygame.sprite.spritecollide(knight, room.enemies, False):
knight.hearts -= 1
knight.hurt = 60
if knight.hearts == 0:
state = "lost"
index = knight.rect.collidelist(room.potions)
if index != -1 and knight.hearts < 3:
knight.hearts += 1
room.potions.pop(index)
for row in range(10):
for col in range(14):
screen.blit(floor_img, (col * TILE, row * TILE))
for wall in room.walls:
screen.blit(wall_img, wall)
if not room.door_open:
screen.blit(door_img, room.door)
if not room.has_key:
draw(key_img, room.key)
for potion in room.potions:
draw(potion_img, potion)
for enemy in room.enemies:
draw(enemy.image, enemy.rect)
if knight.hurt % 10 < 5:
draw(knight.image, knight.rect)
if knight.swing > 0:
blade = pygame.transform.rotate(sword_img, ANGLES[knight.facing])
draw(blade, knight.sword())
for i in range(3):
if i < knight.hearts:
screen.blit(heart_img, (8 + i * 40, 6))
else:
screen.blit(empty_img, (8 + i * 40, 6))
if room.has_key:
screen.blit(key_img, (136, 6))
label = font.render(f"Room {level + 1} of {len(ROOMS)}", True, (240, 240, 240))
screen.blit(label, label.get_rect(topright=(660, 8)))
if state == "lost":
message = font.render("Game over", True, (240, 240, 240))
screen.blit(message, message.get_rect(center=(336, 240)))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Dự án
Tự luyện tập: Playground Pygame