Files
mygame/game/ui.py
2026-05-20 21:23:46 +08:00

155 lines
6.8 KiB
Python

import math
import pygame
from game.config import (
WINDOW_WIDTH, WINDOW_HEIGHT, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT,
CELL_SIZE, COLOR_BLACK, COLOR_WHITE, COLOR_GOLD, COLOR_RED,
COLOR_GREEN, COLOR_DARK_GRAY, COLOR_GRAY, COLOR_BG,
TOWER_DATA, COLOR_LIGHT_BLUE,
)
class UI:
def __init__(self):
self.selected_tower = None
self.tower_buttons = []
self.wave_button = pygame.Rect(WINDOW_WIDTH - 120, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, 110, 40)
self._init_tower_buttons()
def _init_tower_buttons(self):
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):
rect = pygame.Rect(start_x + i * (btn_w + gap), WINDOW_HEIGHT - UI_BOTTOM_HEIGHT + 10, btn_w, 40)
self.tower_buttons.append((rect, t))
def handle_click(self, pos, gold):
for rect, t in self.tower_buttons:
if rect.collidepoint(pos):
price = TOWER_DATA[t]["price"]
if gold >= price:
self.selected_tower = t if self.selected_tower != t else None
return None
if self.wave_button.collidepoint(pos):
return "wave"
return None
def handle_grid_click(self, pos):
if self.selected_tower is None:
return None
return self.selected_tower
def draw(self, surface, gold, lives, wave_num, wave_active, has_more, font, small_font, total_waves=10):
# Top bar
pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, 0, WINDOW_WIDTH, UI_TOP_HEIGHT))
gold_text = font.render(f"金币: {gold}", True, COLOR_GOLD)
surface.blit(gold_text, (10, 10))
lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 3 else COLOR_GREEN)
surface.blit(lives_text, (160, 10))
wave_text = font.render(f"波次: {wave_num}/{total_waves}", True, COLOR_WHITE)
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
pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, WINDOW_HEIGHT - UI_BOTTOM_HEIGHT, WINDOW_WIDTH, UI_BOTTOM_HEIGHT))
for rect, t in self.tower_buttons:
data = TOWER_DATA[t]
selected = self.selected_tower == t
bg = (80, 80, 120) if selected else COLOR_GRAY
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)
icon_x = rect.x + 18
icon_y = rect.y + rect.height // 2
if t == "arrow":
pygame.draw.rect(surface, data["color"], (icon_x - 5, icon_y - 5, 10, 10))
elif t == "cannon":
pygame.draw.circle(surface, data["color"], (icon_x, icon_y), 7)
elif t == "slow":
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)
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)
surface.blit(name_text, (icon_x + 12, icon_y - 7))
# Wave button
if has_more:
btn_color = COLOR_GREEN if not wave_active else COLOR_GRAY
pygame.draw.rect(surface, btn_color, self.wave_button, border_radius=5)
pygame.draw.rect(surface, COLOR_BLACK, self.wave_button, 2, border_radius=5)
btn_text = small_font.render("开始波次" if not wave_active else "进行中...", True, COLOR_WHITE)
surface.blit(btn_text, (self.wave_button.x + 10, self.wave_button.y + 12))
def draw_placement_preview(self, surface, mx, my, game_map):
if self.selected_tower is None:
return
col, row = game_map.pixel_to_grid(mx, my)
if col < 0 or row < 0:
return
px, py = game_map.grid_to_pixel(col, row)
data = TOWER_DATA[self.selected_tower]
buildable = game_map.is_buildable(col, row)
color = (*data["color"][:3], 60) if buildable else (255, 0, 0, 60)
preview = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
preview.fill(color)
surface.blit(preview, (col * CELL_SIZE, row * CELL_SIZE + UI_TOP_HEIGHT))
if buildable:
pygame.draw.circle(surface, COLOR_WHITE, (px, py), data["range"], 1)
def draw_game_over(self, surface, won, font):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
surface.blit(overlay, (0, 0))
text = "胜利!" if won else "失败!"
color = COLOR_GOLD if won else COLOR_RED
rendered = font.render(text, True, color)
rect = rendered.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 - 20))
surface.blit(rendered, rect)
hint = font.render("按 R 重新开始", True, COLOR_WHITE)
hint_rect = hint.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 + 30))
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))