|
|
|
@ -0,0 +1,372 @@
|
|
|
|
|
import pygame
|
|
|
|
|
import random
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
# 获取当前脚本的目录
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
|
|
|
|
# 初始化Pygame
|
|
|
|
|
pygame.init()
|
|
|
|
|
|
|
|
|
|
# 游戏常量设置
|
|
|
|
|
SCREEN_WIDTH = 800
|
|
|
|
|
SCREEN_HEIGHT = 600
|
|
|
|
|
FPS = 60
|
|
|
|
|
PLAYER_SPEED = 8
|
|
|
|
|
BULLET_SPEED = 15
|
|
|
|
|
ALIEN_COUNT = 8
|
|
|
|
|
PLAYER_SIZE = (50, 50)
|
|
|
|
|
ALIEN_SIZE = (50, 50)
|
|
|
|
|
BULLET_SIZE = (20, 40)
|
|
|
|
|
|
|
|
|
|
# 颜色定义
|
|
|
|
|
BACKGROUND_COLOR = (25, 62, 11)
|
|
|
|
|
TEXT_COLOR = (255, 0, 255)
|
|
|
|
|
SCORE_COLOR = (255, 0, 0)
|
|
|
|
|
|
|
|
|
|
# 创建游戏窗口
|
|
|
|
|
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
|
|
|
pygame.display.set_caption("外星人大战")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 字体设置
|
|
|
|
|
def load_chinese_font(size=48):
|
|
|
|
|
"""尝试多种方式加载中文字体"""
|
|
|
|
|
font_paths = [
|
|
|
|
|
"c:/Windows/Fonts/simhei.ttf",
|
|
|
|
|
"c:/Windows/Fonts/msyh.ttc",
|
|
|
|
|
"c:/Windows/Fonts/simsun.ttc",
|
|
|
|
|
"/System/Library/Fonts/PingFang.ttc",
|
|
|
|
|
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for path in font_paths:
|
|
|
|
|
try:
|
|
|
|
|
if os.path.exists(path):
|
|
|
|
|
return pygame.font.Font(path, size)
|
|
|
|
|
except:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
return pygame.font.SysFont('simhei', size)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
print("警告: 无法加载中文字体,将使用默认字体")
|
|
|
|
|
return pygame.font.Font(None, size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 加载字体
|
|
|
|
|
chinese_font_large = load_chinese_font(48)
|
|
|
|
|
chinese_font_medium = load_chinese_font(36)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建默认图像
|
|
|
|
|
def create_default_image(size, color):
|
|
|
|
|
"""创建一个默认图像,带有透明背景"""
|
|
|
|
|
image = pygame.Surface(size, pygame.SRCALPHA)
|
|
|
|
|
pygame.draw.circle(image, color, (size[0] // 2, size[1] // 2), min(size[0], size[1]) // 2)
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建默认玩家图像(蓝色飞船)
|
|
|
|
|
def create_default_player():
|
|
|
|
|
image = pygame.Surface(PLAYER_SIZE, pygame.SRCALPHA)
|
|
|
|
|
# 飞船主体(三角形)
|
|
|
|
|
pygame.draw.polygon(image, (30, 144, 255), [
|
|
|
|
|
(PLAYER_SIZE[0] // 2, 0), # 顶点
|
|
|
|
|
(0, PLAYER_SIZE[1]), # 左下
|
|
|
|
|
(PLAYER_SIZE[0], PLAYER_SIZE[1]) # 右下
|
|
|
|
|
])
|
|
|
|
|
# 飞船窗口(小圆)
|
|
|
|
|
pygame.draw.circle(image, (200, 200, 255), (PLAYER_SIZE[0] // 2, PLAYER_SIZE[1] // 3), PLAYER_SIZE[0] // 6)
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建默认外星人图像(绿色UFO)
|
|
|
|
|
def create_default_alien():
|
|
|
|
|
image = pygame.Surface(ALIEN_SIZE, pygame.SRCALPHA)
|
|
|
|
|
# UFO主体(椭圆)
|
|
|
|
|
pygame.draw.ellipse(image, (50, 205, 50), (0, ALIEN_SIZE[1] // 4, ALIEN_SIZE[0], ALIEN_SIZE[1] // 2))
|
|
|
|
|
# UFO顶部(半圆)
|
|
|
|
|
pygame.draw.circle(image, (180, 230, 180), (ALIEN_SIZE[0] // 2, ALIEN_SIZE[1] // 2), ALIEN_SIZE[0] // 3)
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建默认子弹图像(红色激光)
|
|
|
|
|
def create_default_bullet():
|
|
|
|
|
image = pygame.Surface(BULLET_SIZE, pygame.SRCALPHA)
|
|
|
|
|
# 激光束
|
|
|
|
|
pygame.draw.line(image, (255, 0, 0), (BULLET_SIZE[0] // 2, 0), (BULLET_SIZE[0] // 2, BULLET_SIZE[1]), 3)
|
|
|
|
|
# 激光光晕
|
|
|
|
|
pygame.draw.circle(image, (255, 100, 100, 128), (BULLET_SIZE[0] // 2, BULLET_SIZE[1] // 2), BULLET_SIZE[0] // 2)
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 加载图片并处理可能的错误
|
|
|
|
|
def load_image(filename, size, default_creator=None):
|
|
|
|
|
"""加载并缩放图片,处理可能的错误"""
|
|
|
|
|
try:
|
|
|
|
|
path = os.path.join(BASE_DIR, filename)
|
|
|
|
|
if os.path.exists(path):
|
|
|
|
|
image = pygame.image.load(path).convert_alpha() # 使用convert_alpha()支持透明度
|
|
|
|
|
return pygame.transform.scale(image, size)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"无法加载图片 {filename}: {e}")
|
|
|
|
|
|
|
|
|
|
# 如果无法加载图片,使用默认创建器或创建彩色占位符
|
|
|
|
|
if default_creator:
|
|
|
|
|
return default_creator()
|
|
|
|
|
else:
|
|
|
|
|
return create_default_image(size, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 加载游戏图片
|
|
|
|
|
player_img = load_image('player.png', PLAYER_SIZE, create_default_player)
|
|
|
|
|
alien_img = load_image('alien.png', ALIEN_SIZE, create_default_alien)
|
|
|
|
|
bullet_img = load_image('bullet.png', BULLET_SIZE, create_default_bullet)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 玩家类
|
|
|
|
|
class Player(pygame.sprite.Sprite):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"""初始化玩家对象"""
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.image = player_img
|
|
|
|
|
self.rect = self.image.get_rect()
|
|
|
|
|
self.rect.centerx = SCREEN_WIDTH // 2
|
|
|
|
|
self.rect.bottom = SCREEN_HEIGHT - 10
|
|
|
|
|
self.speed = PLAYER_SPEED
|
|
|
|
|
self.last_shot = pygame.time.get_ticks()
|
|
|
|
|
self.shoot_delay = 250
|
|
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
|
"""更新玩家位置"""
|
|
|
|
|
keys = pygame.key.get_pressed()
|
|
|
|
|
if keys[pygame.K_LEFT] and self.rect.left > 0:
|
|
|
|
|
self.rect.x -= self.speed
|
|
|
|
|
if keys[pygame.K_RIGHT] and self.rect.right < SCREEN_WIDTH:
|
|
|
|
|
self.rect.x += self.speed
|
|
|
|
|
|
|
|
|
|
# 自动射击功能
|
|
|
|
|
now = pygame.time.get_ticks()
|
|
|
|
|
if keys[pygame.K_SPACE] and now - self.last_shot > self.shoot_delay:
|
|
|
|
|
self.shoot()
|
|
|
|
|
self.last_shot = now
|
|
|
|
|
|
|
|
|
|
def shoot(self):
|
|
|
|
|
"""创建一个新子弹"""
|
|
|
|
|
bullet = Bullet(self.rect.centerx, self.rect.top)
|
|
|
|
|
all_sprites.add(bullet)
|
|
|
|
|
bullets.add(bullet)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 外星人类
|
|
|
|
|
class Alien(pygame.sprite.Sprite):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"""初始化外星人对象"""
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.image = alien_img
|
|
|
|
|
self.rect = self.image.get_rect()
|
|
|
|
|
self.reset_position()
|
|
|
|
|
|
|
|
|
|
def reset_position(self):
|
|
|
|
|
"""重置外星人位置和速度"""
|
|
|
|
|
self.rect.x = random.randint(0, SCREEN_WIDTH - self.rect.width)
|
|
|
|
|
self.rect.y = random.randint(-150, -40)
|
|
|
|
|
self.speed = random.randint(2, 5)
|
|
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
|
"""更新外星人位置"""
|
|
|
|
|
self.rect.y += self.speed
|
|
|
|
|
if self.rect.top > SCREEN_HEIGHT:
|
|
|
|
|
self.reset_position()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 子弹类
|
|
|
|
|
class Bullet(pygame.sprite.Sprite):
|
|
|
|
|
def __init__(self, x, y):
|
|
|
|
|
"""初始化子弹对象"""
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.image = bullet_img
|
|
|
|
|
self.rect = self.image.get_rect()
|
|
|
|
|
self.rect.centerx = x
|
|
|
|
|
self.rect.bottom = y
|
|
|
|
|
self.speed = BULLET_SPEED
|
|
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
|
"""更新子弹位置"""
|
|
|
|
|
self.rect.y -= self.speed
|
|
|
|
|
if self.rect.bottom < 0:
|
|
|
|
|
self.kill()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 爆炸效果类
|
|
|
|
|
class Explosion(pygame.sprite.Sprite):
|
|
|
|
|
def __init__(self, center, size):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.size = size
|
|
|
|
|
self.frame = 0
|
|
|
|
|
self.image = self.create_explosion_frame(self.frame, self.size)
|
|
|
|
|
self.rect = self.image.get_rect()
|
|
|
|
|
self.rect.center = center
|
|
|
|
|
self.last_update = pygame.time.get_ticks()
|
|
|
|
|
self.frame_rate = 50 # 爆炸动画帧率
|
|
|
|
|
|
|
|
|
|
def create_explosion_frame(self, frame, size):
|
|
|
|
|
"""创建爆炸动画的一帧"""
|
|
|
|
|
# 根据帧数创建不同大小的爆炸圆圈
|
|
|
|
|
image = pygame.Surface((size, size), pygame.SRCALPHA)
|
|
|
|
|
max_radius = size // 2
|
|
|
|
|
if frame < 5: # 爆炸扩大阶段
|
|
|
|
|
radius = max_radius * (frame + 1) // 5
|
|
|
|
|
alpha = 255
|
|
|
|
|
else: # 爆炸消失阶段
|
|
|
|
|
radius = max_radius
|
|
|
|
|
alpha = 255 * (10 - frame) // 5
|
|
|
|
|
|
|
|
|
|
# 绘制爆炸圆圈
|
|
|
|
|
pygame.draw.circle(image, (255, 200, 50, alpha), (size // 2, size // 2), radius)
|
|
|
|
|
pygame.draw.circle(image, (255, 120, 0, alpha), (size // 2, size // 2), radius * 3 // 4)
|
|
|
|
|
pygame.draw.circle(image, (255, 255, 255, alpha), (size // 2, size // 2), radius // 2)
|
|
|
|
|
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
|
now = pygame.time.get_ticks()
|
|
|
|
|
if now - self.last_update > self.frame_rate:
|
|
|
|
|
self.last_update = now
|
|
|
|
|
self.frame += 1
|
|
|
|
|
if self.frame < 10: # 总共10帧动画
|
|
|
|
|
self.image = self.create_explosion_frame(self.frame, self.size)
|
|
|
|
|
else:
|
|
|
|
|
self.kill() # 动画结束后删除精灵
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 游戏状态类
|
|
|
|
|
class GameState:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"""初始化游戏状态"""
|
|
|
|
|
self.score = 0
|
|
|
|
|
self.game_over = False
|
|
|
|
|
self.reset_game()
|
|
|
|
|
|
|
|
|
|
def reset_game(self):
|
|
|
|
|
"""重置游戏状态和精灵组"""
|
|
|
|
|
global all_sprites, aliens, bullets, player, explosions
|
|
|
|
|
|
|
|
|
|
# 创建精灵组
|
|
|
|
|
all_sprites = pygame.sprite.Group()
|
|
|
|
|
aliens = pygame.sprite.Group()
|
|
|
|
|
bullets = pygame.sprite.Group()
|
|
|
|
|
explosions = pygame.sprite.Group()
|
|
|
|
|
|
|
|
|
|
# 创建玩家实例并添加到精灵组
|
|
|
|
|
player = Player()
|
|
|
|
|
all_sprites.add(player)
|
|
|
|
|
|
|
|
|
|
# 创建多个外星人实例并添加到精灵组
|
|
|
|
|
for i in range(ALIEN_COUNT):
|
|
|
|
|
alien = Alien()
|
|
|
|
|
all_sprites.add(alien)
|
|
|
|
|
aliens.add(alien)
|
|
|
|
|
|
|
|
|
|
# 重置游戏状态
|
|
|
|
|
self.score = 0
|
|
|
|
|
self.game_over = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建游戏状态实例
|
|
|
|
|
game_state = GameState()
|
|
|
|
|
|
|
|
|
|
# 预渲染文本
|
|
|
|
|
try:
|
|
|
|
|
game_over_text = chinese_font_large.render("游戏结束", True, SCORE_COLOR)
|
|
|
|
|
restart_text = chinese_font_medium.render("按 R 键重新开始", True, SCORE_COLOR)
|
|
|
|
|
score_label = chinese_font_medium.render("得分: ", True, SCORE_COLOR)
|
|
|
|
|
except:
|
|
|
|
|
game_over_text = pygame.font.Font(None, 48).render("GAME OVER", True, SCORE_COLOR)
|
|
|
|
|
restart_text = pygame.font.Font(None, 36).render("Press R to Restart", True, SCORE_COLOR)
|
|
|
|
|
score_label = pygame.font.Font(None, 36).render("Score: ", True, SCORE_COLOR)
|
|
|
|
|
|
|
|
|
|
# 游戏主循环
|
|
|
|
|
clock = pygame.time.Clock()
|
|
|
|
|
running = True
|
|
|
|
|
|
|
|
|
|
while running:
|
|
|
|
|
# 处理事件
|
|
|
|
|
for event in pygame.event.get():
|
|
|
|
|
if event.type == pygame.QUIT:
|
|
|
|
|
running = False
|
|
|
|
|
elif event.type == pygame.KEYDOWN:
|
|
|
|
|
if event.key == pygame.K_ESCAPE:
|
|
|
|
|
running = False
|
|
|
|
|
elif event.key == pygame.K_r and game_state.game_over:
|
|
|
|
|
game_state.reset_game()
|
|
|
|
|
elif event.key == pygame.K_SPACE and not game_state.game_over:
|
|
|
|
|
player.shoot()
|
|
|
|
|
|
|
|
|
|
# 更新游戏状态
|
|
|
|
|
if not game_state.game_over:
|
|
|
|
|
# 更新所有精灵
|
|
|
|
|
all_sprites.update()
|
|
|
|
|
|
|
|
|
|
# 检查子弹和外星人的碰撞
|
|
|
|
|
hits = pygame.sprite.groupcollide(bullets, aliens, True, True)
|
|
|
|
|
for hit in hits:
|
|
|
|
|
game_state.score += 10
|
|
|
|
|
# 创建爆炸效果
|
|
|
|
|
explosion = Explosion(hit.rect.center, 50)
|
|
|
|
|
all_sprites.add(explosion)
|
|
|
|
|
explosions.add(explosion)
|
|
|
|
|
# 创建新的外星人
|
|
|
|
|
alien = Alien()
|
|
|
|
|
all_sprites.add(alien)
|
|
|
|
|
aliens.add(alien)
|
|
|
|
|
|
|
|
|
|
# 检查玩家和外星人的碰撞
|
|
|
|
|
hits = pygame.sprite.spritecollide(player, aliens, True)
|
|
|
|
|
if hits:
|
|
|
|
|
# 创建大爆炸效果
|
|
|
|
|
explosion = Explosion(player.rect.center, 100)
|
|
|
|
|
all_sprites.add(explosion)
|
|
|
|
|
explosions.add(explosion)
|
|
|
|
|
game_state.game_over = True
|
|
|
|
|
|
|
|
|
|
# 绘制
|
|
|
|
|
screen.fill(BACKGROUND_COLOR)
|
|
|
|
|
all_sprites.draw(screen)
|
|
|
|
|
|
|
|
|
|
# 显示得分
|
|
|
|
|
screen.blit(score_label, (10, 10))
|
|
|
|
|
try:
|
|
|
|
|
score_value = chinese_font_medium.render(str(game_state.score), True, SCORE_COLOR)
|
|
|
|
|
except:
|
|
|
|
|
score_value = pygame.font.Font(None, 36).render(str(game_state.score), True, SCORE_COLOR)
|
|
|
|
|
screen.blit(score_value, (10 + score_label.get_width(), 10))
|
|
|
|
|
|
|
|
|
|
# 游戏结束逻辑
|
|
|
|
|
if game_state.game_over:
|
|
|
|
|
# 绘制半透明覆盖层
|
|
|
|
|
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
|
|
|
|
|
overlay.fill((0, 0, 0, 128))
|
|
|
|
|
screen.blit(overlay, (0, 0))
|
|
|
|
|
|
|
|
|
|
# 显示游戏结束文本
|
|
|
|
|
screen.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2,
|
|
|
|
|
SCREEN_HEIGHT // 2 - game_over_text.get_height() // 2))
|
|
|
|
|
|
|
|
|
|
# 显示重新开始提示
|
|
|
|
|
screen.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2,
|
|
|
|
|
SCREEN_HEIGHT // 2 + game_over_text.get_height()))
|
|
|
|
|
|
|
|
|
|
# 更新屏幕
|
|
|
|
|
pygame.display.flip()
|
|
|
|
|
|
|
|
|
|
# 控制帧率
|
|
|
|
|
clock.tick(FPS)
|
|
|
|
|
|
|
|
|
|
# 退出Pygame
|
|
|
|
|
pygame.quit()
|
|
|
|
|
sys.exit()
|