游戏可以运行

This commit is contained in:
2026-05-20 20:53:02 +08:00
parent a174a4bcd4
commit e5aa78e8be
7 changed files with 233 additions and 24 deletions

View File

@@ -32,10 +32,17 @@ COLOR_GRAY = (120, 120, 120)
COLOR_DARK_GRAY = (60, 60, 60) COLOR_DARK_GRAY = (60, 60, 60)
COLOR_GOLD = (255, 215, 0) COLOR_GOLD = (255, 215, 0)
COLOR_HIGHLIGHT = (255, 255, 255, 80) COLOR_HIGHLIGHT = (255, 255, 255, 80)
COLOR_DARK_BLUE = (30, 60, 150)
COLOR_DARK_GREEN = (30, 130, 50)
COLOR_BRIGHT_PURPLE = (180, 80, 255)
COLOR_BRIGHT_GREEN = (100, 255, 100)
COLOR_SILVER = (192, 192, 210)
COLOR_PINK = (255, 130, 170)
COLOR_LIGHT_BLUE = (100, 150, 255, 40)
# Game params # Game params
INITIAL_GOLD = 200 INITIAL_GOLD = 200
INITIAL_LIVES = 20 INITIAL_LIVES = 10
TOTAL_WAVES = 10 TOTAL_WAVES = 10
# Tower stats # Tower stats
@@ -43,6 +50,9 @@ TOWER_DATA = {
"arrow": {"damage": 20, "range": 120, "fire_rate": 0.5, "price": 50, "color": COLOR_BLUE, "name": "箭塔"}, "arrow": {"damage": 20, "range": 120, "fire_rate": 0.5, "price": 50, "color": COLOR_BLUE, "name": "箭塔"},
"cannon": {"damage": 80, "range": 100, "fire_rate": 1.5, "price": 100, "color": COLOR_ORANGE, "name": "炮塔", "splash": 60}, "cannon": {"damage": 80, "range": 100, "fire_rate": 1.5, "price": 100, "color": COLOR_ORANGE, "name": "炮塔", "splash": 60},
"slow": {"damage": 5, "range": 130, "fire_rate": 0.8, "price": 75, "color": COLOR_CYAN, "name": "减速塔", "slow_factor": 0.5, "slow_duration": 2.0}, "slow": {"damage": 5, "range": 130, "fire_rate": 0.8, "price": 75, "color": COLOR_CYAN, "name": "减速塔", "slow_factor": 0.5, "slow_duration": 2.0},
"sniper": {"damage": 120, "range": 200, "fire_rate": 2.0, "price": 120, "color": COLOR_DARK_BLUE, "name": "狙击塔"},
"poison": {"damage": 5, "range": 110, "fire_rate": 1.2, "price": 80, "color": COLOR_DARK_GREEN, "name": "毒塔", "poison_dps": 8, "poison_duration": 3.0},
"lightning": {"damage": 40, "range": 140, "fire_rate": 1.0, "price": 150, "color": COLOR_BRIGHT_PURPLE, "name": "雷电塔", "chain_count": 2, "chain_range": 80, "chain_decay": 0.5},
} }
# Enemy stats # Enemy stats
@@ -51,6 +61,9 @@ ENEMY_DATA = {
"fast": {"hp": 60, "speed": 4, "reward": 8, "color": COLOR_YELLOW, "size": 10, "name": "快速兵"}, "fast": {"hp": 60, "speed": 4, "reward": 8, "color": COLOR_YELLOW, "size": 10, "name": "快速兵"},
"heavy": {"hp": 300, "speed": 1, "reward": 25, "color": COLOR_PURPLE, "size": 14, "name": "重装兵"}, "heavy": {"hp": 300, "speed": 1, "reward": 25, "color": COLOR_PURPLE, "size": 14, "name": "重装兵"},
"boss": {"hp": 800, "speed": 0.8, "reward": 100, "color": COLOR_DARK_RED, "size": 20, "name": "BOSS"}, "boss": {"hp": 800, "speed": 0.8, "reward": 100, "color": COLOR_DARK_RED, "size": 20, "name": "BOSS"},
"swarm": {"hp": 30, "speed": 5, "reward": 3, "color": COLOR_BRIGHT_GREEN, "size": 7, "name": "虫群兵"},
"shield": {"hp": 200, "speed": 1.5, "reward": 20, "color": COLOR_SILVER, "size": 15, "name": "护盾兵", "shield_hp": 200},
"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)

View File

@@ -1,6 +1,7 @@
import pygame import pygame
import math import math
from game.config import ENEMY_DATA, PATH_WAYPOINTS, COLOR_BLACK, COLOR_GREEN, COLOR_RED from game.config import ENEMY_DATA, PATH_WAYPOINTS, COLOR_BLACK, COLOR_GREEN, COLOR_RED, COLOR_BLUE
from game.utils import distance
class Enemy: class Enemy:
@@ -17,6 +18,20 @@ class Enemy:
self.alive = True self.alive = True
self.reached_end = False self.reached_end = False
# Shield
self.max_shield = data.get("shield_hp", 0)
self.shield_hp = self.max_shield
# Poison DoT
self.poison_timer = 0
self.poison_dps = 0
# Healer
self.heal_range = data.get("heal_range", 0)
self.heal_amount = data.get("heal_amount", 0)
self.heal_interval = data.get("heal_interval", 2.0)
self.heal_timer = self.heal_interval
self.x, self.y = PATH_WAYPOINTS[0] self.x, self.y = PATH_WAYPOINTS[0]
self.waypoint_index = 1 self.waypoint_index = 1
self.slow_timer = 0 self.slow_timer = 0
@@ -25,21 +40,50 @@ class Enemy:
self.speed = self.base_speed * factor self.speed = self.base_speed * factor
self.slow_timer = duration self.slow_timer = duration
def apply_poison(self, dps, duration):
self.poison_dps = dps
self.poison_timer = duration
def take_damage(self, damage): def take_damage(self, damage):
if self.shield_hp > 0:
absorbed = min(self.shield_hp, damage)
self.shield_hp -= absorbed
damage -= absorbed
if damage > 0:
self.hp -= damage self.hp -= damage
if self.hp <= 0: if self.hp <= 0:
self.hp = 0 self.hp = 0
self.alive = False self.alive = False
def update(self, dt): def update(self, dt, enemies=None):
if not self.alive: if not self.alive:
return return
# Poison DoT
if self.poison_timer > 0:
self.poison_timer -= dt
self.hp -= self.poison_dps * dt
if self.hp <= 0:
self.hp = 0
self.alive = False
return
# Slow
if self.slow_timer > 0: if self.slow_timer > 0:
self.slow_timer -= dt self.slow_timer -= dt
if self.slow_timer <= 0: if self.slow_timer <= 0:
self.speed = self.base_speed self.speed = self.base_speed
# Healer ability
if self.type == "healer" and enemies:
self.heal_timer -= dt
if self.heal_timer <= 0:
self.heal_timer = self.heal_interval
for e in enemies:
if e is not self and e.alive and distance(self.x, self.y, e.x, e.y) <= self.heal_range:
e.hp = min(e.hp + self.heal_amount, e.max_hp)
# Movement
if self.waypoint_index >= len(PATH_WAYPOINTS): if self.waypoint_index >= len(PATH_WAYPOINTS):
self.reached_end = True self.reached_end = True
self.alive = False self.alive = False
@@ -62,14 +106,43 @@ class Enemy:
if not self.alive: if not self.alive:
return return
ix, iy = int(self.x), int(self.y) ix, iy = int(self.x), int(self.y)
# Healer aura
if self.type == "healer":
aura = pygame.Surface((self.heal_range * 2, self.heal_range * 2), pygame.SRCALPHA)
pygame.draw.circle(aura, (255, 130, 170, 30), (self.heal_range, self.heal_range), self.heal_range)
surface.blit(aura, (ix - self.heal_range, iy - self.heal_range))
# Poison tint
if self.poison_timer > 0:
pygame.draw.circle(surface, (0, 180, 0), (ix, iy), self.size + 3)
pygame.draw.circle(surface, self.color, (ix, iy), self.size) pygame.draw.circle(surface, self.color, (ix, iy), self.size)
pygame.draw.circle(surface, COLOR_BLACK, (ix, iy), self.size, 2) pygame.draw.circle(surface, COLOR_BLACK, (ix, iy), self.size, 2)
# Boss eyes
if self.type == "boss": if self.type == "boss":
for offset in [(-6, -6), (6, -6)]: for offset in [(-6, -6), (6, -6)]:
pygame.draw.circle(surface, (255, 200, 0), (ix + offset[0], iy + offset[1]), 4) pygame.draw.circle(surface, (255, 200, 0), (ix + offset[0], iy + offset[1]), 4)
pygame.draw.circle(surface, COLOR_BLACK, (ix + offset[0], iy + offset[1]), 4, 1) pygame.draw.circle(surface, COLOR_BLACK, (ix + offset[0], iy + offset[1]), 4, 1)
# Shield ring
if self.type == "shield" and self.shield_hp > 0:
shield_ratio = self.shield_hp / self.max_shield
arc_color = COLOR_BLUE
pygame.draw.circle(surface, arc_color, (ix, iy), self.size + 4, 2)
# Swarm wings
if self.type == "swarm":
for dx_off in [-4, 4]:
pygame.draw.line(surface, (200, 255, 200), (ix + dx_off, iy - 3), (ix + dx_off * 2, iy - 7), 1)
# Healer cross
if self.type == "healer":
pygame.draw.rect(surface, (255, 255, 255), (ix - 1, iy - 5, 3, 10))
pygame.draw.rect(surface, (255, 255, 255), (ix - 5, iy - 1, 10, 3))
# Health bar
bar_w = self.size * 2 + 4 bar_w = self.size * 2 + 4
if self.type == "boss": if self.type == "boss":
bar_w = self.size * 3 bar_w = self.size * 3
@@ -79,3 +152,10 @@ class Enemy:
ratio = self.hp / self.max_hp ratio = self.hp / self.max_hp
pygame.draw.rect(surface, COLOR_RED, (bx, by, bar_w, bar_h)) pygame.draw.rect(surface, COLOR_RED, (bx, by, bar_w, bar_h))
pygame.draw.rect(surface, COLOR_GREEN, (bx, by, int(bar_w * ratio), bar_h)) pygame.draw.rect(surface, COLOR_GREEN, (bx, by, int(bar_w * ratio), bar_h))
# Shield bar
if self.max_shield > 0 and self.shield_hp > 0:
s_bar_h = 3
s_ratio = self.shield_hp / self.max_shield
pygame.draw.rect(surface, (80, 80, 80), (bx, by - s_bar_h - 1, bar_w, s_bar_h))
pygame.draw.rect(surface, COLOR_BLUE, (bx, by - s_bar_h - 1, int(bar_w * s_ratio), s_bar_h))

View File

@@ -88,10 +88,13 @@ class Game:
self.ui.selected_tower = None self.ui.selected_tower = None
def _update(self, dt): def _update(self, dt):
if self.ui.selected_tower is not None:
dt *= 0.3
self.wave_mgr.update(dt, self.enemies) self.wave_mgr.update(dt, self.enemies)
for e in self.enemies: for e in self.enemies:
e.update(dt) e.update(dt, self.enemies)
if e.reached_end: if e.reached_end:
self.lives -= 2 if e.type == "boss" else 1 self.lives -= 2 if e.type == "boss" else 1
if self.lives <= 0: if self.lives <= 0:
@@ -141,6 +144,8 @@ class Game:
self.font, self.small_font, self.font, self.small_font,
) )
self.ui.draw_slow_overlay(self.screen)
if self.game_over: if self.game_over:
self.ui.draw_game_over(self.screen, self.won, self.big_font) self.ui.draw_game_over(self.screen, self.won, self.big_font)

View File

@@ -1,11 +1,14 @@
import pygame import pygame
import math import math
from game.config import COLOR_WHITE, COLOR_ORANGE, COLOR_CYAN from game.config import COLOR_WHITE, COLOR_ORANGE, COLOR_CYAN, COLOR_DARK_GREEN, COLOR_BRIGHT_PURPLE
from game.utils import distance from game.utils import distance
class Projectile: class Projectile:
def __init__(self, x, y, target, damage, proj_type="arrow", splash=0, slow_factor=0, slow_duration=0): def __init__(self, x, y, target, damage, proj_type="arrow", splash=0,
slow_factor=0, slow_duration=0,
poison_dps=0, poison_duration=0,
chain_count=0, chain_range=0, chain_decay=0):
self.x = x self.x = x
self.y = y self.y = y
self.target = target self.target = target
@@ -14,6 +17,11 @@ class Projectile:
self.splash = splash self.splash = splash
self.slow_factor = slow_factor self.slow_factor = slow_factor
self.slow_duration = slow_duration self.slow_duration = slow_duration
self.poison_dps = poison_dps
self.poison_duration = poison_duration
self.chain_count = chain_count
self.chain_range = chain_range
self.chain_decay = chain_decay
self.speed = 400 self.speed = 400
self.alive = True self.alive = True
@@ -39,6 +47,7 @@ class Projectile:
def _hit(self, enemies): def _hit(self, enemies):
self.alive = False self.alive = False
if self.proj_type == "cannon" and self.splash > 0: if self.proj_type == "cannon" and self.splash > 0:
for e in enemies: for e in enemies:
if e.alive and distance(self.target.x, self.target.y, e.x, e.y) <= self.splash: if e.alive and distance(self.target.x, self.target.y, e.x, e.y) <= self.splash:
@@ -49,6 +58,33 @@ class Projectile:
if self.proj_type == "slow" and self.target.alive: if self.proj_type == "slow" and self.target.alive:
self.target.apply_slow(self.slow_factor, self.slow_duration) self.target.apply_slow(self.slow_factor, self.slow_duration)
if self.proj_type == "poison" and self.target.alive:
self.target.apply_poison(self.poison_dps, self.poison_duration)
if self.proj_type == "lightning" and self.chain_count > 0:
self._chain_lightning(enemies, self.target, self.damage, self.chain_count)
def _chain_lightning(self, enemies, source, damage, chains_left):
if chains_left <= 0:
return
hit = [self.target, source]
current = source
current_damage = damage
for _ in range(chains_left):
current_damage *= self.chain_decay
best = None
best_dist = self.chain_range
for e in enemies:
if e.alive and e not in hit:
d = distance(current.x, current.y, e.x, e.y)
if d < best_dist:
best_dist = d
best = e
if best:
best.take_damage(current_damage)
hit.append(best)
current = best
def draw(self, surface): def draw(self, surface):
if not self.alive: if not self.alive:
return return
@@ -59,3 +95,12 @@ class Projectile:
pygame.draw.circle(surface, COLOR_ORANGE, (ix, iy), 5) pygame.draw.circle(surface, COLOR_ORANGE, (ix, iy), 5)
elif self.proj_type == "slow": elif self.proj_type == "slow":
pygame.draw.circle(surface, COLOR_CYAN, (ix, iy), 4) pygame.draw.circle(surface, COLOR_CYAN, (ix, iy), 4)
elif self.proj_type == "sniper":
pygame.draw.circle(surface, (150, 180, 255), (ix, iy), 4)
pygame.draw.circle(surface, COLOR_WHITE, (ix, iy), 2)
elif self.proj_type == "poison":
pygame.draw.circle(surface, COLOR_DARK_GREEN, (ix, iy), 5)
pygame.draw.circle(surface, (100, 255, 100), (ix, iy), 3)
elif self.proj_type == "lightning":
pygame.draw.circle(surface, COLOR_BRIGHT_PURPLE, (ix, iy), 5)
pygame.draw.circle(surface, (255, 255, 255), (ix, iy), 2)

View File

@@ -22,6 +22,11 @@ class Tower:
self.splash = data.get("splash", 0) self.splash = data.get("splash", 0)
self.slow_factor = data.get("slow_factor", 0) self.slow_factor = data.get("slow_factor", 0)
self.slow_duration = data.get("slow_duration", 0) self.slow_duration = data.get("slow_duration", 0)
self.poison_dps = data.get("poison_dps", 0)
self.poison_duration = data.get("poison_duration", 0)
self.chain_count = data.get("chain_count", 0)
self.chain_range = data.get("chain_range", 0)
self.chain_decay = data.get("chain_decay", 0)
self.cooldown = 0 self.cooldown = 0
self.target = None self.target = None
@@ -54,11 +59,16 @@ class Tower:
if self.target and self.cooldown <= 0: if self.target and self.cooldown <= 0:
self.cooldown = self.fire_rate self.cooldown = self.fire_rate
proj_type = {"arrow": "arrow", "cannon": "cannon", "slow": "slow"}[self.type] proj_type = {
"arrow": "arrow", "cannon": "cannon", "slow": "slow",
"sniper": "sniper", "poison": "poison", "lightning": "lightning",
}[self.type]
projectiles.append(Projectile( projectiles.append(Projectile(
self.x, self.y, self.target, self.damage, self.x, self.y, self.target, self.damage,
proj_type=proj_type, splash=self.splash, proj_type=proj_type, splash=self.splash,
slow_factor=self.slow_factor, slow_duration=self.slow_duration, slow_factor=self.slow_factor, slow_duration=self.slow_duration,
poison_dps=self.poison_dps, poison_duration=self.poison_duration,
chain_count=self.chain_count, chain_range=self.chain_range, chain_decay=self.chain_decay,
)) ))
def draw(self, surface): def draw(self, surface):
@@ -74,6 +84,25 @@ class Tower:
points = [(ix, iy - 14), (ix + 14, iy), (ix, iy + 14), (ix - 14, iy)] points = [(ix, iy - 14), (ix + 14, iy), (ix, iy + 14), (ix - 14, iy)]
pygame.draw.polygon(surface, self.color, points) pygame.draw.polygon(surface, self.color, points)
pygame.draw.polygon(surface, COLOR_BLACK, points, 2) pygame.draw.polygon(surface, COLOR_BLACK, points, 2)
elif self.type == "sniper":
pts = [(ix, iy - 16), (ix + 14, iy + 12), (ix - 14, iy + 12)]
pygame.draw.polygon(surface, self.color, pts)
pygame.draw.polygon(surface, COLOR_BLACK, pts, 2)
pygame.draw.line(surface, (200, 200, 255), (ix, iy - 10), (ix, iy + 6), 2)
elif self.type == "poison":
pts = []
for i in range(6):
angle = math.pi / 3 * i - math.pi / 6
pts.append((ix + int(14 * math.cos(angle)), iy + int(14 * math.sin(angle))))
pygame.draw.polygon(surface, self.color, pts)
pygame.draw.polygon(surface, COLOR_BLACK, pts, 2)
elif self.type == "lightning":
pts = [
(ix - 2, iy - 14), (ix + 6, iy - 4), (ix, iy - 4),
(ix + 4, iy + 14), (ix - 4, iy + 2), (ix + 1, iy + 2),
]
pygame.draw.polygon(surface, self.color, pts)
pygame.draw.polygon(surface, COLOR_BLACK, pts, 2)
if self.target and self.target.alive: if self.target and self.target.alive:
pygame.draw.line(surface, (*self.color, ), (ix, iy), (int(self.target.x), int(self.target.y)), 1) pygame.draw.line(surface, (*self.color, ), (ix, iy), (int(self.target.x), int(self.target.y)), 1)

View File

@@ -1,9 +1,10 @@
import math
import pygame import pygame
from game.config import ( 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, TOWER_DATA, INITIAL_LIVES, TOTAL_WAVES, COLOR_LIGHT_BLUE,
) )
@@ -11,13 +12,17 @@ class UI:
def __init__(self): def __init__(self):
self.selected_tower = None self.selected_tower = None
self.tower_buttons = [] self.tower_buttons = []
self.wave_button = pygame.Rect(WINDOW_WIDTH - 140, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, 130, 40) self.wave_button = pygame.Rect(WINDOW_WIDTH - 120, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, 110, 40)
self._init_tower_buttons() self._init_tower_buttons()
def _init_tower_buttons(self): def _init_tower_buttons(self):
types = ["arrow", "cannon", "slow"] types = ["arrow", "cannon", "slow", "sniper", "poison", "lightning"]
btn_w = 120
gap = 5
total_w = len(types) * btn_w + (len(types) - 1) * gap
start_x = (WINDOW_WIDTH - 120 - 20 - total_w) // 2 + 10
for i, t in enumerate(types): for i, t in enumerate(types):
rect = pygame.Rect(10 + i * 160, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, 150, 40) rect = pygame.Rect(start_x + i * (btn_w + gap), WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, btn_w, 40)
self.tower_buttons.append((rect, t)) self.tower_buttons.append((rect, t))
def handle_click(self, pos, gold): def handle_click(self, pos, gold):
@@ -45,11 +50,16 @@ class UI:
gold_text = font.render(f"金币: {gold}", True, COLOR_GOLD) gold_text = font.render(f"金币: {gold}", True, COLOR_GOLD)
surface.blit(gold_text, (10, 10)) surface.blit(gold_text, (10, 10))
lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 5 else COLOR_GREEN) lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 3 else COLOR_GREEN)
surface.blit(lives_text, (200, 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, (380, 10)) surface.blit(wave_text, (310, 10))
# Slow-time indicator
if self.selected_tower:
slow_text = small_font.render("[ 子弹时间 ]", True, (100, 180, 255))
surface.blit(slow_text, (500, 12))
# Bottom bar # Bottom bar
pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT, WINDOW_WIDTH, UI_BOTTOM_HEIGHT)) pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT, WINDOW_WIDTH, UI_BOTTOM_HEIGHT))
@@ -61,7 +71,7 @@ class UI:
pygame.draw.rect(surface, bg, rect, border_radius=5) pygame.draw.rect(surface, bg, rect, border_radius=5)
pygame.draw.rect(surface, COLOR_WHITE if selected else COLOR_BLACK, rect, 2, border_radius=5) pygame.draw.rect(surface, COLOR_WHITE if selected else COLOR_BLACK, rect, 2, border_radius=5)
icon_x = rect.x + 20 icon_x = rect.x + 18
icon_y = rect.y + rect.height // 2 icon_y = rect.y + rect.height // 2
if t == "arrow": if t == "arrow":
pygame.draw.rect(surface, data["color"], (icon_x - 5, icon_y - 5, 10, 10)) pygame.draw.rect(surface, data["color"], (icon_x - 5, icon_y - 5, 10, 10))
@@ -70,9 +80,24 @@ class UI:
elif t == "slow": elif t == "slow":
pts = [(icon_x, icon_y-7), (icon_x+7, icon_y), (icon_x, icon_y+7), (icon_x-7, icon_y)] pts = [(icon_x, icon_y-7), (icon_x+7, icon_y), (icon_x, icon_y+7), (icon_x-7, icon_y)]
pygame.draw.polygon(surface, data["color"], pts) pygame.draw.polygon(surface, data["color"], pts)
elif t == "sniper":
pts = [(icon_x, icon_y-8), (icon_x+7, icon_y+6), (icon_x-7, icon_y+6)]
pygame.draw.polygon(surface, data["color"], pts)
elif t == "poison":
pts = []
for i in range(6):
angle = math.pi / 3 * i - math.pi / 6
pts.append((icon_x + int(7 * math.cos(angle)), icon_y + int(7 * math.sin(angle))))
pygame.draw.polygon(surface, data["color"], pts)
elif t == "lightning":
pts = [
(icon_x-1, icon_y-7), (icon_x+4, icon_y-2), (icon_x, icon_y-2),
(icon_x+2, icon_y+7), (icon_x-3, icon_y+1), (icon_x+1, icon_y+1),
]
pygame.draw.polygon(surface, data["color"], pts)
name_text = small_font.render(f"{data['name']} {data['price']}G", True, COLOR_WHITE) name_text = small_font.render(f"{data['name']} {data['price']}G", True, COLOR_WHITE)
surface.blit(name_text, (icon_x + 15, icon_y - 7)) surface.blit(name_text, (icon_x + 12, icon_y - 7))
# Wave button # Wave button
if has_more: if has_more:
@@ -115,3 +140,15 @@ class UI:
hint = font.render("按 R 重新开始", True, COLOR_WHITE) hint = font.render("按 R 重新开始", True, COLOR_WHITE)
hint_rect = hint.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 + 30)) hint_rect = hint.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 + 30))
surface.blit(hint, hint_rect) surface.blit(hint, hint_rect)
def draw_slow_overlay(self, surface):
if self.selected_tower is None:
return
border = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
pygame.draw.rect(border, (100, 150, 255, 25), (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT))
for side_w in [4]:
pygame.draw.rect(border, (100, 150, 255, 60), (0, 0, side_w, WINDOW_HEIGHT))
pygame.draw.rect(border, (100, 150, 255, 60), (WINDOW_WIDTH - side_w, 0, side_w, WINDOW_HEIGHT))
pygame.draw.rect(border, (100, 150, 255, 60), (0, 0, WINDOW_WIDTH, side_w))
pygame.draw.rect(border, (100, 150, 255, 60), (0, WINDOW_HEIGHT - side_w, WINDOW_WIDTH, side_w))
surface.blit(border, (0, 0))

View File

@@ -3,15 +3,15 @@ from game.enemy import Enemy
WAVE_DATA = [ WAVE_DATA = [
[{"type": "normal", "count": 5, "interval": 1.0}], [{"type": "normal", "count": 5, "interval": 1.0}],
[{"type": "normal", "count": 8, "interval": 0.8}], [{"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": "normal", "count": 5, "interval": 0.8}, {"type": "fast", "count": 3, "interval": 0.6}],
[{"type": "fast", "count": 8, "interval": 0.5}], [{"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": 5, "interval": 0.6}, {"type": "heavy", "count": 2, "interval": 1.5}, {"type": "boss", "count": 1, "interval": 2.0}],
[{"type": "normal", "count": 10, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}], [{"type": "normal", "count": 8, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}, {"type": "swarm", "count": 8, "interval": 0.2}],
[{"type": "heavy", "count": 5, "interval": 1.0}, {"type": "fast", "count": 5, "interval": 0.5}], [{"type": "shield", "count": 3, "interval": 1.2}, {"type": "healer", "count": 2, "interval": 1.5}, {"type": "normal", "count": 6, "interval": 0.6}],
[{"type": "normal", "count": 8, "interval": 0.4}, {"type": "heavy", "count": 3, "interval": 0.8}, {"type": "fast", "count": 6, "interval": 0.4}], [{"type": "heavy", "count": 4, "interval": 0.8}, {"type": "fast", "count": 5, "interval": 0.5}, {"type": "shield", "count": 3, "interval": 1.0}],
[{"type": "heavy", "count": 8, "interval": 0.7}, {"type": "fast", "count": 8, "interval": 0.3}], [{"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": 10, "interval": 0.3}, {"type": "boss", "count": 2, "interval": 2.0}], [{"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}],
] ]