Vung kiếm
Thuộc phần Dự án trong hành trình Pygame của Coddy. Bài học 28 trên 33.
Phím cách vung kiếm của hiệp sĩ trong 12 khung hình, về phía hiệp sĩ đang quay mặt. Slime nào chạm vào lưỡi kiếm sẽ biến mất.
Hiệp sĩ ghi nhớ hướng đi gần nhất trong knight.facing: (1, 0) là phải, (0, -1) là lên. Lưỡi kiếm là một hình chữ nhật 36 x 36, cách tâm hiệp sĩ 34 pixel theo hướng đó:
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 boxknight.swing đếm ngược số khung hình của cú vung kiếm về 0. Phím cách chỉ bắt đầu cú vung mới khi giá trị này bằng 0, nên giữ phím cách không làm lưỡi kiếm tiếp tục vung. Khi cú vung đang diễn ra, mọi kẻ địch chạm vào hình chữ nhật đều bị xóa bằng enemy.kill(), thao tác này xóa sprite khỏi mọi nhóm mà nó thuộc về.
sword.png hướng lên trên. pygame.transform.rotate xoay ảnh ngược chiều kim đồng hồ theo góc tương ứng với từng hướng:
ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}Thử thách
KhóVung kiếm bằng phím Space.
- Thêm
ANGLES; thiết lập cho hiệp sĩself.facing = (1, 0),self.swing = 0vàsword(). - Các phím mũi tên cũng thiết lập
knight.facing: phím trái(-1, 0). - Phím Space thiết lập
knight.swing = 12nếu giá trị đang là 0. Trước khi kiểm tra trạng thái bị thương, khi giá trị lớn hơn 0, hãy giảm giá trị đó và gọikill()với những kẻ địch chạm vàoknight.sword(). - Khi vung kiếm, vẽ lưỡi kiếm đã xoay tại vị trí
knight.sword().
Nhấn Chơi rồi đánh một con slime: nó sẽ biến mất. 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")
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
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
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
if state == "playing":
keys = pygame.key.get_pressed()
dx = 0
dy = 0
if keys[pygame.K_LEFT]:
dx = -3
if keys[pygame.K_RIGHT]:
dx = 3
if keys[pygame.K_UP]:
dy = -3
if keys[pygame.K_DOWN]:
dy = 3
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.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)
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