Thuốc đỏ
Thuộc phần Dự án trong hành trình Pygame của Coddy. Bài học 29 trên 33.
Mỗi ô H trên bản đồ là một lọ thuốc đỏ. Đi qua lọ thuốc sẽ hồi lại một trái tim, nhưng chỉ khi hiệp sĩ đã mất một trái tim, nên lọ thuốc nằm trên sàn cho đến khi anh ấy cần.
Các lọ thuốc là một danh sách Rects. knight.rect.collidelist(room.potions) trả về chỉ số của lọ thuốc mà hiệp sĩ chạm vào, hoặc -1. room.potions.pop(index) xóa lọ thuốc đó khỏi danh sách, nên lọ thuốc không còn được vẽ hoặc uống lần nữa.
index = knight.rect.collidelist(room.potions)
if index != -1 and knight.hearts < 3:
knight.hearts += 1
room.potions.pop(index)Xóa phần tử khỏi danh sách khi vòng lặp for đang duyệt qua danh sách sẽ khiến phần tử tiếp theo bị bỏ qua sau mỗi lần xóa. Dùng chỉ số thì không cần vòng lặp, tránh gây nhầm lẫn.
Đặt lọ thuốc lệch khỏi đường đi ngắn nhất qua căn phòng. Khi người chơi còn ít trái tim, họ sẽ có lý do để khám phá và mạo hiểm đi đường vòng qua một con slime.
Thử thách
Trung bìnhĐặt các bình thuốc hồi máu trên sàn.
- Trong
Room, đặtself.potions = []; khi gặpH, thêmcell_rect(col, row). - Sau phần kiểm tra bị thương, thêm đoạn code bình thuốc ở trên.
- Vẽ từng bình thuốc bằng
draw(potion_img, potion)trước kẻ địch.
Nhấn Chơi, mất một trái tim, rồi đi qua bình thuốc: trái tim sẽ hồi lại. Sau đó nhấn Kiểm tra.
Tự làm thử
import random
import pygame
TILE = 48
MAP = [
"##############",
"#............#",
"#.P......K...#",
"#............#",
"#....##......D",
"#....##......#",
"##.#######.###",
"#............#",
"#...S....H...#",
"##############",
]
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.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))
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
room = Room(MAP)
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
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"
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 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))
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()
Tất cả bài học trong Dự án
Tự luyện tập: Playground Pygame