游戏可以运行
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
self.hp -= damage
|
if self.shield_hp > 0:
|
||||||
|
absorbed = min(self.shield_hp, damage)
|
||||||
|
self.shield_hp -= absorbed
|
||||||
|
damage -= absorbed
|
||||||
|
if damage > 0:
|
||||||
|
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))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
55
game/ui.py
55
game/ui.py
@@ -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))
|
||||||
|
|||||||
14
game/wave.py
14
game/wave.py
@@ -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}],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user