2024-12-30 14:36:23 +08:00

817 lines
37 KiB
Python

import pygame
import sys
import random
import math
from constants import *
from entities import Plant, Zombie, Projectile
from sprites import PLANT_DRAWINGS, ZOMBIE_DRAWINGS
class Sun:
def __init__(self, x, y, from_sky=True):
self.x = x
self.y = y
self.initial_x = x
self.initial_y = y
self.value = 25
self.rect = pygame.Rect(x, y, 40, 40)
self.collected = False
self.from_sky = from_sky
self.fall_speed = 1.5
self.lifetime = 300 if from_sky else 450
self.hover_offset = 0
self.hover_speed = 0.03
self.hover_range = 5
self.fade_start = 60
self.alpha = 0
self.fade_in = 255
self.glow_offset = 0
self.size = 40
self.collect_speed = 5
self.collecting = False
def move(self):
if self.from_sky and self.y < self.initial_y + WINDOW_HEIGHT//3:
self.y += self.fall_speed
self.rect.y = self.y
self.hover_offset = math.sin(pygame.time.get_ticks() * self.hover_speed) * self.hover_range
self.rect.y = self.y + self.hover_offset
self.glow_offset = abs(math.sin(pygame.time.get_ticks() * 0.002)) * 5
if self.fade_in > 0:
self.alpha = min(255, self.alpha + 10)
self.fade_in -= 10
self.lifetime -= 1
if self.lifetime <= self.fade_start:
self.alpha = max(0, int(255 * (self.lifetime / self.fade_start)))
def draw(self, screen):
sun_surface = pygame.Surface((50, 50), pygame.SRCALPHA)
glow_radius = 25 + self.glow_offset
pygame.draw.circle(sun_surface, (255, 255, 100, int(self.alpha * 0.3)), (25, 25), glow_radius)
pygame.draw.circle(sun_surface, (255, 255, 0, self.alpha), (25, 25), 20)
pygame.draw.circle(sun_surface, (255, 255, 200, self.alpha), (25, 25), 15)
pygame.draw.circle(sun_surface, (255, 255, 255, int(self.alpha * 0.7)), (25, 25), 8)
screen.blit(sun_surface, (self.rect.x - 5, self.rect.y - 5))
class Game:
def __init__(self):
pygame.init()
pygame.mixer.init()
# Set window mode with fixed resolution
self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("植物大战僵尸 - ChatDev制作")
self.clock = pygame.time.Clock()
self.state = GameState.MENU
# Set scale factors based on window size
self.base_width = WINDOW_WIDTH
self.base_height = WINDOW_HEIGHT
self.scale_x = 1.0
self.scale_y = 1.0
# Load and play background music
self.load_music()
# Add Chinese font
try:
self.font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 24)
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 24)
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 24)
except:
print("Warning: Could not load Chinese font, falling back to default font")
self.font = pygame.font.Font(None, 36)
self.large_font = pygame.font.Font(None, 74)
self.small_font = pygame.font.Font(None, 24)
self.reset_game()
def load_music(self):
try:
pygame.mixer.music.load("assets/music/bgm.mp3")
pygame.mixer.music.set_volume(0.5) # Set volume to 50%
pygame.mixer.music.play(-1) # -1 means loop indefinitely
except:
print("Warning: Could not load background music")
def reset_game(self):
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.particles = []
self.sun_points = 2025
self.selected_plant = None
self.spawn_timer = 0
self.sun_spawn_timer = 0
self.wave_number = 1
self.wave_timer = 600
self.score = 0
self.game_over = False
def spawn_sun(self):
if self.sun_spawn_timer <= 0:
x = random.randint(100, WINDOW_WIDTH - 100)
self.suns.append(Sun(x, -40))
self.sun_spawn_timer = random.randint(300, 500)
self.sun_spawn_timer -= 1
def spawn_zombie(self):
if self.spawn_timer <= 0:
# Ensure minimum number of zombies (5) are present
if len(self.zombies) < 5 + self.wave_number:
# Spawn multiple zombies at once if below minimum
zombies_to_spawn = max(2, 5 + self.wave_number - len(self.zombies))
for _ in range(zombies_to_spawn):
row = random.randint(0, GRID_ROWS - 1)
# Zombie type selection based on wave number
zombie_types = [
ZombieType.NORMAL,
ZombieType.CONE,
ZombieType.BUCKET,
ZombieType.NEWSPAPER,
ZombieType.DANCING
]
zombie_type = random.choice(zombie_types)
self.zombies.append(Zombie(row, zombie_type))
else:
# Regular spawn for maintaining zombie presence
row = random.randint(0, GRID_ROWS - 1)
zombie_types = [
ZombieType.NORMAL,
ZombieType.CONE,
ZombieType.BUCKET,
ZombieType.NEWSPAPER,
ZombieType.DANCING
]
zombie_type = random.choice(zombie_types)
self.zombies.append(Zombie(row, zombie_type))
# Adjust spawn timer based on wave - make it faster
base_timer = max(100, 300 - (self.wave_number * 40)) # Reduced from 500 to 300
variation = random.randint(-30, 30) # Add some randomness
self.spawn_timer = base_timer + variation
self.spawn_timer -= 1
def handle_resize(self, event):
# Update screen size
width, height = event.size
self.screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
# Calculate new scale factors
self.scale_x = width / self.base_width
self.scale_y = height / self.base_height
# Update font sizes based on scale
scale = min(self.scale_x, self.scale_y)
try:
self.font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(24 * scale))
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(24 * scale))
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(24 * scale))
except:
self.font = pygame.font.Font(None, int(36 * scale))
self.large_font = pygame.font.Font(None, int(74 * scale))
self.small_font = pygame.font.Font(None, int(24 * scale))
def get_scaled_rect(self, rect):
# Helper function to scale rectangles
return pygame.Rect(
rect.x * self.scale_x,
rect.y * self.scale_y,
rect.width * self.scale_x,
rect.height * self.scale_y
)
def get_real_pos(self, pos):
# Convert screen position to game logic position
return (pos[0] / self.scale_x, pos[1] / self.scale_y)
def handle_click(self, pos):
# Convert screen position to game logic position
x, y = self.get_real_pos(pos)
# Check if clicking on a sun
for sun in self.suns[:]:
if sun.rect.collidepoint(x, y) and not sun.collected:
self.sun_points += sun.value
self.suns.remove(sun)
continue
# Plant placement
# Calculate grid position
grid_x = int(x // CELL_SIZE)
grid_y = int((y - TOP_MARGIN) // CELL_SIZE)
# Check if click is within the planting area
if 0 <= grid_x < GRID_COLS and 0 <= grid_y < GRID_ROWS:
# Check if there's already a plant there
plant_exists = any(p.x == grid_x and p.y == grid_y for p in self.plants)
if not plant_exists and self.selected_plant:
cost = PLANT_STATS[self.selected_plant]["cost"]
if self.sun_points >= cost:
self.plants.append(Plant(grid_x, grid_y, self.selected_plant))
self.sun_points -= cost
self.selected_plant = None
def update_plants(self):
for plant in self.plants:
plant.update()
if plant.can_shoot():
if plant.type == PlantType.SNOW_PEA:
self.projectiles.append(Projectile(plant.x, plant.y, freezing=True))
elif plant.type == PlantType.ROSE_SHOOTER:
# Shoot in current lane and adjacent lanes
lanes = [plant.y] # Current lane
if plant.y > 0: # Add lane above if exists
lanes.append(plant.y - 1)
if plant.y < GRID_ROWS - 1: # Add lane below if exists
lanes.append(plant.y + 1)
for lane in lanes:
# Create rose projectile with special properties
proj = Projectile(plant.x, lane, damage=20, speed=6, is_rose=True)
self.projectiles.append(proj)
else:
self.projectiles.append(Projectile(plant.x, plant.y))
plant.reset_timer()
elif plant.can_produce_sun():
self.suns.append(Sun(plant.rect.x, plant.rect.y, from_sky=False))
plant.reset_timer()
elif plant.can_eat_zombie():
# Check for zombies in range for Chomper
for zombie in self.zombies[:]:
if zombie.y == plant.y and abs(zombie.x - plant.x) <= 1:
self.zombies.remove(zombie)
plant.start_eating()
self.score += 100
break
def update_combat(self):
# Update projectiles and check collisions
for projectile in self.projectiles[:]:
if not projectile.active:
self.projectiles.remove(projectile)
continue
projectile.move()
for zombie in self.zombies[:]:
if projectile.rect.colliderect(zombie.rect):
# Create impact effect based on projectile type
if projectile.is_rose:
# Rose shooter effect (pink petals)
color = (255, 192, 203) # Pink for rose
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 45,
'color': (255, 192, 203),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'petal'
})
# Add sparkle particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.7),
'dy': math.sin(angle) * (speed * 0.7),
'lifetime': 30,
'color': (255, 255, 255),
'size': size * 0.5,
'is_petal': False
})
zombie.intoxicate()
elif projectile.freezing:
# Snow pea effect (ice crystals)
color = (0, 191, 255) # Ice blue
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 40,
'color': (0, 191, 255),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'snowflake'
})
# Add sparkle particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.7),
'dy': math.sin(angle) * (speed * 0.7),
'lifetime': 25,
'color': (255, 255, 255),
'size': size * 0.4,
'is_petal': False
})
zombie.freeze()
else:
# Regular peashooter effect (leaves and splashes)
color = (0, 255, 0) # Green
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 35,
'color': (0, 200, 0),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'leaf'
})
# Add splash particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.8),
'dy': math.sin(angle) * (speed * 0.8),
'lifetime': 20,
'color': (150, 255, 150),
'size': size * 0.6,
'is_petal': False
})
zombie.take_damage(projectile.damage)
if projectile.freezing:
zombie.freeze()
zombie.stun_timer = 2
if zombie.health <= 0:
# Add death particles
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
self.particles.append({
'x': zombie.rect.x + CELL_SIZE//2,
'y': zombie.rect.y + CELL_SIZE//2,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 30,
'color': (139, 69, 19), # Brown for zombie parts
'size': random.uniform(3, 6)
})
self.zombies.remove(zombie)
self.score += 100
if projectile in self.projectiles:
self.projectiles.remove(projectile)
break
# Update particles
for particle in self.particles[:]:
particle['x'] += particle['dx']
particle['y'] += particle['dy']
particle['lifetime'] -= 1
if particle['lifetime'] <= 0:
self.particles.remove(particle)
# Check zombie-plant interactions
for zombie in self.zombies:
for plant in self.plants[:]:
if zombie.rect.colliderect(plant.rect):
zombie.eating = True
plant.health -= zombie.damage
if plant.health <= 0:
# Add plant death particles
for _ in range(8):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(2, 4)
self.particles.append({
'x': plant.rect.x + CELL_SIZE//2,
'y': plant.rect.y + CELL_SIZE//2,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 25,
'color': (0, 100, 0), # Dark green for plant parts
'size': random.uniform(2, 5)
})
self.plants.remove(plant)
zombie.eating = False
break
else:
zombie.eating = False
def draw_lawn(self):
# Create a surface for the lawn at base size
lawn_surface = pygame.Surface((self.base_width, self.base_height))
lawn_surface.fill(LAWN_GREEN)
# Draw grid with better visuals
for row in range(GRID_ROWS):
for col in range(GRID_COLS):
rect = pygame.Rect(
col * CELL_SIZE,
row * CELL_SIZE + TOP_MARGIN,
CELL_SIZE,
CELL_SIZE
)
if (row + col) % 2 == 0:
pygame.draw.rect(lawn_surface, (115, 235, 0), rect)
pygame.draw.rect(lawn_surface, (100, 200, 0), rect, 1)
# Scale and blit to screen
scaled_surface = pygame.transform.scale(lawn_surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
def draw_plant_menu(self):
menu_height = 100 * self.scale_y
menu_surface = pygame.Surface((self.screen.get_width(), menu_height), pygame.SRCALPHA)
pygame.draw.rect(menu_surface, (139, 69, 19, 200), (0, 0, self.screen.get_width(), menu_height))
# Get mouse position for hover effect
mouse_x, mouse_y = pygame.mouse.get_pos()
menu_y = mouse_y - (self.screen.get_height() - menu_height)
# Plant cards
cards = [
(PlantType.SUNFLOWER, YELLOW, 50),
(PlantType.PEASHOOTER, GREEN, 100),
(PlantType.ROSE_SHOOTER, (255, 192, 203), 125), # Pink color for rose
(PlantType.CHOMPER, (148, 0, 211), 150),
(PlantType.SNOW_PEA, (0, 191, 255), 175)
]
card_width = 70 * self.scale_x
card_height = 80 * self.scale_y
card_spacing = 90 * self.scale_x
for i, (plant_type, color, cost) in enumerate(cards):
card_x = 10 * self.scale_x + i * card_spacing
card_rect = pygame.Rect(card_x, 10 * self.scale_y, card_width, card_height)
# Check if card is hovered or selected
is_hovered = (0 <= menu_y <= card_height + 20 * self.scale_y and
card_x <= mouse_x <= card_x + card_width)
is_selected = self.selected_plant == plant_type
# Draw card background with hover/selected effect
if is_selected:
# Glowing effect for selected card
glow_surface = pygame.Surface((card_width + 4, card_height + 4), pygame.SRCALPHA)
pygame.draw.rect(glow_surface, (*PLANT_STATS[plant_type]["color"], 128),
(0, 0, card_width + 4, card_height + 4))
menu_surface.blit(glow_surface, (card_rect.x - 2, card_rect.y - 2))
pygame.draw.rect(menu_surface, WHITE,
(card_rect.x - 2, card_rect.y - 2, card_width + 4, card_height + 4),
max(1, int(2 * self.scale_x)))
elif is_hovered:
# Hover effect
pygame.draw.rect(menu_surface, (255, 255, 255, 30), card_rect)
pygame.draw.rect(menu_surface, color, card_rect)
# Draw plant image on card
if plant_type in PLANT_DRAWINGS:
# Create a smaller surface for the plant
plant_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
PLANT_DRAWINGS[plant_type](plant_surface, 0, 0, CELL_SIZE)
# Scale it down to fit the card
scaled_size = (int(50 * self.scale_x), int(50 * self.scale_y))
scaled_surface = pygame.transform.scale(plant_surface, scaled_size)
menu_surface.blit(scaled_surface,
(card_rect.x + 10 * self.scale_x,
card_rect.y + 5 * self.scale_y))
# Cost indicator with sun icon
sun_size = 10 * min(self.scale_x, self.scale_y)
pygame.draw.circle(menu_surface, YELLOW,
(card_rect.x + sun_size, card_rect.bottom - sun_size),
sun_size)
cost_text = self.small_font.render(str(cost), True, BLACK)
menu_surface.blit(cost_text,
(card_rect.x + sun_size * 2,
card_rect.bottom - sun_size * 1.5))
# Gray out if can't afford
if self.sun_points < cost:
gray_surface = pygame.Surface((card_width, card_height), pygame.SRCALPHA)
pygame.draw.rect(gray_surface, (128, 128, 128, 180),
(0, 0, card_width, card_height))
menu_surface.blit(gray_surface, card_rect)
self.screen.blit(menu_surface, (0, self.screen.get_height() - menu_height))
def draw_hud(self):
# Sun points
sun_size = 30 * min(self.scale_x, self.scale_y)
sun_icon = pygame.Surface((sun_size, sun_size), pygame.SRCALPHA)
pygame.draw.circle(sun_icon, YELLOW, (sun_size/2, sun_size/2), sun_size/2)
self.screen.blit(sun_icon, (10 * self.scale_x, 10 * self.scale_y))
sun_text = self.font.render(str(self.sun_points), True, BLACK)
self.screen.blit(sun_text, (45 * self.scale_x, 15 * self.scale_y))
# Wave number
wave_text = self.font.render(f"{self.wave_number} 波僵尸", True, BLACK)
self.screen.blit(wave_text,
(self.screen.get_width() - 200 * self.scale_x,
15 * self.scale_y))
# Score
score_text = self.font.render(f"得分: {self.score}", True, BLACK)
self.screen.blit(score_text,
(self.screen.get_width()//2 - score_text.get_width()//2,
15 * self.scale_y))
def draw_watermark(self):
watermark = self.small_font.render("ChatDev制作", True, (0, 0, 0, 128))
watermark.set_alpha(128) # Make it semi-transparent
self.screen.blit(watermark,
(self.screen.get_width() - watermark.get_width() - 10,
self.screen.get_height() - watermark.get_height() - 10))
def run(self):
while True:
if self.state == GameState.MENU:
self.run_menu()
elif self.state == GameState.PLAYING:
self.run_game()
elif self.state == GameState.GAME_OVER:
self.run_game_over()
def run_menu(self):
while self.state == GameState.MENU:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Left click
# Start game button area
button_rect = pygame.Rect(
self.screen.get_width()//2 - 100 * self.scale_x,
self.screen.get_height()//2,
200 * self.scale_x,
50 * self.scale_y
)
if button_rect.collidepoint(event.pos):
self.state = GameState.PLAYING
self.reset_game()
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
# Draw menu
self.screen.fill(LAWN_GREEN)
# Draw title
title = self.large_font.render("植物大战僵尸", True, BLACK)
self.screen.blit(title,
(self.screen.get_width()//2 - title.get_width()//2,
self.screen.get_height()//4))
# Draw start button
button_rect = pygame.Rect(
self.screen.get_width()//2 - 100 * self.scale_x,
self.screen.get_height()//2,
200 * self.scale_x,
50 * self.scale_y
)
pygame.draw.rect(self.screen, GREEN, button_rect)
pygame.draw.rect(self.screen, BLACK, button_rect, 2)
start_text = self.font.render("开始游戏", True, BLACK)
self.screen.blit(start_text,
(self.screen.get_width()//2 - start_text.get_width()//2,
self.screen.get_height()//2 + 5 * self.scale_y))
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
def run_game(self):
while self.state == GameState.PLAYING and not self.game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
real_pos = self.get_real_pos(mouse_pos)
if self.base_height - 100 <= real_pos[1] <= self.base_height:
menu_y = real_pos[1] - (self.base_height - 100)
if 10 <= menu_y <= 90:
card_x = int((real_pos[0] - 10) // 90) # Convert to integer
if 0 <= card_x <= 4:
plant_types = [
PlantType.SUNFLOWER,
PlantType.PEASHOOTER,
PlantType.ROSE_SHOOTER,
PlantType.CHOMPER,
PlantType.SNOW_PEA
]
if card_x < len(plant_types):
plant_type = plant_types[card_x]
if self.sun_points >= PLANT_STATS[plant_type]["cost"]:
self.selected_plant = plant_type
else:
self.handle_click(mouse_pos)
# Update game state
self.spawn_zombie()
self.spawn_sun()
self.update_plants()
self.update_combat()
# Wave management
self.wave_timer -= 1
if self.wave_timer <= 0:
self.wave_number += 1
self.wave_timer = 600
# Move entities
for zombie in self.zombies:
zombie.move()
if zombie.x <= 0:
self.game_over = True
self.state = GameState.GAME_OVER
for sun in self.suns[:]:
sun.move()
if sun.lifetime <= 0:
self.suns.remove(sun)
# Draw everything
self.draw_lawn()
# Create a game surface at base size and draw everything on it
game_surface = pygame.Surface((self.base_width, self.base_height), pygame.SRCALPHA)
for plant in self.plants:
plant.draw(game_surface)
for zombie in self.zombies:
zombie.draw(game_surface)
for projectile in self.projectiles:
projectile.draw(game_surface)
for sun in self.suns:
sun.draw(game_surface)
# Draw particles
for particle in self.particles:
if particle.get('is_petal', False):
# Draw shaped particles based on type
shape = particle.get('shape', 'petal')
particle_surface = pygame.Surface((particle['size'] * 2, particle['size'] * 2), pygame.SRCALPHA)
center = (particle['size'], particle['size'])
if shape == 'petal':
# Draw rose petal shape
for angle in range(0, 360, 72):
rad = math.radians(angle + particle['rotation'])
petal_x = center[0] + math.cos(rad) * particle['size']
petal_y = center[1] + math.sin(rad) * particle['size']
pygame.draw.circle(particle_surface, particle['color'],
(int(petal_x), int(petal_y)),
int(particle['size'] * 0.6))
elif shape == 'snowflake':
# Draw snowflake shape
for angle in range(0, 360, 45):
rad = math.radians(angle + particle['rotation'])
# Draw main line
end_x = center[0] + math.cos(rad) * particle['size']
end_y = center[1] + math.sin(rad) * particle['size']
pygame.draw.line(particle_surface, particle['color'],
center, (int(end_x), int(end_y)), 2)
# Draw side branches
branch_length = particle['size'] * 0.5
mid_x = center[0] + math.cos(rad) * particle['size'] * 0.6
mid_y = center[1] + math.sin(rad) * particle['size'] * 0.6
side_angle1 = rad + math.pi / 4
side_angle2 = rad - math.pi / 4
pygame.draw.line(particle_surface, particle['color'],
(int(mid_x), int(mid_y)),
(int(mid_x + math.cos(side_angle1) * branch_length),
int(mid_y + math.sin(side_angle1) * branch_length)), 2)
pygame.draw.line(particle_surface, particle['color'],
(int(mid_x), int(mid_y)),
(int(mid_x + math.cos(side_angle2) * branch_length),
int(mid_y + math.sin(side_angle2) * branch_length)), 2)
elif shape == 'leaf':
# Draw leaf shape
points = []
leaf_length = particle['size'] * 1.5
leaf_width = particle['size'] * 0.8
rad = math.radians(particle['rotation'])
# Create leaf shape points
for t in range(0, 360, 10):
t_rad = math.radians(t)
x = center[0] + math.cos(rad) * leaf_length * math.cos(t_rad) - \
math.sin(rad) * leaf_width * math.sin(t_rad)
y = center[1] + math.sin(rad) * leaf_length * math.cos(t_rad) + \
math.cos(rad) * leaf_width * math.sin(t_rad)
points.append((int(x), int(y)))
if len(points) > 2:
pygame.draw.polygon(particle_surface, particle['color'], points)
# Draw leaf vein
vein_start = center
vein_end = (int(center[0] + math.cos(rad) * leaf_length),
int(center[1] + math.sin(rad) * leaf_length))
pygame.draw.line(particle_surface, (0, 150, 0),
vein_start, vein_end, 1)
# Add fade out effect
alpha = int(255 * (particle['lifetime'] / 45))
particle_surface.set_alpha(alpha)
game_surface.blit(particle_surface,
(particle['x'] - particle['size'],
particle['y'] - particle['size']))
else:
# Draw regular circular particles
alpha = int(255 * (particle['lifetime'] / 30))
color = (*particle['color'][:3], alpha)
pygame.draw.circle(game_surface, color,
(int(particle['x']), int(particle['y'])),
int(particle['size']))
# Scale and blit the game surface
scaled_surface = pygame.transform.scale(game_surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
self.draw_plant_menu()
self.draw_hud()
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
def run_game_over(self):
while self.state == GameState.GAME_OVER:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Left click
self.state = GameState.MENU
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
# Draw game over screen
self.screen.fill((0, 0, 0)) # Black background
# Draw game over text
game_over_text = self.large_font.render("游戏结束", True, RED)
score_text = self.font.render(f"最终得分: {self.score}", True, WHITE)
self.screen.blit(game_over_text,
(self.screen.get_width()//2 - game_over_text.get_width()//2,
self.screen.get_height()//3))
self.screen.blit(score_text,
(self.screen.get_width()//2 - score_text.get_width()//2,
self.screen.get_height()//2))
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
if __name__ == "__main__":
game = Game()
game.run()