From 62fdef71a988891a02e9443f841bb58ad6f2aab2 Mon Sep 17 00:00:00 2001
From: moranshanzha <1412472761@qq.com>
Date: Sun, 2 Nov 2025 14:11:30 +0800
Subject: [PATCH 1/2] test
---
bin/main.py | 75 +++++-
score-system/pom.xml | 64 +++++
.../scoresystem/ScoreSystemApplication.java | 12 +
.../controller/ScoreController.java | 111 +++++++++
.../com/example/scoresystem/entity/Score.java | 34 +++
.../repository/ScoreRepository.java | 24 ++
.../scoresystem/service/ScoreService.java | 69 ++++++
.../src/main/resources/application.properties | 22 ++
src/menu.py | 221 ++++++++++++++++++
src/score.py | 116 +++++++++
10 files changed, 744 insertions(+), 4 deletions(-)
create mode 100644 score-system/pom.xml
create mode 100644 score-system/src/main/java/com/example/scoresystem/ScoreSystemApplication.java
create mode 100644 score-system/src/main/java/com/example/scoresystem/controller/ScoreController.java
create mode 100644 score-system/src/main/java/com/example/scoresystem/entity/Score.java
create mode 100644 score-system/src/main/java/com/example/scoresystem/repository/ScoreRepository.java
create mode 100644 score-system/src/main/java/com/example/scoresystem/service/ScoreService.java
create mode 100644 score-system/src/main/resources/application.properties
create mode 100644 src/menu.py
create mode 100644 src/score.py
diff --git a/bin/main.py b/bin/main.py
index 6623a0b..8a0bab4 100644
--- a/bin/main.py
+++ b/bin/main.py
@@ -2,12 +2,21 @@
# -*- coding: utf-8 -*-
import sys
+import time
+import os
+
from pygame.locals import *
+# 添加项目根目录到sys.path
+project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+sys.path.insert(0, project_root)
+
from config.settings import *
from src.plane import OurPlane # 导入我们的飞机
from src.enemy import SmallEnemy
from src.bullet import Bullet
+from src.score import ScoreSystem
+from src.menu import StartMenu, ScoreHistoryScreen
bg_size = 480, 852 # 初始化游戏背景大小(宽, 高)
@@ -23,6 +32,9 @@
color_red = (255, 0, 0)
color_white = (255, 255, 255)
+# 初始化得分系统
+score_system = ScoreSystem()
+
# 获取我方飞机
our_plane = OurPlane(bg_size)
@@ -41,12 +53,14 @@ def add_small_enemies(group1, group2, num):
group2.add(small_enemy)
-def main():
+def game_loop():
+ """游戏主循环"""
# 响应音乐
pygame.mixer.music.play(-1) # loops 接收该参数, -1 表示无限循环(默认循环播放一次)
running = True
switch_image = False # 切换飞机的标识位(使飞机具有喷气式效果)
delay = 60 # 对一些效果进行延迟, 效果更好一些
+ start_time = time.time() # 记录游戏开始时间
enemies = pygame.sprite.Group() # 生成敌方飞机组(一种精灵组用以存储所有敌机精灵)
small_enemies = pygame.sprite.Group() # 敌方小型飞机组(不同型号敌机创建不同的精灵组来存储)
@@ -57,12 +71,17 @@ def main():
bullet_index = 0
e1_destroy_index = 0
me_destroy_index = 0
+ score_font = pygame.font.Font(None, 36) # 得分显示字体
+ high_score_font = pygame.font.Font(None, 24) # 最高分显示字体
# 定义子弹实例化个数
bullet1 = []
bullet_num = 6
for i in range(bullet_num):
bullet1.append(Bullet(our_plane.rect.midtop))
+
+ # 重置得分系统
+ score_system.reset_score()
while running:
@@ -127,6 +146,7 @@ def main():
b.active = False # 子弹损毁
for e in enemies_hit:
e.active = False # 小型敌机损毁
+ score_system.add_score("small") # 添加得分
# 毁坏状态绘制爆炸的场面
else:
@@ -135,7 +155,8 @@ def main():
me_destroy_index = (me_destroy_index + 1) % 4
if me_destroy_index == 0:
me_down_sound.play()
- our_plane.reset()
+ score_system.save_score() # 保存得分
+ return # 游戏结束,返回主菜单
# 调用 pygame 实现的碰撞方法 spritecollide (我方飞机如果和敌机碰撞, 更改飞机的存活属性)
enemies_down = pygame.sprite.spritecollide(our_plane, enemies, False, pygame.sprite.collide_mask)
@@ -147,8 +168,7 @@ def main():
# 响应用户的操作
for event in pygame.event.get():
if event.type == 12: # 如果用户按下屏幕上的关闭按钮,触发QUIT事件,程序退出
- pygame.quit()
- sys.exit()
+ return "quit"
if delay == 0:
delay = 60
@@ -165,8 +185,55 @@ def main():
if key_pressed[K_d] or key_pressed[K_RIGHT]:
our_plane.move_right()
+ # 显示得分
+ score_text = score_font.render(f"Score: {score_system.score}", True, color_white)
+ screen.blit(score_text, (10, 10))
+
+ # 显示最高分
+ high_score_text = high_score_font.render(f"High Score: {score_system.high_score}", True, color_white)
+ screen.blit(high_score_text, (10, 50))
+
# 绘制图像并输出到屏幕上面
pygame.display.flip()
+ return "quit"
+
+def main():
+ """游戏主入口"""
+ running = True
+ current_screen = "start_menu"
+
+ # 创建开始菜单和得分历史屏幕
+ start_menu = StartMenu(screen, score_system)
+ score_history = ScoreHistoryScreen(screen, score_system)
+
+ while running:
+ if current_screen == "start_menu":
+ result = start_menu.run()
+ if result == "start_game":
+ # 重置飞机状态
+ our_plane.reset()
+ # 开始游戏循环
+ game_result = game_loop()
+ if game_result == "quit":
+ running = False
+ else:
+ current_screen = "start_menu"
+ elif result == "show_history":
+ current_screen = "score_history"
+ elif result == "quit":
+ running = False
+ elif current_screen == "score_history":
+ result = score_history.run()
+ if result == "back":
+ current_screen = "start_menu"
+ elif result == "quit":
+ running = False
+
+ # 清理资源
+ score_system.save_score()
+ score_system.close()
+ pygame.quit()
+ sys.exit()
diff --git a/score-system/pom.xml b/score-system/pom.xml
new file mode 100644
index 0000000..368486c
--- /dev/null
+++ b/score-system/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.0
+
+
+
+ com.example
+ score-system
+ 1.0.0
+ score-system
+ Score System for Game
+
+
+ 17
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/score-system/src/main/java/com/example/scoresystem/ScoreSystemApplication.java b/score-system/src/main/java/com/example/scoresystem/ScoreSystemApplication.java
new file mode 100644
index 0000000..56dabe1
--- /dev/null
+++ b/score-system/src/main/java/com/example/scoresystem/ScoreSystemApplication.java
@@ -0,0 +1,12 @@
+package com.example.scoresystem;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ScoreSystemApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ScoreSystemApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/score-system/src/main/java/com/example/scoresystem/controller/ScoreController.java b/score-system/src/main/java/com/example/scoresystem/controller/ScoreController.java
new file mode 100644
index 0000000..d3d9325
--- /dev/null
+++ b/score-system/src/main/java/com/example/scoresystem/controller/ScoreController.java
@@ -0,0 +1,111 @@
+package com.example.scoresystem.controller;
+
+import com.example.scoresystem.entity.Score;
+import com.example.scoresystem.service.ScoreService;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/api/scores")
+public class ScoreController {
+
+ @Autowired
+ private ScoreService scoreService;
+
+ /**
+ * 创建得分记录
+ * @param score 得分记录
+ * @return 创建后的得分记录
+ */
+ @PostMapping
+ public ResponseEntity createScore(@Valid @RequestBody Score score) {
+ Score savedScore = scoreService.saveScore(score);
+ return new ResponseEntity<>(savedScore, HttpStatus.CREATED);
+ }
+
+ /**
+ * 根据ID获取得分记录
+ * @param id 得分记录ID
+ * @return 得分记录
+ */
+ @GetMapping("/{id}")
+ public ResponseEntity getScoreById(@PathVariable Long id) {
+ Optional score = scoreService.getScoreById(id);
+ return score.map(ResponseEntity::ok)
+ .orElseGet(() -> ResponseEntity.notFound().build());
+ }
+
+ /**
+ * 获取所有得分记录
+ * @return 得分记录列表
+ */
+ @GetMapping
+ public ResponseEntity> getAllScores() {
+ List scores = scoreService.getAllScores();
+ return ResponseEntity.ok(scores);
+ }
+
+ /**
+ * 获取最高分
+ * @return 最高分记录
+ */
+ @GetMapping("/high")
+ public ResponseEntity getHighScore() {
+ Optional highScore = scoreService.getHighScore();
+ return highScore.map(ResponseEntity::ok)
+ .orElseGet(() -> ResponseEntity.notFound().build());
+ }
+
+ /**
+ * 获取前N名的得分记录
+ * @param limit 限制数量
+ * @return 前N名的得分记录列表
+ */
+ @GetMapping("/top")
+ public ResponseEntity> getTopScores(@RequestParam(defaultValue = "10") int limit) {
+ Iterable topScores = scoreService.getTopScores(limit);
+ return ResponseEntity.ok(topScores);
+ }
+
+ /**
+ * 更新得分记录
+ * @param id 得分记录ID
+ * @param score 得分记录
+ * @return 更新后的得分记录
+ */
+ @PutMapping("/{id}")
+ public ResponseEntity updateScore(@PathVariable Long id, @Valid @RequestBody Score score) {
+ Optional existingScore = scoreService.getScoreById(id);
+
+ if (existingScore.isPresent()) {
+ score.setId(id);
+ Score updatedScore = scoreService.saveScore(score);
+ return ResponseEntity.ok(updatedScore);
+ } else {
+ return ResponseEntity.notFound().build();
+ }
+ }
+
+ /**
+ * 删除得分记录
+ * @param id 得分记录ID
+ * @return 响应状态
+ */
+ @DeleteMapping("/{id}")
+ public ResponseEntity deleteScore(@PathVariable Long id) {
+ Optional score = scoreService.getScoreById(id);
+
+ if (score.isPresent()) {
+ scoreService.deleteScore(id);
+ return ResponseEntity.noContent().build();
+ } else {
+ return ResponseEntity.notFound().build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/score-system/src/main/java/com/example/scoresystem/entity/Score.java b/score-system/src/main/java/com/example/scoresystem/entity/Score.java
new file mode 100644
index 0000000..848ccea
--- /dev/null
+++ b/score-system/src/main/java/com/example/scoresystem/entity/Score.java
@@ -0,0 +1,34 @@
+package com.example.scoresystem.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "score_records")
+public class Score {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank(message = "玩家名称不能为空")
+ private String playerName;
+
+ @Positive(message = "得分必须为正数")
+ private int score;
+
+ private LocalDateTime gameTime;
+
+ @PrePersist
+ protected void onCreate() {
+ gameTime = LocalDateTime.now();
+ }
+}
\ No newline at end of file
diff --git a/score-system/src/main/java/com/example/scoresystem/repository/ScoreRepository.java b/score-system/src/main/java/com/example/scoresystem/repository/ScoreRepository.java
new file mode 100644
index 0000000..9cf1baa
--- /dev/null
+++ b/score-system/src/main/java/com/example/scoresystem/repository/ScoreRepository.java
@@ -0,0 +1,24 @@
+package com.example.scoresystem.repository;
+
+import com.example.scoresystem.entity.Score;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ScoreRepository extends JpaRepository {
+
+ /**
+ * 查询最高分
+ * @return 最高分记录
+ */
+ Score findTopByOrderByScoreDesc();
+
+ /**
+ * 查询前N名的得分记录
+ * @param limit 限制数量
+ * @return 前N名的得分记录列表
+ */
+ @Query("SELECT s FROM Score s ORDER BY s.score DESC LIMIT ?1")
+ Iterable findTopScores(int limit);
+}
\ No newline at end of file
diff --git a/score-system/src/main/java/com/example/scoresystem/service/ScoreService.java b/score-system/src/main/java/com/example/scoresystem/service/ScoreService.java
new file mode 100644
index 0000000..cdbcd29
--- /dev/null
+++ b/score-system/src/main/java/com/example/scoresystem/service/ScoreService.java
@@ -0,0 +1,69 @@
+package com.example.scoresystem.service;
+
+import com.example.scoresystem.entity.Score;
+import com.example.scoresystem.repository.ScoreRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Transactional
+public class ScoreService {
+
+ @Autowired
+ private ScoreRepository scoreRepository;
+
+ /**
+ * 保存得分记录
+ * @param score 得分记录
+ * @return 保存后的得分记录
+ */
+ public Score saveScore(Score score) {
+ return scoreRepository.save(score);
+ }
+
+ /**
+ * 根据ID获取得分记录
+ * @param id 得分记录ID
+ * @return 得分记录
+ */
+ public Optional getScoreById(Long id) {
+ return scoreRepository.findById(id);
+ }
+
+ /**
+ * 获取所有得分记录
+ * @return 得分记录列表
+ */
+ public List getAllScores() {
+ return scoreRepository.findAll();
+ }
+
+ /**
+ * 获取最高分
+ * @return 最高分记录
+ */
+ public Optional getHighScore() {
+ return Optional.ofNullable(scoreRepository.findTopByOrderByScoreDesc());
+ }
+
+ /**
+ * 获取前N名的得分记录
+ * @param limit 限制数量
+ * @return 前N名的得分记录列表
+ */
+ public Iterable getTopScores(int limit) {
+ return scoreRepository.findTopScores(limit);
+ }
+
+ /**
+ * 删除得分记录
+ * @param id 得分记录ID
+ */
+ public void deleteScore(Long id) {
+ scoreRepository.deleteById(id);
+ }
+}
\ No newline at end of file
diff --git a/score-system/src/main/resources/application.properties b/score-system/src/main/resources/application.properties
new file mode 100644
index 0000000..04323c8
--- /dev/null
+++ b/score-system/src/main/resources/application.properties
@@ -0,0 +1,22 @@
+# 应用配置
+spring.application.name=score-system
+
+# 服务器配置
+server.port=8080
+
+# 数据库配置
+spring.datasource.url=jdbc:mysql://localhost:3306/game_score?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
+spring.datasource.username=root
+spring.datasource.password=123456
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+
+# JPA配置
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.format_sql=true
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
+
+# CORS配置
+spring.web.cors.allowed-origins=*
+spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
+spring.web.cors.allowed-headers=*
diff --git a/src/menu.py b/src/menu.py
new file mode 100644
index 0000000..7642147
--- /dev/null
+++ b/src/menu.py
@@ -0,0 +1,221 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+游戏菜单模块
+负责处理游戏的开始界面和得分历史界面
+"""
+import pygame
+from config.settings import *
+from src.score import ScoreSystem
+
+
+class StartMenu:
+ """开始菜单类"""
+
+ def __init__(self, screen, score_system):
+ self.screen = screen
+ self.score_system = score_system
+ self.width, self.height = screen.get_size()
+
+ # 加载背景图片
+ self.background = pygame.image.load(os.path.join(BASE_DIR, "material/image/background.png"))
+
+ # 加载标题图片
+ self.title_font = pygame.font.Font(None, 72)
+ self.title_text = self.title_font.render("飞机大战", True, (255, 0, 0))
+ self.title_rect = self.title_text.get_rect(center=(self.width // 2, 150))
+
+ # 加载按钮图片
+ self.start_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_resume_nor.png"))
+ self.start_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_resume_pressed.png"))
+ self.quit_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_nor.png"))
+ self.quit_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_pressed.png"))
+ self.score_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/btn_finish.png"))
+ self.score_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/btn_finish.png")) # 暂时用同一个图片
+
+ # 设置按钮位置
+ self.start_button_rect = self.start_button_normal.get_rect(center=(self.width // 2, 350))
+ self.quit_button_rect = self.quit_button_normal.get_rect(center=(self.width // 2, 450))
+ self.score_button_rect = self.score_button_normal.get_rect(center=(self.width // 2, 550))
+
+ # 加载最高分
+ self.high_score = self.score_system.high_score
+ self.high_score_font = pygame.font.Font(None, 36)
+ self.high_score_text = self.high_score_font.render(f"历史最高分: {self.high_score}", True, (255, 255, 255))
+ self.high_score_rect = self.high_score_text.get_rect(center=(self.width // 2, 250))
+
+ # 当前选中的按钮
+ self.selected_button = None
+
+ def draw(self):
+ """绘制开始菜单"""
+ # 绘制背景
+ self.screen.blit(self.background, (0, 0))
+
+ # 绘制标题
+ self.screen.blit(self.title_text, self.title_rect)
+
+ # 绘制最高分
+ self.screen.blit(self.high_score_text, self.high_score_rect)
+
+ # 绘制按钮
+ if self.selected_button == "start":
+ self.screen.blit(self.start_button_pressed, self.start_button_rect)
+ else:
+ self.screen.blit(self.start_button_normal, self.start_button_rect)
+
+ if self.selected_button == "quit":
+ self.screen.blit(self.quit_button_pressed, self.quit_button_rect)
+ else:
+ self.screen.blit(self.quit_button_normal, self.quit_button_rect)
+
+ if self.selected_button == "score":
+ self.screen.blit(self.score_button_pressed, self.score_button_rect)
+ else:
+ self.screen.blit(self.score_button_normal, self.score_button_rect)
+
+ def handle_event(self, event):
+ """处理菜单事件"""
+ if event.type == pygame.MOUSEMOTION:
+ # 鼠标移动事件
+ if self.start_button_rect.collidepoint(event.pos):
+ self.selected_button = "start"
+ elif self.quit_button_rect.collidepoint(event.pos):
+ self.selected_button = "quit"
+ elif self.score_button_rect.collidepoint(event.pos):
+ self.selected_button = "score"
+ else:
+ self.selected_button = None
+
+ elif event.type == pygame.MOUSEBUTTONDOWN:
+ # 鼠标点击事件
+ if event.button == 1: # 左键点击
+ if self.start_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ return "start"
+ elif self.quit_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ return "quit"
+ elif self.score_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ return "score_history"
+
+ elif event.type == pygame.KEYDOWN:
+ # 键盘事件
+ if event.key == pygame.K_UP or event.key == pygame.K_w:
+ if self.selected_button == "start":
+ self.selected_button = "score"
+ elif self.selected_button == "quit":
+ self.selected_button = "start"
+ elif self.selected_button == "score":
+ self.selected_button = "quit"
+ elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
+ if self.selected_button == "start":
+ self.selected_button = "quit"
+ elif self.selected_button == "quit":
+ self.selected_button = "score"
+ elif self.selected_button == "score":
+ self.selected_button = "start"
+ elif event.key == pygame.K_RETURN:
+ if self.selected_button == "start":
+ button_down_sound.play()
+ return "start"
+ elif self.selected_button == "quit":
+ button_down_sound.play()
+ return "quit"
+ elif self.selected_button == "score":
+ button_down_sound.play()
+ return "score_history"
+
+ return None
+
+
+class ScoreHistoryScreen:
+ """得分历史记录屏幕类"""
+
+ def __init__(self, screen, score_system):
+ self.screen = screen
+ self.score_system = score_system
+ self.width, self.height = screen.get_size()
+
+ # 加载背景图片
+ self.background = pygame.image.load(os.path.join(BASE_DIR, "material/image/background.png"))
+
+ # 标题
+ self.title_font = pygame.font.Font(None, 60)
+ self.title_text = self.title_font.render("得分历史", True, (255, 0, 0))
+ self.title_rect = self.title_text.get_rect(center=(self.width // 2, 100))
+
+ # 返回按钮
+ self.back_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_nor.png"))
+ self.back_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_pressed.png"))
+ self.back_button_rect = self.back_button_normal.get_rect(topleft=(20, 20))
+
+ # 得分列表
+ self.scores = self.score_system.get_top_scores(10)
+ self.score_font = pygame.font.Font(None, 36)
+
+ # 当前选中的按钮
+ self.selected_button = None
+
+ def update_scores(self):
+ """更新得分列表"""
+ self.scores = self.score_system.get_top_scores(10)
+
+ def draw(self):
+ """绘制得分历史记录屏幕"""
+ # 绘制背景
+ self.screen.blit(self.background, (0, 0))
+
+ # 绘制标题
+ self.screen.blit(self.title_text, self.title_rect)
+
+ # 绘制返回按钮
+ if self.selected_button == "back":
+ self.screen.blit(self.back_button_pressed, self.back_button_rect)
+ else:
+ self.screen.blit(self.back_button_normal, self.back_button_rect)
+
+ # 绘制得分列表
+ y_offset = 200
+ for i, score in enumerate(self.scores):
+ if i >= 10: # 最多显示10条记录
+ break
+
+ score_text = self.score_font.render(f"{i+1}. {score['playerName']}: {score['score']}", True, (255, 255, 255))
+ score_rect = score_text.get_rect(center=(self.width // 2, y_offset))
+ self.screen.blit(score_text, score_rect)
+ y_offset += 50
+
+ # 如果没有得分记录
+ if not self.scores:
+ no_scores_text = self.score_font.render("暂无得分记录", True, (255, 255, 255))
+ no_scores_rect = no_scores_text.get_rect(center=(self.width // 2, 300))
+ self.screen.blit(no_scores_text, no_scores_rect)
+
+ def handle_event(self, event):
+ """处理得分历史屏幕事件"""
+ if event.type == pygame.MOUSEMOTION:
+ # 鼠标移动事件
+ if self.back_button_rect.collidepoint(event.pos):
+ self.selected_button = "back"
+ else:
+ self.selected_button = None
+
+ elif event.type == pygame.MOUSEBUTTONDOWN:
+ # 鼠标点击事件
+ if event.button == 1: # 左键点击
+ if self.back_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ return "back"
+
+ elif event.type == pygame.KEYDOWN:
+ # 键盘事件
+ if event.key == pygame.K_ESCAPE or event.key == pygame.K_BACKSPACE:
+ button_down_sound.play()
+ return "back"
+ elif event.key == pygame.K_RETURN and self.selected_button == "back":
+ button_down_sound.play()
+ return "back"
+
+ return None
diff --git a/src/score.py b/src/score.py
new file mode 100644
index 0000000..657b46b
--- /dev/null
+++ b/src/score.py
@@ -0,0 +1,116 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+得分记录模块
+负责游戏得分的计算和记录
+"""
+import json
+import os
+import requests
+from config.settings import BASE_DIR
+
+
+class ScoreSystem:
+ """得分系统类,负责得分计算和记录"""
+
+ def __init__(self):
+ self.score = 0
+ self.high_score = self.load_high_score()
+ self.api_url = "http://localhost:8080/api/scores" # SpringBoot后端API地址
+
+ def add_score(self, enemy_type):
+ """根据敌机类型添加得分"""
+ if enemy_type == "small":
+ self.score += 10
+ elif enemy_type == "mid":
+ self.score += 50
+ elif enemy_type == "big":
+ self.score += 100
+
+ # 更新最高分
+ if self.score > self.high_score:
+ self.high_score = self.score
+
+ def reset_score(self):
+ """重置得分"""
+ self.score = 0
+
+ def save_score(self, player_name="player"):
+ """保存得分到服务器"""
+ try:
+ score_data = {
+ "playerName": player_name,
+ "score": self.score,
+ "gameTime": ""
+ }
+
+ response = requests.post(self.api_url, json=score_data)
+ if response.status_code == 201:
+ print("得分保存成功")
+ else:
+ print(f"得分保存失败: {response.status_code}")
+ # 如果服务器不可用,保存到本地
+ self.save_local_score(player_name)
+ except requests.exceptions.ConnectionError:
+ print("无法连接到服务器,保存到本地")
+ self.save_local_score(player_name)
+
+ def save_local_score(self, player_name="player"):
+ """保存得分到本地文件"""
+ score_file = os.path.join(BASE_DIR, "scores.json")
+
+ try:
+ if os.path.exists(score_file):
+ with open(score_file, "r") as f:
+ scores = json.load(f)
+ else:
+ scores = []
+
+ # 添加新得分
+ scores.append({
+ "playerName": player_name,
+ "score": self.score,
+ "gameTime": ""
+ })
+
+ # 按得分降序排序
+ scores.sort(key=lambda x: x["score"], reverse=True)
+
+ with open(score_file, "w") as f:
+ json.dump(scores, f, indent=4)
+
+ except Exception as e:
+ print(f"保存本地得分失败: {e}")
+
+ def load_high_score(self):
+ """加载本地最高分"""
+ score_file = os.path.join(BASE_DIR, "scores.json")
+
+ try:
+ if os.path.exists(score_file):
+ with open(score_file, "r") as f:
+ scores = json.load(f)
+ if scores:
+ return scores[0]["score"]
+ except Exception as e:
+ print(f"加载最高分失败: {e}")
+
+ return 0
+
+ def get_top_scores(self, count=10):
+ """获取本地排行榜前N名"""
+ score_file = os.path.join(BASE_DIR, "scores.json")
+
+ try:
+ if os.path.exists(score_file):
+ with open(score_file, "r") as f:
+ scores = json.load(f)
+ return scores[:count]
+ except Exception as e:
+ print(f"加载排行榜失败: {e}")
+
+ return []
+
+ def close(self):
+ """关闭得分系统"""
+ pass
From 1f16b2a43f44ed287ec56a789de12e55fa145bee Mon Sep 17 00:00:00 2001
From: moranshanzha <1412472761@qq.com>
Date: Sun, 2 Nov 2025 15:05:25 +0800
Subject: [PATCH 2/2] test
---
as | 0
bin/main.py | 120 +++++++-
src/plane.py | 33 ++
src/score.py | 11 +-
src/systems.py | 816 +++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 966 insertions(+), 14 deletions(-)
create mode 100644 as
create mode 100644 src/systems.py
diff --git a/as b/as
new file mode 100644
index 0000000..e69de29
diff --git a/bin/main.py b/bin/main.py
index 8a0bab4..50e9290 100644
--- a/bin/main.py
+++ b/bin/main.py
@@ -17,6 +17,7 @@
from src.bullet import Bullet
from src.score import ScoreSystem
from src.menu import StartMenu, ScoreHistoryScreen
+from src.systems import KillEffectSystem, SkinSystem, WeaponSystem, ItemSystem, EffectSystem
bg_size = 480, 852 # 初始化游戏背景大小(宽, 高)
@@ -38,6 +39,16 @@
# 获取我方飞机
our_plane = OurPlane(bg_size)
+# 初始化新系统
+kill_effect_system = KillEffectSystem(screen)
+skin_system = SkinSystem(screen, our_plane)
+weapon_system = WeaponSystem(our_plane)
+item_system = ItemSystem(screen, our_plane, weapon_system, score_system)
+effect_system = EffectSystem(screen)
+
+# 加载保存的皮肤选择
+skin_system.load_skin_selection()
+
def add_small_enemies(group1, group2, num):
"""
@@ -82,6 +93,17 @@ def game_loop():
# 重置得分系统
score_system.reset_score()
+ score_system.score_multiplier = 1
+
+ # 重置飞机状态
+ our_plane.reset()
+
+ # 重置道具系统
+ item_system.bomb_count = 3
+
+ # 重置武器系统
+ weapon_system.current_weapon = 'normal'
+ weapon_system.weapon_effects = {}
while running:
@@ -91,6 +113,13 @@ def game_loop():
# 微信的飞机貌似是喷气式的, 那么这个就涉及到一个帧数的问题
clock = pygame.time.Clock()
clock.tick(60)
+
+ # 更新各种系统
+ our_plane.update()
+ kill_effect_system.update()
+ weapon_system.update()
+ item_system.update(enemies)
+ effect_system.update()
# 绘制我方飞机的两种不同的形式
if not delay % 3:
@@ -125,17 +154,44 @@ def game_loop():
# 当我方飞机存活状态, 正常展示
if our_plane.active:
- if switch_image:
- screen.blit(our_plane.image_one, our_plane.rect)
- else:
- screen.blit(our_plane.image_two, our_plane.rect)
+ # 绘制飞机(考虑无敌状态的闪烁效果)
+ if not our_plane.invincible or delay % 5 < 3:
+ if switch_image:
+ screen.blit(our_plane.image_one, our_plane.rect)
+ else:
+ screen.blit(our_plane.image_two, our_plane.rect)
# 飞机存活的状态下才可以发射子弹
- if not (delay % 10): # 每十帧发射一颗移动的子弹
+ current_weapon = weapon_system.get_current_weapon()
+ if not (delay % current_weapon['fire_rate']): # 根据当前武器的射速发射子弹
bullet_sound.play()
bullets = bullet1
- bullets[bullet_index].reset(our_plane.rect.midtop)
- bullet_index = (bullet_index + 1) % bullet_num
+
+ # 根据武器类型发射不同数量的子弹
+ if current_weapon['name'] == '普通子弹':
+ bullets[bullet_index].reset(our_plane.rect.midtop)
+ bullet_index = (bullet_index + 1) % bullet_num
+ elif current_weapon['name'] == '双发子弹':
+ # 发射两颗子弹,左右分开
+ bullets[bullet_index].reset((our_plane.rect.midtop[0] - 20, our_plane.rect.midtop[1]))
+ bullet_index = (bullet_index + 1) % bullet_num
+ bullets[bullet_index].reset((our_plane.rect.midtop[0] + 20, our_plane.rect.midtop[1]))
+ bullet_index = (bullet_index + 1) % bullet_num
+ elif current_weapon['name'] == '激光':
+ # 发射激光
+ effect_system.add_laser_effect(
+ our_plane.rect.midtop,
+ (our_plane.rect.midtop[0], 0),
+ color=(0, 255, 255),
+ duration=5
+ )
+
+ # 检测激光是否击中敌机
+ for enemy in enemies:
+ if enemy.active and enemy.rect.collidepoint((our_plane.rect.midtop[0], enemy.rect.top)):
+ enemy.active = False
+ score_system.add_score("small")
+ kill_effect_system.add_kill_effect(enemy.rect.center, "small")
for b in bullets:
if b.active: # 只有激活的子弹才可能击中敌机
@@ -147,6 +203,7 @@ def game_loop():
for e in enemies_hit:
e.active = False # 小型敌机损毁
score_system.add_score("small") # 添加得分
+ kill_effect_system.add_kill_effect(e.rect.center, "small")
# 毁坏状态绘制爆炸的场面
else:
@@ -161,14 +218,30 @@ def game_loop():
# 调用 pygame 实现的碰撞方法 spritecollide (我方飞机如果和敌机碰撞, 更改飞机的存活属性)
enemies_down = pygame.sprite.spritecollide(our_plane, enemies, False, pygame.sprite.collide_mask)
if enemies_down:
- our_plane.active = False
- for row in enemies:
- row.active = False
+ for enemy in enemies_down:
+ if enemy.active:
+ enemy.active = False
+ # 飞机受到伤害
+ if not our_plane.take_damage():
+ # 飞机被摧毁
+ for row in enemies:
+ row.active = False
# 响应用户的操作
for event in pygame.event.get():
if event.type == 12: # 如果用户按下屏幕上的关闭按钮,触发QUIT事件,程序退出
return "quit"
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_t:
+ # 切换皮肤菜单
+ skin_system.toggle_skin_menu()
+ elif event.key == pygame.K_SPACE:
+ # 使用炸弹
+ item_system.use_bomb(enemies)
+
+ # 处理皮肤菜单事件
+ if skin_system.skin_menu_active:
+ skin_system.handle_event(event)
if delay == 0:
delay = 60
@@ -192,7 +265,32 @@ def game_loop():
# 显示最高分
high_score_text = high_score_font.render(f"High Score: {score_system.high_score}", True, color_white)
screen.blit(high_score_text, (10, 50))
-
+
+ # 显示当前武器
+ current_weapon = weapon_system.get_current_weapon()
+ weapon_text = high_score_font.render(f"Weapon: {current_weapon['name']}", True, color_white)
+ screen.blit(weapon_text, (10, 90))
+
+ # 显示生命值
+ health_bar_width = 100
+ health_bar_height = 10
+ health_ratio = our_plane.health / our_plane.max_health
+ pygame.draw.rect(screen, color_black, (10, 130, health_bar_width, health_bar_height))
+ pygame.draw.rect(screen, color_red if health_ratio < 0.3 else color_green,
+ (10, 130, health_bar_width * health_ratio, health_bar_height))
+ health_text = high_score_font.render(f"Health: {our_plane.health}/{our_plane.max_health}", True, color_white)
+ screen.blit(health_text, (10, 150))
+
+ # 绘制各种效果
+ kill_effect_system.draw()
+ effect_system.draw()
+
+ # 绘制道具
+ item_system.draw()
+
+ # 绘制皮肤菜单
+ skin_system.draw()
+
# 绘制图像并输出到屏幕上面
pygame.display.flip()
return "quit"
diff --git a/src/plane.py b/src/plane.py
index 2670884..ba54316 100644
--- a/src/plane.py
+++ b/src/plane.py
@@ -44,6 +44,12 @@ def __init__(self, bg_size):
pygame.image.load(os.path.join(BASE_DIR, "material/image/hero_blowup_n4.png")),
]
)
+ # 飞机生命值
+ self.max_health = 3
+ self.health = self.max_health
+ # 无敌状态
+ self.invincible = False
+ self.invincible_time = 0
def move_up(self):
"""
@@ -86,6 +92,33 @@ def reset(self):
self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60)
# 重置飞机的存活状态
self.active = True
+ # 重置生命值
+ self.health = self.max_health
+ # 设置无敌状态
+ self.invincible = True
+ self.invincible_time = 120 # 2秒无敌时间
+
+ def update(self):
+ """更新飞机状态"""
+ # 处理无敌状态
+ if self.invincible:
+ self.invincible_time -= 1
+ if self.invincible_time <= 0:
+ self.invincible = False
+
+ def take_damage(self, damage=1):
+ """处理飞机受伤"""
+ if not self.invincible and self.active:
+ self.health -= damage
+ if self.health <= 0:
+ self.active = False
+ return False
+ else:
+ # 受伤后短暂无敌
+ self.invincible = True
+ self.invincible_time = 60 # 1秒无敌时间
+ return True
+ return False
diff --git a/src/score.py b/src/score.py
index 657b46b..2b44603 100644
--- a/src/score.py
+++ b/src/score.py
@@ -17,16 +17,21 @@ def __init__(self):
self.score = 0
self.high_score = self.load_high_score()
self.api_url = "http://localhost:8080/api/scores" # SpringBoot后端API地址
+ self.score_multiplier = 1
def add_score(self, enemy_type):
"""根据敌机类型添加得分"""
+ base_score = 0
if enemy_type == "small":
- self.score += 10
+ base_score = 10
elif enemy_type == "mid":
- self.score += 50
+ base_score = 50
elif enemy_type == "big":
- self.score += 100
+ base_score = 100
+ # 应用得分加倍效果
+ self.score += base_score * self.score_multiplier
+
# 更新最高分
if self.score > self.high_score:
self.high_score = self.score
diff --git a/src/systems.py b/src/systems.py
new file mode 100644
index 0000000..4bb70e7
--- /dev/null
+++ b/src/systems.py
@@ -0,0 +1,816 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+游戏系统模块
+负责实现击杀画面系统、飞机皮肤调换界面和道具系统
+"""
+import pygame
+import random
+import os
+from config.settings import BASE_DIR, button_down_sound, get_bullet_sound, get_bomb_sound, level_up_sound, bomb_sound
+
+
+class KillEffectSystem:
+ """击杀画面系统类"""
+
+ def __init__(self, screen):
+ self.screen = screen
+ self.width, self.height = screen.get_size()
+
+ # 加载击杀特效资源
+ self.effects = {
+ 'small': [
+ pygame.image.load("material/image/enemy1_down1.png"),
+ pygame.image.load("material/image/enemy1_down2.png"),
+ pygame.image.load("material/image/enemy1_down3.png"),
+ pygame.image.load("material/image/enemy1_down4.png")
+ ],
+ 'mid': [
+ pygame.image.load("material/image/enemy2_down1.png"),
+ pygame.image.load("material/image/enemy2_down2.png"),
+ pygame.image.load("material/image/enemy2_down3.png"),
+ pygame.image.load("material/image/enemy2_down4.png")
+ ],
+ 'big': [
+ pygame.image.load("material/image/enemy3_down1.png"),
+ pygame.image.load("material/image/enemy3_down2.png"),
+ pygame.image.load("material/image/enemy3_down3.png"),
+ pygame.image.load("material/image/enemy3_down4.png"),
+ pygame.image.load("material/image/enemy3_down5.png"),
+ pygame.image.load("material/image/enemy3_down6.png")
+ ]
+ }
+
+ # 当前正在播放的特效
+ self.active_effects = []
+
+ # 击杀信息面板
+ self.font = pygame.font.Font(None, 36)
+ self.kill_messages = []
+
+ def add_kill_effect(self, position, enemy_type):
+ """添加击杀特效"""
+ if enemy_type not in self.effects:
+ enemy_type = 'small'
+
+ effect = {
+ 'position': position,
+ 'type': enemy_type,
+ 'images': self.effects[enemy_type],
+ 'current_frame': 0,
+ 'frame_delay': 0,
+ 'active': True
+ }
+
+ self.active_effects.append(effect)
+
+ # 添加击杀信息
+ message = {
+ 'text': self.get_kill_message(enemy_type),
+ 'position': (position[0], position[1] - 30),
+ 'alpha': 255,
+ 'y_speed': -2,
+ 'active': True
+ }
+
+ self.kill_messages.append(message)
+
+ def get_kill_message(self, enemy_type):
+ """获取随机击杀消息"""
+ messages = {
+ 'small': ['干得漂亮!', '完美击杀!', '太棒了!'],
+ 'mid': ['精彩绝伦!', '太厉害了!', '漂亮的一击!'],
+ 'big': ['史诗级击杀!', '太壮观了!', '难以置信!']
+ }
+
+ if enemy_type not in messages:
+ enemy_type = 'small'
+
+ return random.choice(messages[enemy_type])
+
+ def update(self):
+ """更新击杀特效和消息"""
+ # 更新特效
+ for effect in self.active_effects[:]:
+ if not effect['active']:
+ self.active_effects.remove(effect)
+ continue
+
+ effect['frame_delay'] += 1
+ if effect['frame_delay'] >= 3:
+ effect['current_frame'] += 1
+ effect['frame_delay'] = 0
+
+ if effect['current_frame'] >= len(effect['images']):
+ effect['active'] = False
+
+ # 更新消息
+ for message in self.kill_messages[:]:
+ if not message['active']:
+ self.kill_messages.remove(message)
+ continue
+
+ message['position'] = (message['position'][0], message['position'][1] + message['y_speed'])
+ message['alpha'] -= 5
+
+ if message['alpha'] <= 0:
+ message['active'] = False
+
+ def draw(self):
+ """绘制击杀特效和消息"""
+ # 绘制特效
+ for effect in self.active_effects:
+ if effect['active']:
+ image = effect['images'][effect['current_frame']]
+ rect = image.get_rect(center=effect['position'])
+ self.screen.blit(image, rect)
+
+ # 绘制消息
+ for message in self.kill_messages:
+ if message['active']:
+ text_surface = self.font.render(message['text'], True, (255, 255, 255))
+ text_surface.set_alpha(message['alpha'])
+ rect = text_surface.get_rect(center=message['position'])
+ self.screen.blit(text_surface, rect)
+
+
+class SkinSystem:
+ """飞机皮肤系统类"""
+
+ def __init__(self, screen, our_plane):
+ self.screen = screen
+ self.our_plane = our_plane
+ self.width, self.height = screen.get_size()
+
+ # 加载皮肤资源
+ # 使用现有图片作为所有皮肤的默认图片
+ hero1 = pygame.image.load(os.path.join(BASE_DIR, "material/image/hero1.png"))
+ hero2 = pygame.image.load(os.path.join(BASE_DIR, "material/image/hero2.png"))
+ destroy_images = [
+ pygame.image.load(os.path.join(BASE_DIR, "material/image/hero_blowup_n1.png")),
+ pygame.image.load(os.path.join(BASE_DIR, "material/image/hero_blowup_n2.png")),
+ pygame.image.load(os.path.join(BASE_DIR, "material/image/hero_blowup_n3.png")),
+ pygame.image.load(os.path.join(BASE_DIR, "material/image/hero_blowup_n4.png")),
+ ]
+
+ self.skins = [
+ {
+ 'name': '默认皮肤',
+ 'image_one': hero1,
+ 'image_two': hero2,
+ 'destroy_images': destroy_images,
+ 'unlocked': True,
+ 'price': 0
+ },
+ {
+ 'name': '红色皮肤',
+ 'image_one': hero1,
+ 'image_two': hero2,
+ 'destroy_images': destroy_images,
+ 'unlocked': False,
+ 'price': 1000
+ },
+ {
+ 'name': '蓝色皮肤',
+ 'image_one': hero1,
+ 'image_two': hero2,
+ 'destroy_images': destroy_images,
+ 'unlocked': False,
+ 'price': 2000
+ }
+ ]
+
+ # 当前选中的皮肤索引
+ self.current_skin_index = 0
+
+ # 皮肤选择界面状态
+ self.skin_menu_active = False
+
+ # 界面元素
+ self.title_font = pygame.font.Font(None, 60)
+ self.text_font = pygame.font.Font(None, 36)
+
+ # 按钮
+ self.back_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_nor.png"))
+ self.back_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_pause_pressed.png"))
+ self.select_button_normal = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_resume_nor.png"))
+ self.select_button_pressed = pygame.image.load(os.path.join(BASE_DIR, "material/image/game_resume_pressed.png"))
+
+ self.back_button_rect = self.back_button_normal.get_rect(topleft=(20, 20))
+ self.select_button_rect = self.select_button_normal.get_rect(bottomright=(self.width - 20, self.height - 20))
+
+ # 皮肤预览区域
+ self.preview_rect = pygame.Rect(self.width // 2 - 150, 150, 300, 300)
+
+ # 皮肤列表区域
+ self.list_rect = pygame.Rect(50, 150, 200, 500)
+
+ # 当前选中的列表项
+ self.selected_list_item = 0
+
+ def update_skin(self):
+ """更新飞机皮肤"""
+ skin = self.skins[self.current_skin_index]
+ self.our_plane.image_one = skin['image_one']
+ self.our_plane.image_two = skin['image_two']
+ self.our_plane.destroy_images = skin['destroy_images']
+ self.our_plane.mask = pygame.mask.from_surface(self.our_plane.image_one)
+
+ def toggle_skin_menu(self):
+ """切换皮肤菜单的显示状态"""
+ self.skin_menu_active = not self.skin_menu_active
+ if not self.skin_menu_active:
+ # 保存当前皮肤选择
+ self.save_skin_selection()
+
+ def save_skin_selection(self):
+ """保存皮肤选择到本地"""
+ import json
+ import os
+
+ skin_file = os.path.join(BASE_DIR, "skin_selection.json")
+
+ try:
+ with open(skin_file, "w") as f:
+ json.dump({'current_skin': self.current_skin_index}, f)
+ except Exception as e:
+ print(f"保存皮肤选择失败: {e}")
+
+ def load_skin_selection(self):
+ """从本地加载皮肤选择"""
+ import json
+ import os
+
+ skin_file = os.path.join(BASE_DIR, "skin_selection.json")
+
+ try:
+ if os.path.exists(skin_file):
+ with open(skin_file, "r") as f:
+ data = json.load(f)
+ if 'current_skin' in data:
+ self.current_skin_index = data['current_skin']
+ self.update_skin()
+ except Exception as e:
+ print(f"加载皮肤选择失败: {e}")
+
+ def handle_event(self, event):
+ """处理皮肤菜单事件"""
+ if event.type == pygame.MOUSEMOTION:
+ # 鼠标移动事件
+ if self.back_button_rect.collidepoint(event.pos):
+ self.selected_button = "back"
+ elif self.select_button_rect.collidepoint(event.pos):
+ self.selected_button = "select"
+ else:
+ # 检查皮肤列表
+ list_y = self.list_rect.y + 50
+ for i, skin in enumerate(self.skins):
+ item_rect = pygame.Rect(self.list_rect.x + 10, list_y + i * 60, self.list_rect.width - 20, 50)
+ if item_rect.collidepoint(event.pos):
+ self.selected_list_item = i
+ break
+ else:
+ self.selected_button = None
+
+ elif event.type == pygame.MOUSEBUTTONDOWN:
+ # 鼠标点击事件
+ if event.button == 1:
+ if self.back_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ self.toggle_skin_menu()
+ elif self.select_button_rect.collidepoint(event.pos):
+ button_down_sound.play()
+ self.current_skin_index = self.selected_list_item
+ self.update_skin()
+ else:
+ # 检查皮肤列表
+ list_y = self.list_rect.y + 50
+ for i, skin in enumerate(self.skins):
+ item_rect = pygame.Rect(self.list_rect.x + 10, list_y + i * 60, self.list_rect.width - 20, 50)
+ if item_rect.collidepoint(event.pos):
+ self.selected_list_item = i
+ break
+
+ elif event.type == pygame.KEYDOWN:
+ # 键盘事件
+ if event.key == pygame.K_ESCAPE or event.key == pygame.K_b:
+ self.toggle_skin_menu()
+ elif event.key == pygame.K_UP or event.key == pygame.K_w:
+ self.selected_list_item = (self.selected_list_item - 1) % len(self.skins)
+ elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
+ self.selected_list_item = (self.selected_list_item + 1) % len(self.skins)
+ elif event.key == pygame.K_RETURN:
+ self.current_skin_index = self.selected_list_item
+ self.update_skin()
+
+ def draw(self):
+ """绘制皮肤选择界面"""
+ if not self.skin_menu_active:
+ return
+
+ # 绘制半透明背景
+ overlay = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
+ overlay.fill((0, 0, 0, 180))
+ self.screen.blit(overlay, (0, 0))
+
+ # 绘制标题
+ title_text = self.title_font.render("飞机皮肤", True, (255, 0, 0))
+ title_rect = title_text.get_rect(center=(self.width // 2, 80))
+ self.screen.blit(title_text, title_rect)
+
+ # 绘制皮肤预览区域
+ pygame.draw.rect(self.screen, (255, 255, 255), self.preview_rect, 2)
+ skin = self.skins[self.selected_list_item]
+ preview_image = skin['image_one']
+ preview_image = pygame.transform.scale(preview_image, (200, 200))
+ preview_rect = preview_image.get_rect(center=self.preview_rect.center)
+ self.screen.blit(preview_image, preview_rect)
+
+ # 绘制皮肤信息
+ name_text = self.text_font.render(f"名称: {skin['name']}", True, (255, 255, 255))
+ self.screen.blit(name_text, (self.preview_rect.x, self.preview_rect.y + self.preview_rect.height + 20))
+
+ if skin['unlocked']:
+ status_text = self.text_font.render("状态: 已解锁", True, (0, 255, 0))
+ else:
+ status_text = self.text_font.render(f"状态: 未解锁 (价格: {skin['price']})", True, (255, 255, 0))
+ self.screen.blit(status_text, (self.preview_rect.x, self.preview_rect.y + self.preview_rect.height + 60))
+
+ # 绘制皮肤列表
+ pygame.draw.rect(self.screen, (255, 255, 255), self.list_rect, 2)
+ list_title = self.text_font.render("可用皮肤", True, (255, 255, 255))
+ self.screen.blit(list_title, (self.list_rect.x + 10, self.list_rect.y + 10))
+
+ list_y = self.list_rect.y + 50
+ for i, skin in enumerate(self.skins):
+ item_rect = pygame.Rect(self.list_rect.x + 10, list_y + i * 60, self.list_rect.width - 20, 50)
+
+ if i == self.selected_list_item:
+ pygame.draw.rect(self.screen, (255, 0, 0), item_rect)
+
+ if skin['unlocked']:
+ color = (255, 255, 255)
+ else:
+ color = (100, 100, 100)
+
+ skin_name = self.text_font.render(skin['name'], True, color)
+ self.screen.blit(skin_name, (item_rect.x + 10, item_rect.y + 10))
+
+ # 绘制按钮
+ if self.selected_button == "back":
+ self.screen.blit(self.back_button_pressed, self.back_button_rect)
+ else:
+ self.screen.blit(self.back_button_normal, self.back_button_rect)
+
+ if self.selected_button == "select":
+ self.screen.blit(self.select_button_pressed, self.select_button_rect)
+ else:
+ self.screen.blit(self.select_button_normal, self.select_button_rect)
+
+ # 绘制按钮文字
+ back_text = self.text_font.render("返回", True, (255, 255, 255))
+ back_text_rect = back_text.get_rect(center=self.back_button_rect.center)
+ self.screen.blit(back_text, back_text_rect)
+
+ select_text = self.text_font.render("选择", True, (255, 255, 255))
+ select_text_rect = select_text.get_rect(center=self.select_button_rect.center)
+ self.screen.blit(select_text, select_text_rect)
+
+
+class WeaponSystem:
+ """武器系统类"""
+
+ def __init__(self, our_plane):
+ self.our_plane = our_plane
+
+ # 武器类型
+ self.weapon_types = {
+ 'normal': {
+ 'name': '普通子弹',
+ 'damage': 1,
+ 'fire_rate': 10,
+ 'spread': 0,
+ 'image': pygame.image.load("material/image/bullet1.png")
+ },
+ 'double': {
+ 'name': '双发子弹',
+ 'damage': 1,
+ 'fire_rate': 10,
+ 'spread': 20,
+ 'image': pygame.image.load("material/image/bullet2.png")
+ },
+ 'laser': {
+ 'name': '激光',
+ 'damage': 3,
+ 'fire_rate': 5,
+ 'spread': 0,
+ 'image': pygame.image.load("material/image/bullet1.png")
+ }
+ }
+
+ # 当前武器
+ self.current_weapon = 'normal'
+
+ # 武器切换冷却时间
+ self.weapon_switch_cooldown = 0
+
+ # 武器效果持续时间
+ self.weapon_effects = {}
+
+ def update(self):
+ """更新武器系统"""
+ # 处理武器切换冷却
+ if self.weapon_switch_cooldown > 0:
+ self.weapon_switch_cooldown -= 1
+
+ # 处理武器效果持续时间
+ for weapon_type in list(self.weapon_effects.keys()):
+ self.weapon_effects[weapon_type] -= 1
+ if self.weapon_effects[weapon_type] <= 0:
+ del self.weapon_effects[weapon_type]
+
+ # 如果当前武器效果结束,切换回普通武器
+ if self.current_weapon == weapon_type:
+ self.current_weapon = 'normal'
+
+ def switch_weapon(self, weapon_type):
+ """切换武器"""
+ if weapon_type not in self.weapon_types:
+ return
+
+ if self.weapon_switch_cooldown == 0:
+ self.current_weapon = weapon_type
+ self.weapon_switch_cooldown = 60 # 1秒冷却时间
+
+ def add_weapon_effect(self, weapon_type, duration):
+ """添加武器效果"""
+ if weapon_type not in self.weapon_types:
+ return
+
+ if weapon_type in self.weapon_effects:
+ # 如果武器效果已存在,延长持续时间
+ self.weapon_effects[weapon_type] += duration
+ else:
+ # 否则添加新的武器效果
+ self.weapon_effects[weapon_type] = duration
+ self.current_weapon = weapon_type
+
+ def get_current_weapon(self):
+ """获取当前武器"""
+ return self.weapon_types[self.current_weapon]
+
+ def get_weapon_duration(self, weapon_type):
+ """获取武器效果剩余持续时间"""
+ return self.weapon_effects.get(weapon_type, 0)
+
+
+class ItemSystem:
+ """道具系统类"""
+
+ def __init__(self, screen, our_plane, weapon_system, score_system):
+ self.screen = screen
+ self.our_plane = our_plane
+ self.weapon_system = weapon_system
+ self.score_system = score_system
+ self.width, self.height = screen.get_size()
+
+ # 道具类型
+ self.item_types = {
+ 'double_bullet': {
+ 'name': '双发子弹',
+ 'image': pygame.image.load("material/image/powerup_double.png"),
+ 'effect': 'weapon',
+ 'effect_param': 'double',
+ 'duration': 600 # 10秒
+ },
+ 'laser': {
+ 'name': '激光',
+ 'image': pygame.image.load("material/image/powerup_laser.png"),
+ 'effect': 'weapon',
+ 'effect_param': 'laser',
+ 'duration': 300 # 5秒
+ },
+ 'bomb': {
+ 'name': '炸弹',
+ 'image': pygame.image.load("material/image/bomb.png"),
+ 'effect': 'bomb',
+ 'effect_param': 1,
+ 'duration': 0 # 立即生效
+ },
+ 'health': {
+ 'name': '生命',
+ 'image': pygame.image.load("material/image/powerup_health.png"),
+ 'effect': 'health',
+ 'effect_param': 1,
+ 'duration': 0 # 立即生效
+ },
+ 'score_multiplier': {
+ 'name': '得分加倍',
+ 'image': pygame.image.load("material/image/powerup_score.png"),
+ 'effect': 'score_multiplier',
+ 'effect_param': 2,
+ 'duration': 600 # 10秒
+ }
+ }
+
+ # 当前活动的道具
+ self.active_items = []
+
+ # 道具生成定时器
+ self.item_spawn_timer = 0
+ self.item_spawn_interval = 300 # 5秒生成一个道具
+
+ # 道具效果
+ self.active_effects = {
+ 'score_multiplier': 0,
+ 'health_boost': 0
+ }
+
+ # 炸弹数量
+ self.bomb_count = 3
+
+ def update(self, enemies):
+ """更新道具系统"""
+ # 生成道具
+ self.item_spawn_timer += 1
+ if self.item_spawn_timer >= self.item_spawn_interval:
+ self.spawn_item()
+ self.item_spawn_timer = 0
+ self.item_spawn_interval = random.randint(200, 400) # 随机生成间隔
+
+ # 更新道具位置
+ for item in self.active_items[:]:
+ item['rect'].y += item['speed']
+
+ # 检查是否超出屏幕
+ if item['rect'].y > self.height:
+ self.active_items.remove(item)
+ continue
+
+ # 检查是否与玩家飞机碰撞
+ if pygame.sprite.collide_mask(self.our_plane, item['sprite']):
+ self.collect_item(item)
+ self.active_items.remove(item)
+ continue
+
+ # 更新道具效果
+ for effect_type in list(self.active_effects.keys()):
+ if self.active_effects[effect_type] > 0:
+ self.active_effects[effect_type] -= 1
+ else:
+ # 效果结束
+ if effect_type == 'score_multiplier':
+ self.score_system.score_multiplier = 1
+
+ def draw(self):
+ """绘制道具"""
+ for item in self.active_items:
+ self.screen.blit(item['image'], item['rect'])
+
+ # 绘制炸弹数量
+ bomb_font = pygame.font.Font(None, 36)
+ bomb_text = bomb_font.render(f"炸弹: {self.bomb_count}", True, (255, 255, 255))
+ self.screen.blit(bomb_text, (self.width - 120, 10))
+
+ # 绘制道具效果指示器
+ self.draw_effect_indicators()
+
+ def spawn_item(self):
+ """生成道具"""
+ # 随机选择道具类型
+ item_type = random.choice(list(self.item_types.keys()))
+ item_data = self.item_types[item_type]
+
+ # 创建道具精灵
+ item_sprite = pygame.sprite.Sprite()
+ item_sprite.image = item_data['image']
+ item_sprite.rect = item_data['image'].get_rect()
+ item_sprite.mask = pygame.mask.from_surface(item_data['image'])
+
+ # 随机生成位置
+ x = random.randint(0, self.width - item_sprite.rect.width)
+ y = random.randint(-200, -50)
+
+ item_sprite.rect.x = x
+ item_sprite.rect.y = y
+
+ # 添加到活动道具列表
+ self.active_items.append({
+ 'type': item_type,
+ 'data': item_data,
+ 'sprite': item_sprite,
+ 'image': item_data['image'],
+ 'rect': item_sprite.rect,
+ 'speed': random.randint(2, 5)
+ })
+
+ def collect_item(self, item):
+ """收集道具"""
+ item_type = item['type']
+ item_data = item['data']
+
+ if item_data['effect'] == 'weapon':
+ self.weapon_system.add_weapon_effect(item_data['effect_param'], item_data['duration'])
+ get_bullet_sound.play()
+ elif item_data['effect'] == 'bomb':
+ self.bomb_count += item_data['effect_param']
+ get_bomb_sound.play()
+ elif item_data['effect'] == 'health':
+ # 这里假设飞机有生命值属性
+ if hasattr(self.our_plane, 'health'):
+ self.our_plane.health += item_data['effect_param']
+ if self.our_plane.health > self.our_plane.max_health:
+ self.our_plane.health = self.our_plane.max_health
+ elif item_data['effect'] == 'score_multiplier':
+ self.active_effects['score_multiplier'] = item_data['duration']
+ self.score_system.score_multiplier = item_data['effect_param']
+ level_up_sound.play()
+
+ def use_bomb(self, enemies):
+ """使用炸弹"""
+ if self.bomb_count > 0:
+ bomb_sound.play()
+ self.bomb_count -= 1
+
+ # 清除所有敌机
+ for enemy in enemies:
+ enemy.active = False
+
+ # 播放炸弹特效
+ # 这里可以添加炸弹爆炸的特效动画
+
+ def draw_effect_indicators(self):
+ """绘制道具效果指示器"""
+ x = 10
+ y = 100
+
+ # 绘制得分加倍效果
+ if self.active_effects['score_multiplier'] > 0:
+ multiplier_text = self.text_font.render(f"得分加倍: {self.active_effects['score_multiplier'] // 60}s", True, (255, 255, 0))
+ self.screen.blit(multiplier_text, (x, y))
+ y += 40
+
+ # 绘制当前武器效果
+ for weapon_type in self.weapon_system.weapon_effects:
+ duration = self.weapon_system.weapon_effects[weapon_type]
+ if duration > 0:
+ weapon_text = self.text_font.render(f"{self.weapon_system.weapon_types[weapon_type]['name']}: {duration // 60}s", True, (0, 255, 0))
+ self.screen.blit(weapon_text, (x, y))
+ y += 40
+
+ def get_score_multiplier(self):
+ """获取得分倍数"""
+ return self.active_effects.get('score_multiplier', 0) > 0 and self.score_system.score_multiplier or 1
+
+
+class EffectSystem:
+ """效果系统类,负责管理所有视觉效果"""
+
+ def __init__(self, screen):
+ self.screen = screen
+ self.width, self.height = screen.get_size()
+
+ # 爆炸效果
+ self.explosion_effects = []
+
+ # 激光效果
+ self.laser_effects = []
+
+ # 粒子效果
+ self.particle_effects = []
+
+ def add_explosion_effect(self, position, size='small'):
+ """添加爆炸效果"""
+ explosion = {
+ 'position': position,
+ 'size': size,
+ 'frames': [],
+ 'current_frame': 0,
+ 'frame_delay': 0,
+ 'active': True
+ }
+
+ # 根据爆炸大小加载不同的图像
+ if size == 'small':
+ for i in range(1, 5):
+ image = pygame.image.load(f"material/image/explosion_small_{i}.png")
+ explosion['frames'].append(image)
+ elif size == 'medium':
+ for i in range(1, 7):
+ image = pygame.image.load(f"material/image/explosion_medium_{i}.png")
+ explosion['frames'].append(image)
+ elif size == 'large':
+ for i in range(1, 9):
+ image = pygame.image.load(f"material/image/explosion_large_{i}.png")
+ explosion['frames'].append(image)
+
+ self.explosion_effects.append(explosion)
+
+ def add_laser_effect(self, start_pos, end_pos, color=(255, 0, 0), duration=5):
+ """添加激光效果"""
+ laser = {
+ 'start_pos': start_pos,
+ 'end_pos': end_pos,
+ 'color': color,
+ 'width': 3,
+ 'duration': duration,
+ 'active': True
+ }
+
+ self.laser_effects.append(laser)
+
+ def add_particle_effect(self, position, count=10, color=(255, 255, 255), size=2, speed=5):
+ """添加粒子效果"""
+ particles = []
+
+ for _ in range(count):
+ angle = random.uniform(0, 360)
+ x_speed = speed * random.cos(angle)
+ y_speed = speed * random.sin(angle)
+
+ particle = {
+ 'position': list(position),
+ 'velocity': (x_speed, y_speed),
+ 'color': color,
+ 'size': size,
+ 'life': random.randint(10, 30),
+ 'active': True
+ }
+
+ particles.append(particle)
+
+ self.particle_effects.append({
+ 'particles': particles,
+ 'active': True
+ })
+
+ def update(self):
+ """更新所有效果"""
+ # 更新爆炸效果
+ for explosion in self.explosion_effects[:]:
+ if not explosion['active']:
+ self.explosion_effects.remove(explosion)
+ continue
+
+ explosion['frame_delay'] += 1
+ if explosion['frame_delay'] >= 3:
+ explosion['current_frame'] += 1
+ explosion['frame_delay'] = 0
+
+ if explosion['current_frame'] >= len(explosion['frames']):
+ explosion['active'] = False
+
+ # 更新激光效果
+ for laser in self.laser_effects[:]:
+ if not laser['active']:
+ self.laser_effects.remove(laser)
+ continue
+
+ laser['duration'] -= 1
+ if laser['duration'] <= 0:
+ laser['active'] = False
+
+ # 更新粒子效果
+ for effect in self.particle_effects[:]:
+ if not effect['active']:
+ self.particle_effects.remove(effect)
+ continue
+
+ active_particles = []
+ for particle in effect['particles']:
+ if not particle['active']:
+ continue
+
+ particle['position'][0] += particle['velocity'][0]
+ particle['position'][1] += particle['velocity'][1]
+ particle['life'] -= 1
+
+ if particle['life'] > 0:
+ active_particles.append(particle)
+
+ if not active_particles:
+ effect['active'] = False
+ else:
+ effect['particles'] = active_particles
+
+ def draw(self):
+ """绘制所有效果"""
+ # 绘制爆炸效果
+ for explosion in self.explosion_effects:
+ if explosion['active']:
+ image = explosion['frames'][explosion['current_frame']]
+ rect = image.get_rect(center=explosion['position'])
+ self.screen.blit(image, rect)
+
+ # 绘制激光效果
+ for laser in self.laser_effects:
+ if laser['active']:
+ pygame.draw.line(self.screen, laser['color'], laser['start_pos'], laser['end_pos'], laser['width'])
+
+ # 绘制粒子效果
+ for effect in self.particle_effects:
+ for particle in effect['particles']:
+ if particle['active']:
+ pygame.draw.circle(self.screen, particle['color'],
+ (int(particle['position'][0]), int(particle['position'][1])),
+ particle['size'])