Chốt
Bài học 10 trên 25 trong khóa học Dự án Pygame: Trấn giữ thành trì của Coddy.
Vũ khí đầu tiên của hiệp sĩ tự bắn: cứ sau 30 khung hình, một tia sét ma thuật bay về phía quái vật gần nhất. Để tìm con gần nhất, hãy giữ lại con gần nhất đã thấy cho đến lúc đó rồi so sánh từng con quái vật với nó:
def nearest(self):
point = pygame.Vector2(self.knight.rect.center)
best = None
for monster in self.monsters:
if best is None or point.distance_to(monster.pos) < point.distance_to(best.pos):
best = monster
return bestTia sét là một sprite bay theo đường thẳng. Nó tính hướng bay một lần khi được tạo ra, giống như bước di chuyển của quái vật, rồi giữ nguyên hướng đó. Khi rời khỏi màn hình, nó tự xóa bằng self.kill(), thao tác này xóa sprite khỏi mọi Group mà nó thuộc về:
class Bolt(pygame.sprite.Sprite):
def __init__(self, start, target, damage):
super().__init__()
self.pos = pygame.Vector2(start)
step = pygame.Vector2(target) - self.pos
if step.length() == 0:
step = pygame.Vector2(1, 0)
self.velocity = step.normalize() * 7
self.damage = damage
self.rect = pygame.Rect(0, 0, 10, 10)
self.rect.center = self.pos
def update(self):
self.pos += self.velocity
self.rect.center = self.pos
if not screen.get_rect().colliderect(self.rect):
self.kill()Bộ hẹn giờ đếm số khung hình kể từ lần bắn tia sét trước. Khi đạt đến bolt_wait của hiệp sĩ và có mục tiêu, hãy bắn rồi đặt lại bộ hẹn giờ về 0. Thời gian chờ giữa các lần sử dụng như vậy được gọi là thời gian hồi chiêu.
Thử thách
Trung bìnhCứ mỗi 30 khung hình, hiệp sĩ bắn một tia vào con quái vật gần nhất.
- Thêm
BOLT = (120, 200, 255); đặtbolt_wait = 30vàbolt_damage = 1cho hiệp sĩ. - Viết lớp
Boltnhư trên. - Trong
Night: thêmbolts(một Group),bolt_timer = 0,nearest()như trên vàshoot().updatedi chuyển các tia sau khi cập nhật quái vật, rồi bắn. - Vẽ các tia:
pygame.draw.circle(screen, BOLT, bolt.rect.center, 5).
Nhấn Chơi: các tia bay về phía con quái vật gần nhất, tạm thời bay xuyên qua nó. Sau đó nhấn Kiểm tra.
Tự làm thử
import random
import pygame
WIDTH, HEIGHT = 720, 480
TILE = 48
NIGHT = 60 * 60 * 4
# Các loại quái vật xuất hiện trong từng đợt, từ đợt đầu đến đợt cuối
WAVES = [
["slime"],
["slime", "bat"],
["slime", "bat", "spider"],
["bat", "spider"],
["slime", "spider", "ghost"],
["bat", "spider", "ghost"],
["spider", "ghost"],
["bat", "ghost"],
]
WHITE = (240, 240, 240)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hold the Keep")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 24)
big_font = pygame.font.Font("assets/fonts/pixel.ttf", 48)
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")
heart_img = load("heart.png")
empty_img = load("heart_empty.png")
MONSTERS = {
"slime": {"image": load("slime.png"), "speed": 1.0},
"bat": {"image": load("dungeon_bat.png"), "speed": 1.6},
"spider": {"image": load("spider.png"), "speed": 1.2},
"ghost": {"image": load("ghost.png"), "speed": 0.8},
}
courtyard = pygame.Rect(TILE, TILE, WIDTH - 2 * TILE, HEIGHT - 2 * TILE)
background = pygame.Surface((WIDTH, HEIGHT))
for row in range(HEIGHT // TILE):
for col in range(WIDTH // TILE):
x = col * TILE
y = row * TILE
if courtyard.collidepoint(x, y):
background.blit(floor_img, (x, y))
else:
background.blit(wall_img, (x, y))
def draw(image, center):
screen.blit(image, image.get_rect(center=center))
def clock_text(frames):
seconds = frames // 60
return f"{seconds // 60}:{seconds % 60:02d}"
def edge_point():
side = random.randint(0, 3)
if side == 0:
return (random.randint(0, WIDTH), -24)
if side == 1:
return (random.randint(0, WIDTH), HEIGHT + 24)
if side == 2:
return (-24, random.randint(0, HEIGHT))
return (WIDTH + 24, random.randint(0, HEIGHT))
class Knight(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = knight_img
self.rect = pygame.Rect(0, 0, 24, 24)
self.rect.center = courtyard.center
self.speed = 3
self.hearts = 5
self.max_hearts = 5
self.hurt = 0
def move(self, keys):
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
if keys[pygame.K_UP]:
self.rect.y -= self.speed
if keys[pygame.K_DOWN]:
self.rect.y += self.speed
self.rect.clamp_ip(courtyard)
class Monster(pygame.sprite.Sprite):
def __init__(self, kind, pos):
super().__init__()
self.kind = kind
self.image = MONSTERS[kind]["image"]
self.speed = MONSTERS[kind]["speed"]
self.pos = pygame.Vector2(pos)
self.rect = pygame.Rect(0, 0, 28, 28)
self.rect.center = self.pos
def update(self, target):
step = pygame.Vector2(target) - self.pos
if step.length() > 0:
self.pos += step.normalize() * self.speed
self.rect.center = self.pos
class Night:
def __init__(self):
self.knight = Knight()
self.time = 0
self.monsters = pygame.sprite.Group()
self.state = "playing"
def wave(self):
return min(len(WAVES), self.time * len(WAVES) // NIGHT + 1)
def spawn(self):
wave = self.wave()
if self.time % max(16, 46 - wave * 5) == 0:
kind = random.choice(WAVES[wave - 1])
self.monsters.add(Monster(kind, edge_point()))
def take_hits(self):
knight = self.knight
if knight.hurt > 0:
knight.hurt -= 1
elif pygame.sprite.spritecollideany(knight, self.monsters):
knight.hearts -= 1
knight.hurt = 75
if knight.hearts <= 0:
self.state = "lost"
def update(self, keys):
self.time += 1
self.knight.move(keys)
self.spawn()
self.monsters.update(self.knight.rect.center)
self.take_hits()
night = Night()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if night.state == "playing":
night.update(pygame.key.get_pressed())
screen.blit(background, (0, 0))
knight = night.knight
for monster in night.monsters:
draw(monster.image, monster.rect.center)
if knight.hurt % 10 < 5:
draw(knight.image, knight.rect.center)
for i in range(knight.max_hearts):
if i < knight.hearts:
screen.blit(heart_img, (8 + i * 34, 6))
else:
screen.blit(empty_img, (8 + i * 34, 6))
left = font.render(clock_text(max(0, NIGHT - night.time)) + " until dawn", True, WHITE)
screen.blit(left, left.get_rect(center=(WIDTH // 2, 24)))
if night.state == "lost":
message = big_font.render("The keep has fallen", True, WHITE)
screen.blit(message, message.get_rect(center=(WIDTH // 2, 200)))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Tất cả bài học trong Dự án Pygame: Trấn giữ thành trì
Tự luyện tập: Playground Pygame