游戏可以运行

This commit is contained in:
2026-05-20 21:23:46 +08:00
parent e5aa78e8be
commit 804f9d87aa
7 changed files with 256 additions and 49 deletions

View File

@@ -66,7 +66,7 @@ ENEMY_DATA = {
"healer": {"hp": 150, "speed": 1.8, "reward": 30, "color": COLOR_PINK, "size": 12, "name": "治疗兵", "heal_range": 100, "heal_amount": 30, "heal_interval": 2.0}, "healer": {"hp": 150, "speed": 1.8, "reward": 30, "color": COLOR_PINK, "size": 12, "name": "治疗兵", "heal_range": 100, "heal_amount": 30, "heal_interval": 2.0},
} }
# Path waypoints (pixel coordinates) # Path waypoints (pixel coordinates) — default/legacy
PATH_WAYPOINTS = [ PATH_WAYPOINTS = [
(0, 180), (0, 180),
(160, 180), (160, 180),
@@ -79,3 +79,75 @@ PATH_WAYPOINTS = [
(760, 220), (760, 220),
(800, 220), (800, 220),
] ]
# Level definitions
LEVELS = [
{
"name": "第一关 - 森林小径",
"color": (44, 72, 44),
"start_gold": 200,
"start_lives": 10,
"waypoints": [
(0, 180), (160, 180), (160, 340), (400, 340),
(400, 100), (600, 100), (600, 340), (760, 340),
(760, 220), (800, 220),
],
"waves": [
[{"type": "normal", "count": 5, "interval": 1.0}],
[{"type": "normal", "count": 6, "interval": 0.8}, {"type": "swarm", "count": 4, "interval": 0.3}],
[{"type": "normal", "count": 5, "interval": 0.8}, {"type": "fast", "count": 3, "interval": 0.6}],
[{"type": "swarm", "count": 10, "interval": 0.25}, {"type": "normal", "count": 5, "interval": 0.7}],
[{"type": "normal", "count": 5, "interval": 0.6}, {"type": "heavy", "count": 2, "interval": 1.5}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "normal", "count": 8, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}, {"type": "swarm", "count": 8, "interval": 0.2}],
[{"type": "shield", "count": 3, "interval": 1.2}, {"type": "healer", "count": 2, "interval": 1.5}, {"type": "normal", "count": 6, "interval": 0.6}],
[{"type": "heavy", "count": 4, "interval": 0.8}, {"type": "fast", "count": 5, "interval": 0.5}, {"type": "shield", "count": 3, "interval": 1.0}],
],
},
{
"name": "第二关 - 沙漠要塞",
"color": (82, 77, 50),
"start_gold": 180,
"start_lives": 8,
"waypoints": [
(0, 60), (200, 60), (200, 180), (60, 180),
(60, 300), (340, 300), (340, 180), (480, 180),
(480, 300), (620, 300), (620, 100), (800, 100),
],
"waves": [
[{"type": "normal", "count": 8, "interval": 0.8}],
[{"type": "fast", "count": 6, "interval": 0.5}, {"type": "swarm", "count": 6, "interval": 0.25}],
[{"type": "normal", "count": 6, "interval": 0.6}, {"type": "heavy", "count": 3, "interval": 1.2}],
[{"type": "swarm", "count": 12, "interval": 0.2}, {"type": "shield", "count": 2, "interval": 1.5}],
[{"type": "fast", "count": 8, "interval": 0.4}, {"type": "healer", "count": 2, "interval": 1.0}],
[{"type": "heavy", "count": 5, "interval": 0.8}, {"type": "normal", "count": 8, "interval": 0.5}],
[{"type": "shield", "count": 4, "interval": 1.0}, {"type": "fast", "count": 6, "interval": 0.4}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "heavy", "count": 4, "interval": 0.7}],
[{"type": "normal", "count": 10, "interval": 0.4}, {"type": "shield", "count": 4, "interval": 0.8}, {"type": "healer", "count": 3, "interval": 1.0}],
[{"type": "heavy", "count": 6, "interval": 0.6}, {"type": "fast", "count": 10, "interval": 0.3}, {"type": "boss", "count": 2, "interval": 2.0}],
],
},
{
"name": "第三关 - 暗夜城堡",
"color": (35, 30, 45),
"start_gold": 150,
"start_lives": 8,
"waypoints": [
(0, 260), (120, 260), (120, 60), (280, 60),
(280, 180), (440, 180), (440, 60), (560, 60),
(560, 260), (680, 260), (680, 100), (800, 100),
],
"waves": [
[{"type": "normal", "count": 8, "interval": 0.7}, {"type": "fast", "count": 4, "interval": 0.5}],
[{"type": "heavy", "count": 4, "interval": 1.0}, {"type": "swarm", "count": 8, "interval": 0.2}],
[{"type": "shield", "count": 4, "interval": 1.0}, {"type": "fast", "count": 6, "interval": 0.4}],
[{"type": "healer", "count": 3, "interval": 1.0}, {"type": "normal", "count": 10, "interval": 0.5}],
[{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "heavy", "count": 5, "interval": 0.8}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "shield", "count": 5, "interval": 0.8}, {"type": "healer", "count": 3, "interval": 0.8}, {"type": "fast", "count": 8, "interval": 0.3}],
[{"type": "heavy", "count": 6, "interval": 0.6}, {"type": "swarm", "count": 12, "interval": 0.15}],
[{"type": "normal", "count": 12, "interval": 0.3}, {"type": "shield", "count": 5, "interval": 0.7}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "fast", "count": 12, "interval": 0.25}, {"type": "healer", "count": 4, "interval": 0.7}],
[{"type": "heavy", "count": 8, "interval": 0.5}, {"type": "shield", "count": 6, "interval": 0.6}, {"type": "swarm", "count": 15, "interval": 0.12}],
[{"type": "normal", "count": 12, "interval": 0.25}, {"type": "heavy", "count": 6, "interval": 0.5}, {"type": "fast", "count": 10, "interval": 0.2}, {"type": "boss", "count": 3, "interval": 2.0}],
],
},
]

View File

@@ -5,7 +5,7 @@ from game.utils import distance
class Enemy: class Enemy:
def __init__(self, enemy_type): def __init__(self, enemy_type, waypoints=None):
data = ENEMY_DATA[enemy_type] data = ENEMY_DATA[enemy_type]
self.type = enemy_type self.type = enemy_type
self.max_hp = data["hp"] self.max_hp = data["hp"]
@@ -32,7 +32,8 @@ class Enemy:
self.heal_interval = data.get("heal_interval", 2.0) self.heal_interval = data.get("heal_interval", 2.0)
self.heal_timer = self.heal_interval self.heal_timer = self.heal_interval
self.x, self.y = PATH_WAYPOINTS[0] self.waypoints = waypoints or PATH_WAYPOINTS
self.x, self.y = self.waypoints[0]
self.waypoint_index = 1 self.waypoint_index = 1
self.slow_timer = 0 self.slow_timer = 0
@@ -84,12 +85,12 @@ class Enemy:
e.hp = min(e.hp + self.heal_amount, e.max_hp) e.hp = min(e.hp + self.heal_amount, e.max_hp)
# Movement # Movement
if self.waypoint_index >= len(PATH_WAYPOINTS): if self.waypoint_index >= len(self.waypoints):
self.reached_end = True self.reached_end = True
self.alive = False self.alive = False
return return
tx, ty = PATH_WAYPOINTS[self.waypoint_index] tx, ty = self.waypoints[self.waypoint_index]
dx = tx - self.x dx = tx - self.x
dy = ty - self.y dy = ty - self.y
dist = math.hypot(dx, dy) dist = math.hypot(dx, dy)

View File

@@ -4,7 +4,7 @@ from game.config import (
WINDOW_WIDTH, WINDOW_HEIGHT, FPS, WINDOW_WIDTH, WINDOW_HEIGHT, FPS,
COLOR_BG, COLOR_WHITE, INITIAL_GOLD, INITIAL_LIVES, COLOR_BG, COLOR_WHITE, INITIAL_GOLD, INITIAL_LIVES,
UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT, GAME_HEIGHT, TOWER_DATA, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT, GAME_HEIGHT, TOWER_DATA,
TOTAL_WAVES, LEVELS,
) )
from game.map import GameMap from game.map import GameMap
from game.enemy import Enemy from game.enemy import Enemy
@@ -23,19 +23,25 @@ class Game:
self.font = pygame.font.SysFont("microsoftyahei", 20) self.font = pygame.font.SysFont("microsoftyahei", 20)
self.small_font = pygame.font.SysFont("microsoftyahei", 16) self.small_font = pygame.font.SysFont("microsoftyahei", 16)
self.big_font = pygame.font.SysFont("microsoftyahei", 48) self.big_font = pygame.font.SysFont("microsoftyahei", 48)
self.reset() self.title_font = pygame.font.SysFont("microsoftyahei", 36)
self.state = "select"
self.current_level_idx = 0
self.ui = UI()
def reset(self): def _start_level(self, level_idx):
self.game_map = GameMap() level = LEVELS[level_idx]
self.current_level_idx = level_idx
self.game_map = GameMap(level["waypoints"], level["color"])
self.enemies = [] self.enemies = []
self.towers = [] self.towers = []
self.projectiles = [] self.projectiles = []
self.wave_mgr = WaveManager() self.wave_mgr = WaveManager(level["waves"], level["waypoints"])
self.ui = UI() self.ui = UI()
self.gold = INITIAL_GOLD self.gold = level["start_gold"]
self.lives = INITIAL_LIVES self.lives = level["start_lives"]
self.game_over = False self.game_over = False
self.won = False self.won = False
self.state = "playing"
def run(self): def run(self):
while True: while True:
@@ -48,18 +54,36 @@ class Game:
sys.exit() sys.exit()
self._handle_event(event) self._handle_event(event)
if not self.game_over: if self.state == "playing" and not self.game_over:
self._update(dt) self._update(dt)
self._draw() self._draw()
def _handle_event(self, event): def _handle_event(self, event):
if self.state == "select":
self._handle_select_event(event)
return
if self.state == "playing":
self._handle_playing_event(event)
return
if self.state == "game_over":
self._handle_game_over_event(event)
def _handle_select_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pos = event.pos pos = event.pos
for i in range(len(LEVELS)):
if self.game_over: btn_rect = self._level_button_rect(i)
if btn_rect.collidepoint(pos):
self._start_level(i)
return return
def _handle_playing_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pos = event.pos
action = self.ui.handle_click(pos, self.gold) action = self.ui.handle_click(pos, self.gold)
if action == "wave": if action == "wave":
if self.wave_mgr.has_more_waves() and not self.wave_mgr.wave_active: if self.wave_mgr.has_more_waves() and not self.wave_mgr.wave_active:
@@ -82,11 +106,43 @@ class Game:
self.ui.selected_tower = None self.ui.selected_tower = None
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and self.game_over:
self.reset()
if event.key == pygame.K_ESCAPE: if event.key == pygame.K_ESCAPE:
self.ui.selected_tower = None self.ui.selected_tower = None
def _handle_game_over_event(self, event):
if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
self._start_level(self.current_level_idx)
return
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pos = event.pos
# Restart button
restart_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 50, 160, 45)
if restart_rect.collidepoint(pos):
self._start_level(self.current_level_idx)
return
# Next level button (win only, not last level)
if self.won and self.current_level_idx < len(LEVELS) - 1:
next_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 105, 160, 45)
if next_rect.collidepoint(pos):
self._start_level(self.current_level_idx + 1)
return
# Back to select button
select_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 160, 160, 45)
if select_rect.collidepoint(pos):
self.state = "select"
return
def _level_button_rect(self, idx):
bw, bh = 300, 100
col = idx % 3
total_w = 3 * bw + 2 * 30
start_x = (WINDOW_WIDTH - total_w) // 2
start_y = 250
return pygame.Rect(start_x + col * (bw + 30), start_y, bw, bh)
def _update(self, dt): def _update(self, dt):
if self.ui.selected_tower is not None: if self.ui.selected_tower is not None:
dt *= 0.3 dt *= 0.3
@@ -101,6 +157,7 @@ class Game:
self.lives = 0 self.lives = 0
self.game_over = True self.game_over = True
self.won = False self.won = False
self.state = "game_over"
for t in self.towers: for t in self.towers:
t.update(dt, self.enemies, self.projectiles) t.update(dt, self.enemies, self.projectiles)
@@ -116,12 +173,19 @@ class Game:
self.enemies = [e for e in self.enemies if e.alive] self.enemies = [e for e in self.enemies if e.alive]
self.projectiles = [p for p in self.projectiles if p.alive] self.projectiles = [p for p in self.projectiles if p.alive]
if self.wave_mgr.all_waves_done or (self.wave_mgr.current_wave >= TOTAL_WAVES and not self.wave_mgr.wave_active and not self.enemies): total_waves = self.wave_mgr.total_waves
if self.wave_mgr.all_waves_done or (self.wave_mgr.current_wave >= total_waves and not self.wave_mgr.wave_active and not self.enemies):
if not self.game_over: if not self.game_over:
self.game_over = True self.game_over = True
self.won = True self.won = True
self.state = "game_over"
def _draw(self): def _draw(self):
if self.state == "select":
self._draw_select()
return
# Playing / game_over
self.screen.fill(COLOR_BG) self.screen.fill(COLOR_BG)
self.game_map.draw(self.screen) self.game_map.draw(self.screen)
@@ -142,15 +206,83 @@ class Game:
self.wave_mgr.current_wave, self.wave_mgr.wave_active, self.wave_mgr.current_wave, self.wave_mgr.wave_active,
self.wave_mgr.has_more_waves(), self.wave_mgr.has_more_waves(),
self.font, self.small_font, self.font, self.small_font,
self.wave_mgr.total_waves,
) )
self.ui.draw_slow_overlay(self.screen) self.ui.draw_slow_overlay(self.screen)
if self.game_over: if self.state == "game_over":
self.ui.draw_game_over(self.screen, self.won, self.big_font) self._draw_game_over()
pygame.display.flip() pygame.display.flip()
def _draw_select(self):
self.screen.fill(COLOR_BG)
title = self.title_font.render("塔防游戏 - 选择关卡", True, COLOR_WHITE)
self.screen.blit(title, title.get_rect(center=(WINDOW_WIDTH // 2, 80)))
subtitle = self.small_font.render("点击关卡开始游戏", True, (180, 180, 180))
self.screen.blit(subtitle, subtitle.get_rect(center=(WINDOW_WIDTH // 2, 130)))
level_colors = [(60, 100, 60), (120, 100, 50), (60, 50, 80)]
for i, level in enumerate(LEVELS):
btn_rect = self._level_button_rect(i)
color = level_colors[i % len(level_colors)]
pygame.draw.rect(self.screen, color, btn_rect, border_radius=10)
pygame.draw.rect(self.screen, COLOR_WHITE, btn_rect, 2, border_radius=10)
name_text = self.font.render(level["name"], True, COLOR_WHITE)
self.screen.blit(name_text, name_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 25)))
info = f"{len(level['waves'])}波 | {level['start_gold']}金 | {level['start_lives']}"
info_text = self.small_font.render(info, True, (200, 200, 200))
self.screen.blit(info_text, info_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 55)))
diff = ["简单", "中等", "困难"][i]
diff_colors = [COLOR_WHITE, (255, 200, 50), (255, 80, 80)][i]
diff_text = self.small_font.render(diff, True, diff_colors)
self.screen.blit(diff_text, diff_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 80)))
pygame.display.flip()
def _draw_game_over(self):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
self.screen.blit(overlay, (0, 0))
text = "胜利!" if self.won else "失败!"
color = (255, 215, 0) if self.won else (220, 50, 50)
rendered = self.big_font.render(text, True, color)
self.screen.blit(rendered, rendered.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 - 40)))
cx = WINDOW_WIDTH // 2
# Restart button
restart_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 50, 160, 45)
pygame.draw.rect(self.screen, (80, 120, 80), restart_rect, border_radius=8)
pygame.draw.rect(self.screen, COLOR_WHITE, restart_rect, 2, border_radius=8)
r_text = self.font.render("重新开始", True, COLOR_WHITE)
self.screen.blit(r_text, r_text.get_rect(center=restart_rect.center))
# Next level button (only on win, not last level)
if self.won and self.current_level_idx < len(LEVELS) - 1:
next_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 105, 160, 45)
pygame.draw.rect(self.screen, (50, 80, 150), next_rect, border_radius=8)
pygame.draw.rect(self.screen, COLOR_WHITE, next_rect, 2, border_radius=8)
n_text = self.font.render("下一关", True, COLOR_WHITE)
self.screen.blit(n_text, n_text.get_rect(center=next_rect.center))
# Back to select
select_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 160, 160, 45)
pygame.draw.rect(self.screen, (100, 100, 100), select_rect, border_radius=8)
pygame.draw.rect(self.screen, COLOR_WHITE, select_rect, 2, border_radius=8)
s_text = self.font.render("选择关卡", True, COLOR_WHITE)
self.screen.blit(s_text, s_text.get_rect(center=select_rect.center))
hint = self.small_font.render("或按 R 重新开始", True, (180, 180, 180))
self.screen.blit(hint, hint.get_rect(center=(cx, WINDOW_HEIGHT // 2 + 225)))
if __name__ == "__main__": if __name__ == "__main__":
Game().run() Game().run()

View File

@@ -2,20 +2,21 @@ import pygame
import math import math
from game.config import ( from game.config import (
CELL_SIZE, GRID_COLS, GRID_ROWS, UI_TOP_HEIGHT, CELL_SIZE, GRID_COLS, GRID_ROWS, UI_TOP_HEIGHT,
COLOR_GRASS, COLOR_PATH, COLOR_GRID_LINE, COLOR_GRASS, COLOR_PATH, COLOR_GRID_LINE, PATH_WAYPOINTS,
PATH_WAYPOINTS,
) )
class GameMap: class GameMap:
def __init__(self): def __init__(self, waypoints=None, bg_color=None):
self.waypoints = waypoints or PATH_WAYPOINTS
self.bg_color = bg_color or COLOR_GRASS
self.path_cells = set() self.path_cells = set()
self._calc_path_cells() self._calc_path_cells()
def _calc_path_cells(self): def _calc_path_cells(self):
for i in range(len(PATH_WAYPOINTS) - 1): for i in range(len(self.waypoints) - 1):
x1, y1 = PATH_WAYPOINTS[i] x1, y1 = self.waypoints[i]
x2, y2 = PATH_WAYPOINTS[i + 1] x2, y2 = self.waypoints[i + 1]
dx = x2 - x1 dx = x2 - x1
dy = y2 - y1 dy = y2 - y1
dist = math.hypot(dx, dy) dist = math.hypot(dx, dy)
@@ -52,9 +53,9 @@ class GameMap:
row * CELL_SIZE + UI_TOP_HEIGHT, row * CELL_SIZE + UI_TOP_HEIGHT,
CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE,
) )
color = COLOR_PATH if (col, row) in self.path_cells else COLOR_GRASS color = COLOR_PATH if (col, row) in self.path_cells else self.bg_color
pygame.draw.rect(surface, color, rect) pygame.draw.rect(surface, color, rect)
pygame.draw.rect(surface, COLOR_GRID_LINE, rect, 1) pygame.draw.rect(surface, COLOR_GRID_LINE, rect, 1)
for i in range(len(PATH_WAYPOINTS) - 1): for i in range(len(self.waypoints) - 1):
pygame.draw.line(surface, (180, 150, 120), PATH_WAYPOINTS[i], PATH_WAYPOINTS[i + 1], 3) pygame.draw.line(surface, (180, 150, 120), self.waypoints[i], self.waypoints[i + 1], 3)

View File

@@ -39,16 +39,19 @@ class Tower:
continue continue
d = distance(self.x, self.y, e.x, e.y) d = distance(self.x, self.y, e.x, e.y)
if d <= self.range: if d <= self.range:
progress = e.waypoint_index + (1 - distance(e.x, e.y, *self._wp(e.waypoint_index)) / 100) wp_idx = min(e.waypoint_index, len(e.waypoints) - 1)
progress = e.waypoint_index + (1 - distance(e.x, e.y, *e.waypoints[wp_idx]) / 100)
if progress > best_progress: if progress > best_progress:
best_progress = progress best_progress = progress
best = e best = e
self.target = best self.target = best
def _wp(self, idx): def _wp(self, idx):
if self.target and idx < len(self.target.waypoints):
return self.target.waypoints[idx]
if self.target:
return self.target.waypoints[-1]
from game.config import PATH_WAYPOINTS from game.config import PATH_WAYPOINTS
if idx < len(PATH_WAYPOINTS):
return PATH_WAYPOINTS[idx]
return PATH_WAYPOINTS[-1] return PATH_WAYPOINTS[-1]
def update(self, dt, enemies, projectiles): def update(self, dt, enemies, projectiles):

View File

@@ -4,7 +4,7 @@ from game.config import (
WINDOW_WIDTH, WINDOW_HEIGHT, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT,
CELL_SIZE, COLOR_BLACK, COLOR_WHITE, COLOR_GOLD, COLOR_RED, CELL_SIZE, COLOR_BLACK, COLOR_WHITE, COLOR_GOLD, COLOR_RED,
COLOR_GREEN, COLOR_DARK_GRAY, COLOR_GRAY, COLOR_BG, COLOR_GREEN, COLOR_DARK_GRAY, COLOR_GRAY, COLOR_BG,
TOWER_DATA, INITIAL_LIVES, TOTAL_WAVES, COLOR_LIGHT_BLUE, TOWER_DATA, COLOR_LIGHT_BLUE,
) )
@@ -43,7 +43,7 @@ class UI:
return None return None
return self.selected_tower return self.selected_tower
def draw(self, surface, gold, lives, wave_num, wave_active, has_more, font, small_font): def draw(self, surface, gold, lives, wave_num, wave_active, has_more, font, small_font, total_waves=10):
# Top bar # Top bar
pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, 0, WINDOW_WIDTH, UI_TOP_HEIGHT)) pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, 0, WINDOW_WIDTH, UI_TOP_HEIGHT))
@@ -53,7 +53,7 @@ class UI:
lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 3 else COLOR_GREEN) lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 3 else COLOR_GREEN)
surface.blit(lives_text, (160, 10)) surface.blit(lives_text, (160, 10))
wave_text = font.render(f"波次: {wave_num}/{TOTAL_WAVES}", True, COLOR_WHITE) wave_text = font.render(f"波次: {wave_num}/{total_waves}", True, COLOR_WHITE)
surface.blit(wave_text, (310, 10)) surface.blit(wave_text, (310, 10))
# Slow-time indicator # Slow-time indicator

View File

@@ -1,22 +1,16 @@
from game.enemy import Enemy from game.enemy import Enemy
from game.config import PATH_WAYPOINTS
WAVE_DATA = [ WAVE_DATA = [
[{"type": "normal", "count": 5, "interval": 1.0}], [{"type": "normal", "count": 5, "interval": 1.0}],
[{"type": "normal", "count": 6, "interval": 0.8}, {"type": "swarm", "count": 4, "interval": 0.3}],
[{"type": "normal", "count": 5, "interval": 0.8}, {"type": "fast", "count": 3, "interval": 0.6}],
[{"type": "swarm", "count": 10, "interval": 0.25}, {"type": "normal", "count": 5, "interval": 0.7}],
[{"type": "normal", "count": 5, "interval": 0.6}, {"type": "heavy", "count": 2, "interval": 1.5}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "normal", "count": 8, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}, {"type": "swarm", "count": 8, "interval": 0.2}],
[{"type": "shield", "count": 3, "interval": 1.2}, {"type": "healer", "count": 2, "interval": 1.5}, {"type": "normal", "count": 6, "interval": 0.6}],
[{"type": "heavy", "count": 4, "interval": 0.8}, {"type": "fast", "count": 5, "interval": 0.5}, {"type": "shield", "count": 3, "interval": 1.0}],
[{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "healer", "count": 3, "interval": 1.0}, {"type": "heavy", "count": 4, "interval": 0.7}],
[{"type": "normal", "count": 10, "interval": 0.3}, {"type": "heavy", "count": 5, "interval": 0.5}, {"type": "fast", "count": 8, "interval": 0.3}, {"type": "shield", "count": 4, "interval": 0.8}, {"type": "healer", "count": 2, "interval": 1.0}, {"type": "boss", "count": 2, "interval": 2.0}],
] ]
class WaveManager: class WaveManager:
def __init__(self): def __init__(self, wave_data=None, waypoints=None):
self.wave_data = wave_data or WAVE_DATA
self.waypoints = waypoints or PATH_WAYPOINTS
self.current_wave = 0 self.current_wave = 0
self.spawn_queue = [] self.spawn_queue = []
self.spawn_timer = 0 self.spawn_timer = 0
@@ -24,12 +18,12 @@ class WaveManager:
self.all_waves_done = False self.all_waves_done = False
def start_next_wave(self): def start_next_wave(self):
if self.current_wave >= len(WAVE_DATA): if self.current_wave >= len(self.wave_data):
self.all_waves_done = True self.all_waves_done = True
return False return False
self.spawn_queue = [] self.spawn_queue = []
for group in WAVE_DATA[self.current_wave]: for group in self.wave_data[self.current_wave]:
for _ in range(group["count"]): for _ in range(group["count"]):
self.spawn_queue.append((group["type"], group["interval"])) self.spawn_queue.append((group["type"], group["interval"]))
@@ -38,6 +32,10 @@ class WaveManager:
self.spawn_timer = 0 self.spawn_timer = 0
return True return True
@property
def total_waves(self):
return len(self.wave_data)
def update(self, dt, enemies): def update(self, dt, enemies):
if not self.wave_active: if not self.wave_active:
return return
@@ -50,11 +48,11 @@ class WaveManager:
self.spawn_timer -= dt self.spawn_timer -= dt
if self.spawn_timer <= 0: if self.spawn_timer <= 0:
enemy_type, interval = self.spawn_queue.pop(0) enemy_type, interval = self.spawn_queue.pop(0)
enemies.append(Enemy(enemy_type)) enemies.append(Enemy(enemy_type, self.waypoints))
self.spawn_timer = self.spawn_queue[0][1] if self.spawn_queue else 0 self.spawn_timer = self.spawn_queue[0][1] if self.spawn_queue else 0
def is_wave_complete(self): def is_wave_complete(self):
return not self.wave_active and self.current_wave > 0 return not self.wave_active and self.current_wave > 0
def has_more_waves(self): def has_more_waves(self):
return self.current_wave < len(WAVE_DATA) return self.current_wave < len(self.wave_data)