20244309 2025-2026-2 《Python程序设计》实验四报告
20244309 2025-2026-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级:2443
姓名: 黄至泓
学号:20244309
实验教师:王志强
实验日期:2026年5月18日
必修/选修: 公选课
1. 实验内容
本次综合实践要求开发一款基于Python的图形化交互游戏。具体要求如下:
(1)程序能正常运行,具备完整的游戏逻辑,功能丰富,不少于5个核心功能。
(2)使用Python标准库进行开发,本程序使用tkinter作为图形界面框架。
(3)实现完整的游戏循环,包括玩家控制、敌人AI、碰撞检测、分数系统等核心机制。
(4)编写综合实践报告,包含实验分析、设计、实现过程和结果,格式规范、逻辑清晰。
(5)对全课进行总结,撰写课程感想体会、意见和建议。
基于上述要求,我设计并实现了一款名为“星际守卫者 - 太空大战”的纵版射击游戏。游戏的主要功能包括:玩家控制飞船移动与射击、多种类型的敌人(战斗机、巡洋舰、精英、BOSS)、武器升级系统(单发→三发→散弹)、特殊武器(全屏AOE攻击,消耗能量)、波次推进与等级系统、粒子特效(爆炸、命中特效)、游戏暂停、游戏结束与重开机制。
2. 实验过程及结果
2.1 实验分析
2.1.1 项目目标
开发一款完整的太空射击游戏,玩家操控飞船抵御不断来袭的敌人,通过得分提升等级和武器性能,在波次推进后挑战强大的BOSS敌人。
2.1.2 需求分析
玩家控制模块:需要支持键盘方向键或WASD控制飞船上下左右移动,并设置边界限制防止移出屏幕。
射击系统模块:空格键射击,子弹有冷却时间限制射速,支持单发、三发、散弹三种弹道模式。
敌人系统模块:需要实现多种敌人类型,包括速度快血量低的战斗机、速度中血量中的巡洋舰、速度慢血量高的精英单位,以及拥有多阶段AI的BOSS单位。
碰撞检测模块:玩家子弹与敌人的碰撞判定,敌人子弹与玩家的碰撞判定,玩家与敌人的碰撞判定。
分数与等级模块:击杀敌人获得分数,每击杀10个敌人武器等级提升,每完成一波击杀进入下一等级。
特殊武器模块:消耗能量值释放全屏AOE伤害,有冷却时间限制。
特效系统模块:爆炸粒子效果、命中粒子效果、动态背景星星。
游戏状态管理模块:实现暂停功能、游戏结束判定、重新开始功能。
2.2 实验设计
2.2.1 系统架构
本程序采用面向对象的设计思想,核心类是SpaceWarGame,负责管理所有游戏对象和游戏循环。程序整体分为以下几个层次:
界面层:使用tkinter创建游戏窗口和画布,负责绘制所有游戏元素和显示UI信息。
逻辑层:包含玩家控制逻辑、敌人AI逻辑、子弹更新逻辑、碰撞检测逻辑、波次管理逻辑。
数据层:维护玩家属性字典、敌人列表、子弹列表、粒子列表、星星列表等数据结构。
控制层:键盘事件绑定与响应、游戏主循环、暂停与继续控制。
2.2.2 核心数据结构设计
玩家属性使用字典存储,包含位置坐标x和y、移动速度speed、碰撞半径radius、无敌帧数invincible、射击冷却shoot_cooldown、能量值energy。
敌人使用字典存储,包含位置坐标、血量hp和最大血量max_hp、移动速度speed、击杀分数points、类型type、颜色color、大小size、射击冷却shoot_cooldown。
子弹使用字典存储,包含位置坐标、速度向量vx和vy、伤害值damage、碰撞半径radius、颜色color。
粒子使用字典存储,包含位置坐标、速度向量、生命值life、颜色color、大小size。
2.2.3 关键算法设计
碰撞检测算法采用圆形碰撞检测,计算两个对象中心点之间的欧几里得距离,如果距离小于两对象半径之和则判定为碰撞。
敌人AI设计:普通敌人沿直线向下移动,巡洋舰会瞄准玩家方向射击,精英敌人会发射三发散弹,BOSS拥有三个战斗阶段,根据剩余血量百分比切换攻击模式。
波次控制逻辑:维护当前波次需要击杀的敌人数量kills_in_wave和enemies_in_wave,当击杀数达到波次要求且场上无敌人时,触发等级提升,刷新波次目标并生成新一波敌人。
武器升级逻辑:维护总击杀数kills,每击杀10个敌人且武器等级未满3级时提升武器等级,同时播放升级特效。
粒子特效系统:每个粒子拥有位置、速度、生命值和大小属性,每帧更新位置并减少生命值,生命值归零时从列表中移除。
2.3 实现过程
2.3.1 游戏窗口与UI实现
首先创建tkinter主窗口,设置窗口标题为“星际守卫者 - 太空大战”,窗口大小为1000x700像素且禁止调整大小。创建Canvas画布作为游戏绘制区域,背景色设置为深蓝色。在窗口顶部创建信息栏,使用Label控件显示得分、生命值、等级、波次进度、武器类型、能量值等信息。同时添加键盘操作提示标签,方便玩家了解控制方式。
2.3.2 玩家控制实现
绑定键盘按下和释放事件,维护keys字典记录各按键状态。在游戏循环中根据keys字典更新玩家位置,并设置边界限制防止玩家移出屏幕边缘。射击功能通过检测空格键状态触发,受冷却时间限制防止射速过快。无敌帧机制在玩家受到伤害后激活60帧,期间玩家处于闪烁状态且不会再次受到伤害。
2.3.3 敌人系统实现
敌人生成函数根据当前等级决定敌人类型概率。低等级时以战斗机为主,高等级时精英敌人出现概率增加。BOSS生成条件为等级达到3级以上且总击杀数为15的倍数,同时确保场上不存在其他BOSS。BOSS拥有独立的绘制函数和多阶段射击模式,第一阶段为扇形散射,第二阶段为螺旋弹幕,第三阶段为追踪弹幕加散射。
2.3.4 子弹与碰撞实现
玩家子弹和敌人子弹分别存储在两个列表中。子弹更新时根据速度向量移动位置,超出屏幕边界则移除。碰撞检测遍历所有子弹和敌人,计算距离判定是否命中,命中后减少敌人血量,血量归零时增加分数、击杀计数,并生成爆炸粒子特效。玩家与敌人子弹的碰撞类似,命中后减少生命值并触发无敌状态。
2.3.5 波次与等级实现
维护kills_in_wave变量记录当前波次击杀数,enemies_in_wave记录波次目标击杀数。每次击杀敌人后调用check_wave_complete函数检查是否达成波次要求。达成条件时调用level_up函数:等级加一,重置波次击杀计数,根据等级更新波次目标击杀数,恢复玩家能量值,播放升级特效和粒子庆祝效果,并在延迟后生成新一波敌人。
2.3.6 特效与动画实现
背景星星系统:创建200个星星对象,每个星星有位置、大小和下落速度,每帧向下移动,超出屏幕底部后重新出现在顶部。
粒子系统:爆炸和命中时生成粒子对象,每帧更新位置和生命值,生命值耗尽后移除。
游戏主循环:使用root.after方法以约60帧的速率循环调用game_loop函数,每帧依次调用更新玩家、更新子弹、更新敌人、更新粒子、更新星星和绘制函数。
2.4 结果展示
程序成功实现了以下功能:
(1)玩家可以使用方向键控制飞船在屏幕内移动,按空格键发射子弹。

(2)三种普通敌人类型各有不同的外观、血量、速度和分数,敌人会向下移动并向玩家射击。
(3)击杀敌人获得分数,累计击杀可提升武器等级,武器从单发升级为三发再到散弹。

(4)每完成一波击杀后等级提升,敌人数量和强度增加。
(5)当总击杀数达到15的倍数且等级达到3级时,BOSS敌人出现,BOSS拥有血条显示和三个战斗阶段。

(6)按S键可以释放特殊武器,消耗50点能量对全屏敌人造成伤害,能量随时间自动回复。
(7)玩家拥有3条生命,被击中后短暂无敌,生命耗尽后游戏结束并弹出重开对话框。

(8)按P键可暂停游戏,再次按P键恢复。

(9)爆炸粒子特效和命中特效增强了游戏的视觉反馈。
(10)动态背景星星营造了太空氛围。
程序运行流畅,画面帧率稳定,所有功能均正常工作。
3. 实验过程中遇到的问题和解决过程
问题1:波次击杀计数不准确
问题描述:在测试过程中发现,每次等级提升后,新的波次击杀计数没有正确重置,导致下一波次需要的击杀数计算错误。例如第一波需要击杀6个敌人,升级后第二波应该重置为0/7,但实际显示依然为6/7。
问题解决方案:经过代码审查发现,问题出在两个地方。第一,在level_up函数中没有重置kills_in_wave变量。第二,在击杀敌人时虽然增加了kills_in_wave,但使用特殊武器击杀敌人时遗漏了这一步骤。修复方案是在level_up函数中添加kills_in_wave = 0,并在use_special_weapon函数的击杀逻辑中添加kills_in_wave加一操作。同时新增check_wave_complete函数统一检查波次完成条件,避免逻辑分散。
问题2:BOSS生成数量过多
问题描述:原始代码中BOSS生成条件只检查总击杀数是否为15的倍数,但没有检查场上是否已存在BOSS。当玩家快速击杀敌人时,可能在BOSS存活期间又触发新的BOSS生成条件,导致多个BOSS同时出现在场上,严重影响游戏平衡性。
问题解决方案:在spawn_enemy函数的BOSS生成条件中添加判断,检查enemies列表中是否存在type为boss的敌人。如果已存在BOSS则跳过本次BOSS生成,改为生成普通敌人。这样可以确保同时最多只有一个BOSS在场。
问题3:玩家与敌人碰撞判定过于严格
问题描述:初始版本中玩家与敌人的碰撞半径设定为固定值,导致玩家飞船的尖角部分虽然视觉上没有接触敌人,但系统判定为碰撞,给玩家带来挫败感。
问题解决方案:调整碰撞判定半径,将玩家碰撞半径从实际飞船模型的半宽缩小约20%,同时敌人碰撞半径也根据类型适当缩小。经过多次测试调整,最终找到平衡点,使碰撞判定既符合视觉直觉,又不会过于宽松导致难度过低。
问题4:高帧率下特殊武器冷却时间过短
问题描述:特殊武器的冷却计数器每帧减一,在60帧率下180帧的冷却时间仅为3秒,加上能量回复速度较快,导致特殊武器使用频率过高,降低了游戏挑战性。
问题解决方案:将特殊武器冷却时间调整为180帧不变,但将能量回复速率从每帧0.15降低到每帧0.08,同时增加特殊武器的最低能量消耗为50点。这样特殊武器大约每3秒可用一次,但需要约6秒才能回复足够能量,有效控制了使用频率。
本次实验感悟与思考
通过本次综合实践,我对Python编程有了更深入的理解。首先是面向对象编程思想的应用,将游戏中的各个实体抽象为数据结构和操作函数,使代码结构清晰易于维护。其次是游戏循环的设计理念,理解了一个实时交互程序如何在每一帧中更新状态并重新绘制画面。再者是碰撞检测算法的实现,虽然是简单的圆形检测,但让我体会到了游戏开发中物理判定的基本原理。在调试过程中,我学会了使用print语句追踪变量状态,也学会了分模块测试的方法。当粒子系统导致游戏卡顿时,我通过分析发现是粒子数量过多且逐帧创建新图形对象导致的性能问题,后续通过限制粒子数量和优化绘制方式得到改善。这次实践让我认识到,一个完整的软件项目不仅仅是写出能运行的代码,还需要考虑用户体验、性能优化、代码可读性等多个方面。从最初的控制台程序到如今能够开发出带有图形界面和特效的游戏,这学期的Python课程让我收获颇丰。
4. 全课总结与感想体会
本学期《Python 程序设计》课程系统覆盖了从基础语法到综合项目开发的完整知识体系,让我建立起规范、完整的 Python 编程认知。
在基础语法部分,我掌握了变量、数据类型、条件判断、循环结构、输入输出等核心语法,理解了 Python 简洁、灵活、易读的语言特点,能够独立编写顺序、分支、循环结构的基础程序。
在数据结构部分,我熟练掌握列表、元组、字典、集合的定义、操作与适用场景,学会用列表存储有序数据、用字典快速存取键值对、用集合去重与判断成员关系,本次太空大战游戏中玩家、敌人、子弹、粒子等对象均采用字典与列表组合存储,实现高效数据管理。
在函数与模块部分,我理解了函数定义、参数传递、返回值、作用域与递归思想,学会将重复逻辑封装为函数提升代码复用性;掌握模块导入与使用方法,能够合理拆分功能、组织代码结构,让程序更清晰易维护。
在面向对象编程部分,我掌握类与对象、属性与方法、封装、继承等核心概念,能够用面向对象思想抽象现实对象、设计程序架构。本次游戏以SpaceWarGame类为核心,统一管理窗口、玩家、敌人、碰撞、UI 等全部逻辑,是面向对象设计的典型实践。
通过一学期的学习与本次综合实践,我对 Python 编程从陌生到熟悉,从只会写简单代码到能独立完成完整图形化游戏,收获远超预期。编程不再是枯燥的语法记忆,而是用代码实现想法、解决问题、创造作品的过程。本次太空大战游戏从需求分析、架构设计、代码编写到调试优化,全程独立完成,让我真正体会到从0到1搭建项目的成就感。当看到飞船移动、子弹发射、敌人被击中、分数上涨、BOSS 登场、特效绽放时,我深刻感受到编程的魅力与乐趣。在开发过程中,我学会先设计再编码,遇到 BUG 不再盲目修改,而是通过定位变量、拆分模块、逐步测试找到问题根源。同时我也认识到,优秀的代码不仅要能运行,还要命名规范、结构清晰、注释合理、易于维护。良好的编程习惯能大幅提升开发与调试效率。本次实践让我理解游戏开发的核心逻辑:游戏循环、状态更新、画面重绘、碰撞判定、AI 行为、数值平衡等,也体会到大型项目中性能优化、用户体验、边界处理的重要性。从控制台程序到图形化交互游戏,我的编程思维、逻辑能力、工程能力都得到全面提升。
课程意见为可设置小组合作项目,让学生分工完成界面、逻辑、文档等任务,培养团队协作与沟通能力,更贴近真实开发场景。
总体而言,本课程内容充实、节奏合理、实践性强,为我打下扎实的 Python 基础。感谢老师的耐心指导,我将继续深入学习,用 Python 实现更多有趣、实用的项目。
附带运行视频如下:
https://meeting.tencent.com/crm/NQLw18eJf9
附带实验代码如下:
`import tkinter as tk
from tkinter import messagebox
import math
import random
import time
WIDTH, HEIGHT = 1000, 700
PLAYER_SIZE = 18
class SpaceWarGame:
def init(self, root):
self.root = root
self.root.title("🚀 星际守卫者 - 太空大战")
self.root.resizable(False, False)
self.canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg="#000010", highlightthickness=0)
self.canvas.pack()
self.running = True
self.paused = False
self.score = 0
self.lives = 3
self.level = 1
self.kills = 0 # 总击杀数
self.kills_in_wave = 0
self.player = {
"x": WIDTH // 2,
"y": HEIGHT - 60,
"speed": 7,
"radius": 15,
"invincible": 0,
"shoot_cooldown": 0,
"energy": 100
}
self.enemies = []
self.bullets = []
self.enemy_bullets = []
self.particles = []
self.stars = []
self.weapon_level = 1
self.special_weapon_ready = True
self.special_cooldown = 0
self.enemies_in_wave = 6
self.wave_delay = 0
self.wave_complete = False
self.frame = 0
for _ in range(200):
self.stars.append({
"x": random.randint(0, WIDTH),
"y": random.randint(0, HEIGHT),
"size": random.randint(1, 3),
"speed": random.uniform(0.5, 2)
})
self.keys = {"left": False, "right": False, "up": False, "down": False, "space": False}
self.root.bind("
self.root.bind("
self.create_ui()
self.start_game()
self.game_loop()
def create_ui(self):
self.info_frame = tk.Frame(self.root, bg="#000020", height=50)
self.info_frame.pack(fill=tk.X, side=tk.TOP)
self.score_label = tk.Label(self.info_frame, text=f"得分: {self.score}",
font=("Courier", 14, "bold"), fg="#00ff00", bg="#000020")
self.score_label.pack(side=tk.LEFT, padx=20, pady=10)
self.lives_label = tk.Label(self.info_frame, text=f"生命: {'❤️' * self.lives}",
font=("Courier", 14, "bold"), fg="#ff0000", bg="#000020")
self.lives_label.pack(side=tk.LEFT, padx=20, pady=10)
self.level_label = tk.Label(self.info_frame, text=f"等级: {self.level}",
font=("Courier", 14, "bold"), fg="#ffff00", bg="#000020")
self.level_label.pack(side=tk.LEFT, padx=20, pady=10)
self.wave_progress_label = tk.Label(self.info_frame, text=f"波次进度: 0/{self.enemies_in_wave}",
font=("Courier", 10), fg="#00ffff", bg="#000020")
self.wave_progress_label.pack(side=tk.LEFT, padx=20, pady=10)
self.weapon_label = tk.Label(self.info_frame, text=f"武器: {['单发', '三发', '散弹'][self.weapon_level - 1]}",
font=("Courier", 12), fg="#00ffff", bg="#000020")
self.weapon_label.pack(side=tk.LEFT, padx=20, pady=10)
self.energy_label = tk.Label(self.info_frame, text=f"能量: {self.player['energy']}%",
font=("Courier", 12), fg="#ffaa00", bg="#000020")
self.energy_label.pack(side=tk.RIGHT, padx=20, pady=10)
controls_text = "← → ↑ ↓ 移动 | [空格] 射击 | [S] 特殊武器 | [P] 暂停"
self.controls_label = tk.Label(self.info_frame, text=controls_text,
font=("Arial", 10), fg="#888", bg="#000020")
self.controls_label.pack(side=tk.BOTTOM, pady=5)
def update_ui(self):
self.score_label.config(text=f"得分: {self.score}")
self.lives_label.config(text=f"生命: {'❤️' * self.lives}")
self.level_label.config(text=f"等级: {self.level}")
self.wave_progress_label.config(text=f"波次进度: {self.kills_in_wave}/{self.enemies_in_wave}")
self.weapon_label.config(text=f"武器: {['单发', '三发', '散弹'][self.weapon_level - 1]}")
self.energy_label.config(text=f"能量: {int(self.player['energy'])}%")
def key_press(self, event):
if event.keysym == "Left":
self.keys["left"] = True
elif event.keysym == "Right":
self.keys["right"] = True
elif event.keysym == "Up":
self.keys["up"] = True
elif event.keysym == "Down":
self.keys["down"] = True
elif event.keysym == "space":
self.keys["space"] = True
elif event.keysym == "p" or event.keysym == "P":
self.toggle_pause()
elif event.keysym == "s" or event.keysym == "S":
self.use_special_weapon()
def key_release(self, event):
if event.keysym == "Left":
self.keys["left"] = False
elif event.keysym == "Right":
self.keys["right"] = False
elif event.keysym == "Up":
self.keys["up"] = False
elif event.keysym == "Down":
self.keys["down"] = False
elif event.keysym == "space":
self.keys["space"] = False
def toggle_pause(self):
self.paused = not self.paused
if self.paused:
self.canvas.create_text(WIDTH // 2, HEIGHT // 2, text="⏸ 游戏暂停",
font=("Arial", 40, "bold"), fill="white", tags="pause_text")
else:
self.canvas.delete("pause_text")
def start_game(self):
for _ in range(3):
self.spawn_enemy()
def level_up(self):
self.level += 1
self.kills_in_wave = 0
self.enemies_in_wave = min(5 + self.level, 15)
self.player["energy"] = 100
self.wave_delay = 90
self.wave_complete = True
self.canvas.create_text(WIDTH // 2, HEIGHT // 2, text=f"第 {self.level} 关",
font=("Arial", 48, "bold"), fill="#ffff00", tags="level_up")
self.root.after(1500, lambda: self.canvas.delete("level_up"))
for _ in range(50):
self.particles.append({
"x": random.randint(0, WIDTH),
"y": random.randint(0, HEIGHT),
"vx": random.uniform(-3, 3),
"vy": random.uniform(-3, 3),
"life": 30,
"color": random.choice(["#ffff00", "#00ff00", "#00ffff"]),
"size": random.randint(2, 5)
})
self.update_ui()
def spawn_enemy(self):
if self.wave_delay > 0:
return
if len(self.enemies) > 15:
return
is_boss = False
if self.level >= 3 and self.kills > 0 and self.kills % 15 == 0:
if not any(e["type"] == "boss" for e in self.enemies):
is_boss = True
if is_boss:
boss_hp = 30 + self.level * 5
self.enemies.append({
"x": WIDTH // 2,
"y": -80,
"hp": boss_hp,
"max_hp": boss_hp,
"speed": 0.8,
"points": 500 * self.level,
"type": "boss",
"shoot_cooldown": 0,
"phase": 1,
"angle": 0,
"glow": 0,
"armor": 3
})
for _ in range(50):
self.particles.append({
"x": random.randint(0, WIDTH),
"y": random.randint(0, 100),
"vx": random.uniform(-3, 3),
"vy": random.uniform(2, 6),
"life": 30,
"color": "#ff00ff",
"size": random.randint(2, 5)
})
else:
rand = random.random()
if self.level < 3:
enemy_type = "fighter" if rand < 0.7 else "cruiser"
elif self.level < 6:
if rand < 0.5:
enemy_type = "fighter"
elif rand < 0.8:
enemy_type = "cruiser"
else:
enemy_type = "elite"
else:
if rand < 0.4:
enemy_type = "fighter"
elif rand < 0.7:
enemy_type = "cruiser"
else:
enemy_type = "elite"
if enemy_type == "fighter":
hp = 1 + self.level // 3
speed = 2.5
points = 10 * self.level
color = "#ff4444"
size = 15
elif enemy_type == "cruiser":
hp = 3 + self.level // 2
speed = 1.5
points = 30 * self.level
color = "#ff8844"
size = 20
else:
hp = 8 + self.level
speed = 1.2
points = 80 * self.level
color = "#aa44ff"
size = 25
self.enemies.append({
"x": random.randint(50, WIDTH - 50),
"y": random.randint(-50, 50),
"hp": hp,
"max_hp": hp,
"speed": speed,
"points": points,
"color": color,
"size": size,
"type": enemy_type,
"shoot_cooldown": random.randint(20, 50),
"sway": random.choice([True, False])
})
def shoot(self):
if self.player["shoot_cooldown"] > 0:
return
if self.weapon_level == 1:
self.bullets.append({
"x": self.player["x"],
"y": self.player["y"] - PLAYER_SIZE,
"vx": 0,
"vy": -12,
"damage": 1,
"radius": 4,
"color": "#00ff00"
})
self.player["shoot_cooldown"] = 10
elif self.weapon_level == 2:
for offset in [-10, 0, 10]:
self.bullets.append({
"x": self.player["x"] + offset,
"y": self.player["y"] - PLAYER_SIZE,
"vx": offset * 0.15,
"vy": -12,
"damage": 1,
"radius": 4,
"color": "#00ff00"
})
self.player["shoot_cooldown"] = 12
else:
for angle in [-25, -12, 0, 12, 25]:
rad = math.radians(angle)
self.bullets.append({
"x": self.player["x"],
"y": self.player["y"] - PLAYER_SIZE,
"vx": math.sin(rad) * 4,
"vy": -12,
"damage": 1,
"radius": 4,
"color": "#00ff00"
})
self.player["shoot_cooldown"] = 15
def use_special_weapon(self):
if not self.special_weapon_ready or self.player["energy"] < 50:
return
self.special_weapon_ready = False
self.special_cooldown = 180
self.player["energy"] -= 50
for enemy in self.enemies[:]:
damage = 3 if enemy["type"] != "boss" else 2
enemy["hp"] -= damage
if enemy["hp"] <= 0:
self.score += enemy["points"]
self.kills += 1
self.kills_in_wave += 1 # 【修复】计入波次击杀
self.enemies.remove(enemy)
self.create_explosion(enemy["x"], enemy["y"], 30)
for _ in range(80):
self.particles.append({
"x": random.randint(0, WIDTH),
"y": random.randint(0, HEIGHT),
"vx": random.uniform(-6, 6),
"vy": random.uniform(-6, 6),
"life": 30,
"color": "#ffff00",
"size": random.randint(2, 5)
})
self.update_ui()
self.check_wave_complete() # 【新增】检查是否完成波次
self.root.after(3000, lambda: setattr(self, 'special_weapon_ready', True))
def check_wave_complete(self):
if self.kills_in_wave >= self.enemies_in_wave and len(self.enemies) == 0 and not self.wave_complete:
self.level_up()
def boss_shoot(self, boss):
boss["shoot_cooldown"] -= 1
if boss["shoot_cooldown"] > 0:
return
hp_percent = boss["hp"] / boss["max_hp"]
if hp_percent > 0.66:
for angle_offset in range(-60, 61, 15):
rad = math.radians(angle_offset)
self.enemy_bullets.append({
"x": boss["x"],
"y": boss["y"] + 20,
"vx": math.sin(rad) * 6,
"vy": math.cos(rad) * 6,
"damage": 2,
"radius": 6,
"color": "#ff00ff"
})
boss["shoot_cooldown"] = 40
elif hp_percent > 0.33:
boss["angle"] = boss.get("angle", 0) + 15
for i in range(8):
rad = math.radians(boss["angle"] + i * 45)
self.enemy_bullets.append({
"x": boss["x"],
"y": boss["y"] + 20,
"vx": math.sin(rad) * 5,
"vy": math.cos(rad) * 5,
"damage": 2,
"radius": 6,
"color": "#ff44ff"
})
boss["shoot_cooldown"] = 30
else:
dx = self.player["x"] - boss["x"]
dy = self.player["y"] - boss["y"]
dist = math.hypot(dx, dy)
if dist > 0:
vx = (dx / dist) * 7
vy = (dy / dist) * 7
else:
vx, vy = 0, 7
self.enemy_bullets.append({
"x": boss["x"],
"y": boss["y"] + 20,
"vx": vx,
"vy": vy,
"damage": 3,
"radius": 8,
"color": "#ff0000"
})
for angle_offset in range(-45, 46, 22):
rad = math.radians(angle_offset)
self.enemy_bullets.append({
"x": boss["x"],
"y": boss["y"] + 20,
"vx": math.sin(rad) * 6 + vx * 0.3,
"vy": math.cos(rad) * 6 + vy * 0.3,
"damage": 2,
"radius": 5,
"color": "#ff6600"
})
boss["shoot_cooldown"] = 20
def enemy_shoot(self, enemy):
if enemy["type"] == "boss":
self.boss_shoot(enemy)
return
if enemy.get("shoot_cooldown", 0) > 0:
enemy["shoot_cooldown"] -= 1
return
if enemy["type"] == "cruiser":
dx = self.player["x"] - enemy["x"]
dy = self.player["y"] - enemy["y"]
dist = math.hypot(dx, dy)
if dist > 0:
vx = (dx / dist) * 5
vy = (dy / dist) * 5
else:
vx, vy = 0, 5
self.enemy_bullets.append({
"x": enemy["x"],
"y": enemy["y"],
"vx": vx,
"vy": vy,
"damage": 1,
"radius": 5,
"color": "#ff4444"
})
enemy["shoot_cooldown"] = 50
elif enemy["type"] == "elite":
for offset in [-10, 0, 10]:
self.enemy_bullets.append({
"x": enemy["x"] + offset,
"y": enemy["y"] + 5,
"vx": offset * 0.2,
"vy": 6,
"damage": 1,
"radius": 4,
"color": "#ff8844"
})
enemy["shoot_cooldown"] = 60
def update_player(self):
if self.player["invincible"] > 0:
self.player["invincible"] -= 1
if self.keys["left"] and self.player["x"] > PLAYER_SIZE:
self.player["x"] -= self.player["speed"]
if self.keys["right"] and self.player["x"] < WIDTH - PLAYER_SIZE:
self.player["x"] += self.player["speed"]
if self.keys["up"] and self.player["y"] > PLAYER_SIZE + 50:
self.player["y"] -= self.player["speed"]
if self.keys["down"] and self.player["y"] < HEIGHT - PLAYER_SIZE:
self.player["y"] += self.player["speed"]
if self.keys["space"]:
self.shoot()
if self.player["shoot_cooldown"] > 0:
self.player["shoot_cooldown"] -= 1
if self.special_cooldown > 0:
self.special_cooldown -= 1
if self.player["energy"] < 100:
self.player["energy"] += 0.15
def update_bullets(self):
for bullet in self.bullets[:]:
bullet["x"] += bullet["vx"]
bullet["y"] += bullet["vy"]
if bullet["y"] < 0 or bullet["y"] > HEIGHT or bullet["x"] < 0 or bullet["x"] > WIDTH:
self.bullets.remove(bullet)
continue
for enemy in self.enemies[:]:
dist = math.hypot(bullet["x"] - enemy["x"], bullet["y"] - enemy["y"])
enemy_size = 35 if enemy["type"] == "boss" else enemy.get("size", 20)
if dist < enemy_size:
enemy["hp"] -= bullet["damage"]
self.bullets.remove(bullet)
self.create_particle(bullet["x"], bullet["y"], "#ffff00", 3)
if enemy["hp"] <= 0:
self.score += enemy["points"]
self.kills += 1
self.kills_in_wave += 1
self.enemies.remove(enemy)
self.create_explosion(enemy["x"], enemy["y"], 40 if enemy["type"] == "boss" else 20)
self.update_ui()
if self.kills % 10 == 0 and self.weapon_level < 3:
self.weapon_level += 1
self.create_particle(self.player["x"], self.player["y"], "#00ffff", 30)
self.update_ui()
self.check_wave_complete()
break
for bullet in self.enemy_bullets[:]:
bullet["x"] += bullet["vx"]
bullet["y"] += bullet["vy"]
if bullet["y"] > HEIGHT or bullet["y"] < 0 or bullet["x"] < 0 or bullet["x"] > WIDTH:
self.enemy_bullets.remove(bullet)
continue
if self.player["invincible"] <= 0:
dist = math.hypot(bullet["x"] - self.player["x"], bullet["y"] - self.player["y"])
if dist < PLAYER_SIZE:
self.lives -= 1
self.player["invincible"] = 60
self.enemy_bullets.remove(bullet)
self.create_explosion(self.player["x"], self.player["y"], PLAYER_SIZE)
self.update_ui()
if self.lives <= 0:
self.game_over()
break
def update_enemies(self):
if self.wave_delay > 0:
self.wave_delay -= 1
if self.wave_delay == 0:
self.wave_complete = False
for _ in range(min(3 + self.level // 2, 8)):
self.spawn_enemy()
return
if len(self.enemies) < 5 and not self.wave_complete:
if random.randint(1, 30) == 1:
self.spawn_enemy()
for enemy in self.enemies[:]:
enemy["y"] += enemy["speed"]
if enemy["type"] == "boss":
enemy["x"] += math.sin(self.frame * 0.02) * 1.5
enemy["x"] = max(100, min(WIDTH - 100, enemy["x"]))
enemy["glow"] = enemy.get("glow", 0) + 0.1
if enemy["y"] > HEIGHT + 100:
self.enemies.remove(enemy)
if enemy["type"] != "boss":
self.lives -= 1
self.update_ui()
if self.lives <= 0:
self.game_over()
continue
if enemy.get("sway"):
enemy["x"] += math.sin(time.time() * 3) * 0.8
enemy["x"] = max(20, min(WIDTH - 20, enemy["x"]))
self.enemy_shoot(enemy)
if self.player["invincible"] <= 0:
enemy_size = 35 if enemy["type"] == "boss" else enemy.get("size", 20)
dist = math.hypot(self.player["x"] - enemy["x"], self.player["y"] - enemy["y"])
if dist < PLAYER_SIZE + enemy_size:
self.lives -= 1
self.player["invincible"] = 60
self.enemies.remove(enemy)
self.create_explosion(self.player["x"], self.player["y"], PLAYER_SIZE)
self.update_ui()
if self.lives <= 0:
self.game_over()
break
def create_particle(self, x, y, color, count):
for _ in range(count):
self.particles.append({
"x": x,
"y": y,
"vx": random.uniform(-3, 3),
"vy": random.uniform(-3, 3),
"life": random.randint(15, 30),
"color": color,
"size": random.randint(2, 4)
})
def create_explosion(self, x, y, size):
for _ in range(size):
self.particles.append({
"x": x + random.uniform(-size, size),
"y": y + random.uniform(-size, size),
"vx": random.uniform(-5, 5),
"vy": random.uniform(-5, 5),
"life": random.randint(20, 40),
"color": random.choice(["#ff6600", "#ff0000", "#ffff00"]),
"size": random.randint(3, 6)
})
def update_particles(self):
for p in self.particles[:]:
p["x"] += p["vx"]
p["y"] += p["vy"]
p["life"] -= 1
if p["life"] <= 0:
self.particles.remove(p)
def update_stars(self):
for star in self.stars:
star["y"] += star["speed"]
if star["y"] > HEIGHT:
star["y"] = 0
star["x"] = random.randint(0, WIDTH)
def draw_boss(self, x, y, boss):
hp_percent = boss["hp"] / boss["max_hp"]
glow_intensity = (math.sin(boss["glow"]) + 1) / 2
for i in range(3):
radius = 45 + i * 5 + int(glow_intensity * 5)
self.canvas.create_oval(x - radius, y - radius, x + radius, y + radius,
outline=f"#{int(255 * 0.5):02x}{int(0):02x}{int(255 * 0.5 + 100 * glow_intensity):02x}",
width=2, tags="game")
main_color = f"#{int(150 + 105 * (1 - hp_percent)):02x}{int(50):02x}{int(150 + 105 * hp_percent):02x}"
self.canvas.create_oval(x - 35, y - 35, x + 35, y + 35,
fill=main_color, outline="#ff00ff", width=3, tags="game")
eye_color = "#ff0000" if hp_percent < 0.3 else "#ff4400" if hp_percent < 0.6 else "#ff8800"
self.canvas.create_oval(x - 18, y - 12, x - 8, y - 2, fill=eye_color, outline="white", width=2, tags="game")
self.canvas.create_oval(x - 16, y - 10, x - 12, y - 6, fill="black", tags="game")
self.canvas.create_oval(x + 8, y - 12, x + 18, y - 2, fill=eye_color, outline="white", width=2, tags="game")
self.canvas.create_oval(x + 12, y - 10, x + 16, y - 6, fill="black", tags="game")
mouth_y = y + 5
if hp_percent > 0.66:
self.canvas.create_arc(x - 15, mouth_y - 5, x + 15, mouth_y + 10,
start=0, extent=180, fill="#440000", outline="#ff0000", width=2, tags="game")
elif hp_percent > 0.33:
self.canvas.create_line(x - 15, mouth_y, x - 5, mouth_y + 8, fill="#ff0000", width=3, tags="game")
self.canvas.create_line(x + 5, mouth_y + 8, x + 15, mouth_y, fill="#ff0000", width=3, tags="game")
else:
self.canvas.create_line(x - 15, mouth_y + 5, x - 5, mouth_y - 3, fill="#ff0000", width=3, tags="game")
self.canvas.create_line(x + 5, mouth_y - 3, x + 15, mouth_y + 5, fill="#ff0000", width=3, tags="game")
self.canvas.create_line(x - 25, y - 5, x - 35, y - 15, fill="#ff0000", width=2, tags="game")
self.canvas.create_line(x + 25, y - 5, x + 35, y - 15, fill="#ff0000", width=2, tags="game")
self.canvas.create_polygon(x - 25, y - 35, x - 35, y - 55, x - 15, y - 40,
fill="#660066", outline="#ff00ff", width=2, tags="game")
self.canvas.create_polygon(x + 25, y - 35, x + 35, y - 55, x + 15, y - 40,
fill="#660066", outline="#ff00ff", width=2, tags="game")
arm_count = 4 + int((1 - hp_percent) * 4)
for i in range(arm_count):
angle = self.frame * 0.05 + i * (math.pi * 2 / arm_count)
arm_x = x + math.cos(angle) * 45
arm_y = y + math.sin(angle) * 45
self.canvas.create_line(x, y, arm_x, arm_y, fill="#ff00ff", width=3, tags="game")
self.canvas.create_oval(arm_x - 5, arm_y - 5, arm_x + 5, arm_y + 5,
fill="#ff4444", outline="#ffffff", width=1, tags="game")
bar_width = 200
bar_height = 12
bar_x = x - bar_width // 2
bar_y = y - 55
self.canvas.create_rectangle(bar_x, bar_y, bar_x + bar_width, bar_y + bar_height,
fill="#330000", outline="#ff0000", width=1, tags="game")
hp_width = bar_width * hp_percent
if hp_percent > 0.6:
bar_color = "#00ff00"
elif hp_percent > 0.3:
bar_color = "#ffaa00"
else:
bar_color = "#ff0000"
self.canvas.create_rectangle(bar_x, bar_y, bar_x + hp_width, bar_y + bar_height,
fill=bar_color, outline="", tags="game")
self.canvas.create_text(x, y - 70, text=f"⚠️ 虚空领主 ⚠️",
fill="#ff00ff", font=("Arial", 14, "bold"), tags="game")
if hp_percent > 0.66:
phase_text = "阶段 1 - 散射模式"
elif hp_percent > 0.33:
phase_text = "阶段 2 - 螺旋模式"
else:
phase_text = "阶段 3 - 疯狂模式"
self.canvas.create_text(x, y + 50, text=phase_text,
fill="#ffaa00", font=("Arial", 10, "bold"), tags="game")
def draw_enemy(self, enemy):
if enemy["type"] == "boss":
self.draw_boss(enemy["x"], enemy["y"], enemy)
return
self.canvas.create_oval(enemy["x"] - enemy["size"], enemy["y"] - enemy["size"],
enemy["x"] + enemy["size"], enemy["y"] + enemy["size"],
fill=enemy["color"], outline="#ffffff", width=2, tags="game")
eye_size = enemy["size"] // 4
self.canvas.create_oval(enemy["x"] - eye_size, enemy["y"] - eye_size,
enemy["x"] + eye_size, enemy["y"] + eye_size,
fill="white", tags="game")
self.canvas.create_oval(enemy["x"] - eye_size // 2, enemy["y"] - eye_size // 2,
enemy["x"] + eye_size // 2, enemy["y"] + eye_size // 2,
fill="black", tags="game")
if enemy["hp"] < enemy["max_hp"]:
bar_width = enemy["size"] * 2
bar_height = 5
bar_x = enemy["x"] - enemy["size"]
bar_y = enemy["y"] - enemy["size"] - 8
hp_width = bar_width * (enemy["hp"] / enemy["max_hp"])
self.canvas.create_rectangle(bar_x, bar_y, bar_x + bar_width, bar_y + bar_height,
fill="red", outline="", tags="game")
self.canvas.create_rectangle(bar_x, bar_y, bar_x + hp_width, bar_y + bar_height,
fill="#00ff00", outline="", tags="game")
if enemy["type"] == "elite":
self.canvas.create_text(enemy["x"], enemy["y"] - enemy["size"] - 10,
text="⭐", fill="#ffdd00", font=("Arial", 12), tags="game")
def draw_player(self):
if self.player["invincible"] > 0 and (self.player["invincible"] // 5) % 2 == 0:
player_color = "#ffffff"
else:
player_color = "#00ff00"
self.canvas.create_polygon(
self.player["x"], self.player["y"] - PLAYER_SIZE,
self.player["x"] - PLAYER_SIZE, self.player["y"] + PLAYER_SIZE // 2,
self.player["x"] - PLAYER_SIZE // 2, self.player["y"] + PLAYER_SIZE // 2,
self.player["x"] - PLAYER_SIZE // 2, self.player["y"] + PLAYER_SIZE,
self.player["x"], self.player["y"] + PLAYER_SIZE // 1.5,
self.player["x"] + PLAYER_SIZE // 2, self.player["y"] + PLAYER_SIZE,
self.player["x"] + PLAYER_SIZE // 2, self.player["y"] + PLAYER_SIZE // 2,
self.player["x"] + PLAYER_SIZE, self.player["y"] + PLAYER_SIZE // 2,
fill=player_color, outline="#ffffff", width=2, tags="game"
)
flame = random.randint(5, 12)
self.canvas.create_polygon(
self.player["x"] - 6, self.player["y"] + PLAYER_SIZE,
self.player["x"], self.player["y"] + PLAYER_SIZE + flame,
self.player["x"] + 6, self.player["y"] + PLAYER_SIZE,
fill="#ff6600", outline="", tags="game"
)
def draw(self):
self.canvas.delete("game")
self.frame += 1
for star in self.stars:
self.canvas.create_oval(star["x"] - star["size"] // 2, star["y"] - star["size"] // 2,
star["x"] + star["size"] // 2, star["y"] + star["size"] // 2,
fill="white", outline="", tags="game")
self.draw_player()
for enemy in self.enemies:
self.draw_enemy(enemy)
for bullet in self.bullets:
self.canvas.create_oval(bullet["x"] - bullet["radius"], bullet["y"] - bullet["radius"],
bullet["x"] + bullet["radius"], bullet["y"] + bullet["radius"],
fill=bullet["color"], outline="", tags="game")
for bullet in self.enemy_bullets:
self.canvas.create_oval(bullet["x"] - bullet["radius"], bullet["y"] - bullet["radius"],
bullet["x"] + bullet["radius"], bullet["y"] + bullet["radius"],
fill=bullet["color"], outline="#ff0000", tags="game")
for p in self.particles:
self.canvas.create_oval(p["x"] - p["size"], p["y"] - p["size"],
p["x"] + p["size"], p["y"] + p["size"],
fill=p["color"], outline="", tags="game")
if self.special_weapon_ready and self.player["energy"] >= 50:
self.canvas.create_text(WIDTH - 100, 30, text="⚡ 特殊武器就绪",
fill="#ffff00", font=("Arial", 10, "bold"), tags="game")
elif self.special_cooldown > 0:
self.canvas.create_text(WIDTH - 100, 30, text=f"⏳ 冷却: {self.special_cooldown // 60}",
fill="#888", font=("Arial", 10), tags="game")
def game_over(self):
self.running = False
self.canvas.create_text(WIDTH // 2, HEIGHT // 2, text="GAME OVER",
font=("Arial", 60, "bold"), fill="red", tags="game_over")
self.canvas.create_text(WIDTH // 2, HEIGHT // 2 + 60, text=f"最终得分: {self.score}",
font=("Arial", 24), fill="white", tags="game_over")
if messagebox.askyesno("游戏结束", f"得分: {self.score}\n等级: {self.level}\n是否重新开始?"):
self.restart_game()
def restart_game(self):
self.canvas.delete("all")
self.init(self.root)
def game_loop(self):
if self.running and not self.paused:
self.update_player()
self.update_bullets()
self.update_enemies()
self.update_particles()
self.update_stars()
self.draw()
self.root.after(16, self.game_loop)
if name == "main":
root = tk.Tk()
game = SpaceWarGame(root)
root.mainloop()`
浙公网安备 33010602011771号