20244305 2025-2026-2 《Python程序设计》实验四报告
20244305 2025-2026-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2443
姓名: 苏楠
学号:20244305
实验教师:王志强
实验日期:2026年5月27日
必修/选修: 公选课
1.实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
课代表和各小组负责人收集作业(源代码、视频、综合实践报告)
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
本次python实践我是做了一款无尽生存射击类小游戏(网络上很火),玩家操控屏幕中心角色,通过移动、冲刺、射击击败随机刷新的各类怪物,获取经验升级,解锁强化属性,挑战无尽波次的怪物围攻,直至角色死亡游戏结束。游戏开发全程采用面向对象 + 模块化设计,代码结构清晰、功能完整、运行稳定,实现了角色操控、怪物 AI、升级系统、粒子特效、音效系统、计分系统等核心功能。
2. 实验过程及结果
(一)安装Pygame库

(二)由于刚开始做比较茫然,所以借助了豆包大模型的帮助生成了初版
点击查看代码
import pygame
import random
import math
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("俯视角割草游戏")
clock = pygame.time.Clock()
FPS = 60
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
class Player:
def __init__(self):
self.x = WIDTH // 2
self.y = HEIGHT // 2
self.radius = 20
self.speed = 4
self.hp = 100
self.level = 1
self.exp = 0
self.exp_need = 20
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_w] and self.y > self.radius:
self.y -= self.speed
if keys[pygame.K_s] and self.y < HEIGHT - self.radius:
self.y += self.speed
if keys[pygame.K_a] and self.x > self.radius:
self.x -= self.speed
if keys[pygame.K_d] and self.x < WIDTH - self.radius:
self.x += self.speed
def draw(self):
pygame.draw.circle(screen, BLUE, (int(self.x), int(self.y)), self.radius)
class Monster:
def __init__(self):
side = random.randint(0, 3)
if side == 0:
self.x = random.randint(0, WIDTH)
self.y = -20
elif side == 1:
self.x = WIDTH + 20
self.y = random.randint(0, HEIGHT)
elif side == 2:
self.x = random.randint(0, WIDTH)
self.y = HEIGHT + 20
else:
self.x = -20
self.y = random.randint(0, HEIGHT)
self.radius = 15
self.speed = 1.5
self.hp = 20
def update(self, px, py):
dx = px - self.x
dy = py - self.y
dist = math.hypot(dx, dy)
if dist > 0:
self.x += dx / dist * self.speed
self.y += dy / dist * self.speed
def draw(self):
pygame.draw.circle(screen, RED, (int(self.x), int(self.y)), self.radius)
class Bullet:
def __init__(self, x, y, tx, ty):
self.x = x
self.y = y
speed = 8
dx = tx - x
dy = ty - y
dist = math.hypot(dx, dy)
self.vx = dx / dist * speed
self.vy = dy / dist * speed
self.radius = 6
def update(self):
self.x += self.vx
self.y += self.vy
def draw(self):
pygame.draw.circle(screen, GREEN, (int(self.x), int(self.y)), self.radius)
# 全局对象
player = Player()
monsters = []
bullets = []
spawn_timer = 0
spawn_interval = 80
attack_timer = 0
attack_cd = 25
running = True
while running:
clock.tick(FPS)
screen.fill(BLACK)
# 退出事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 玩家移动+绘制
player.move()
player.draw()
# 怪物刷新
spawn_timer += 1
if spawn_timer >= spawn_interval:
monsters.append(Monster())
spawn_timer = 0
# 自动攻击
attack_timer += 1
if attack_timer >= attack_cd and monsters:
nearest = min(monsters, key=lambda m: math.hypot(m.x - player.x, m.y - player.y))
bullets.append(Bullet(player.x, player.y, nearest.x, nearest.y))
attack_timer = 0
# 子弹更新
for b in bullets[:]:
b.update()
if b.x < 0 or b.x > WIDTH or b.y < 0 or b.y > HEIGHT:
bullets.remove(b)
b.draw()
# 子弹碰撞怪物
for b in bullets[:]:
for m in monsters[:]:
dis = math.hypot(b.x - m.x, b.y - m.y)
if dis < b.radius + m.radius:
monsters.remove(m)
bullets.remove(b)
player.exp += 5
break
# 怪物更新
for m in monsters:
m.update(player.x, player.y)
m.draw()
# 升级系统
if player.exp >= player.exp_need:
player.level += 1
player.exp -= player.exp_need
player.exp_need = int(player.exp_need * 1.3)
player.speed += 0.2
attack_cd = max(10, attack_cd - 1)
pygame.display.flip()
pygame.quit()

但是这样还是很粗糙,几经大模型的调试和自己的创意改善,最后实现了这样的效果。

(三)下面我来解释一下各部分代码:
1.库导入与全局初始化
点击查看代码
import pygame
import random
import math
import sys
from enum import Enum
from collections import deque
# 初始化Pygame与音效
pygame.init()
pygame.mixer.init()
pygame.key.set_repeat(10, 10) # 键盘长按重复响应
2.常量与配置定义
点击查看代码
WIDTH, HEIGHT = 1000, 700
FPS = 60
# 颜色常量
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (220, 20, 60)
...
3.音效与工具函数
点击查看代码
# 生成游戏音效
def generate_sound(frequency, duration, volume=0.5, wave_type='square'):
...
# 辅助计算函数
def distance(x1, y1, x2, y2):
return math.hypot(x2 - x1, y2 - y1)
def normalize(dx, dy):
dist = math.hypot(dx, dy)
return dx/dist, dy/dist if dist !=0 else (0,0)
4.粒子特效系统
点击查看代码
class Particle:
def __init__(self, x, y, color, speed, lifetime):
...
def update(self):
...
def draw(self, surface):
...
class ParticleSystem:
def emit(self, x, y, color, count=10):
...
5.游戏核心对象类(最重要也是下功夫最多的)
首先是玩家类
点击查看代码
class Player:
def __init__(self):
# 定义属性:坐标、血量、速度、等级、攻击冷却等
self.x = WIDTH//2
self.max_hp = 100
self.speed = 5
...
def move(self, keys): # 移动+冲刺
...
def dash(self): # 空格键冲刺
...
def take_damage(self, damage): # 受击+无敌
...
def draw(self, surface): # 绘制角色+特效
...
点击查看代码
eye = y - 4
# 眼白
pygame.draw.circle(surface, (255, 255, 255), (x - 5, eye), 4)
pygame.draw.circle(surface, (255, 255, 255), (x + 5, eye), 4)
# 瞳孔
pygame.draw.circle(surface, (0, 0, 0), (x - 5, eye), 2)
pygame.draw.circle(surface, (0, 0, 0), (x + 5, eye), 2)
# 高光点
pygame.draw.circle(surface, (255, 255, 255), (x - 6, eye - 1), 1)
pygame.draw.circle(surface, (255, 255, 255), (x + 4, eye - 1), 1)
点击查看代码
class Monster:
def __init__(self, x, y, monster_type="normal"):
# 区分四种怪物:normal/fast/tank/shooter
if monster_type == "fast":
self.speed = 3.5
self.hp = 10
...
def update(self, player_x, player_y): # 追踪玩家
...
def take_damage(self, damage): # 受击死亡
...
点击查看代码
class Bullet:
def __init__(self, x, y, tx, ty, damage):
...
class Gem:
def __init__(self, x, y, value=5):
...
6.升级系统
点击查看代码
class UpgradeType(Enum):
MAX_HP = "Max HP +20"
SPEED = "Speed +0.5"
...
class UpgradeChoice:
def apply(self, player):
# 应用升级效果
if self.type == UpgradeType.MAX_HP:
player.max_hp +=20
7.游戏总管理类 Game
点击查看代码
class Game:
def __init__(self):
self.player = Player()
self.monsters = []
self.bullets = []
...
def spawn_monster(self): # 刷怪
...
def player_attack(self): # 自动攻击
...
def update(self): # 帧更新
...
def draw(self): # 画面绘制
...

我按照这一行的方式f"Score: {self.score}",对其进行了修正,新建一个变量self.kill,分别在reset和update中加入,最后将这一行("Kills: (calculating...)")改为f"Kills: {self.kills}"。
结果就是这样:

新加一部分,通过文件读写的学习,我决定增加一个本地最高分记录功能(在AI建议下选择了json文件)
首先在Game的init中加入最高分的初始记录
然后重点加入以下俩个方法
点击查看代码
def load_high_score(self):
"""从文件读取最高分"""
try:
with open("highscore.json", "r") as f:
data = json.load(f)
self.high_score = data.get("score", 0)
self.high_wave = data.get("wave", 0)
except (FileNotFoundError, json.JSONDecodeError):
self.high_score = 0
self.high_wave = 0
def save_high_score(self):
"""如果当前分数更高则保存"""
if self.score > self.high_score:
self.high_score = self.score
self.high_wave = self.wave
try:
with open("highscore.json", "w") as f:
json.dump({"score": self.high_score, "wave": self.high_wave}, f)
except Exception as e:
print("Could not save high score:", e)
(四)完整版代码在这里:暗影割草游戏

(五)游戏演示视频:
注:玩游戏时记得切换为英文模式,不然键盘可能失效
课程总结
时光匆匆,为期一学期的 Python 课程已悄然结束。这段学习之旅,虽未触及高阶编程知识,却让我收获满满、受益匪浅。
初次接触 Python 时,面对一行行陌生的代码、各式各样的语法规则,我满是迷茫与不知所措,连最简单的程序编写都觉得无从下手。但随着课程的逐步推进,在老师的讲解和一次次练习中,我慢慢拨开迷雾,熟悉了变量、循环、判断、列表、字符串等基础语法,也能独立编写简单的小程序,从毫无头绪到逐渐上手,每一点进步都让我倍感欣喜。
最让我有成就感的是,课程尾声我成功完成了结课作业,实现了较为复杂的游戏。从构思逻辑到调试修改,虽然借助AI的帮助,但我真切感受到了学习的各种知识的应用,也彻底打破了最初对代码的畏惧。
这一过程中,我用python实现了计数统计,绘画制作,也帮助同学完成了socket通信,也用爬虫代码去复现网页,当然,我深知自己掌握的只是 Python 的冰山一角,还有大量进阶知识、实用技巧等待探索,自身的编程能力也还有诸多不足。但这段学习经历,不仅让我入门了 Python 这门编程语言,更让我懂得了学习编程没有捷径,唯有耐心钻研、坚持练习,才能不断突破。
感悟体会和意见建议
这一学期的Python课程结束了,真心觉得王老师的课堂实用性十足。课上学到的内容总能立刻落地实操,在动手实践中掌握知识,这大概就是编程语言学习最好的方式,也正是它真正的魅力所在。
整学期上课氛围轻松又愉快,我不仅收获了满满的知识,还认识了不少志同道合的伙伴。尤其难忘最后一课,老师聊起窗外的风景。身处室内课堂,恰逢春夏时节,我也常常望见窗外盛放的繁花,总能一扫心头的烦闷。
课程虽已落幕,但我对编程的探索不会停止,学习的脚步也会一直向前。由衷感谢王老师的悉心授课,也庆幸自己当初选择了这门课程。
整体而言,这门课体验极佳,也是我在电科院就读以来,觉得收获颇丰的优质课程。如果提一点小小的建议,希望课程能增加更多趣味内容。我一直对游戏开发很感兴趣,也梦想着能独立制作小游戏,目前课堂上相关内容涉及较少。另外爬虫技术我也十分感兴趣,但学习起来有一定难度,课堂讲解时长偏短,希望后续可以适当增加这部分的授课时间与内容深度。
参考资料
pygame使用方法
pygame绘画教程
python读写json文件的方法
豆包
Deepseek
附录(完整代码)
import pygame
import random
import math
import sys
from enum import Enum
from collections import deque
import json
import os
==================== 初始化 ====================
pygame.init()
pygame.key.set_repeat(10, 10)
尝试导入 numpy,用于生成音效
try:
import numpy as np
_HAS_NUMPY = True
except ImportError:
_HAS_NUMPY = False
当没有 numpy 时用来代替真实音效的静音对象
class DummySound:
def play(self, loops=0, maxtime=0, fade_ms=0):
pass
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)
屏幕设置
WIDTH, HEIGHT = 1200, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.DOUBLEBUF)
pygame.display.set_caption("Shadow Slayer: Endless Trial")
clock = pygame.time.Clock()
FPS = 60
==================== 颜色常量 ====================
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (220, 20, 60)
GREEN = (50, 205, 50)
BLUE = (30, 144, 255)
YELLOW = (255, 215, 0)
PURPLE = (138, 43, 226)
ORANGE = (255, 140, 0)
CYAN = (0, 255, 255)
DARK_GRAY = (30, 30, 30)
LIGHT_GRAY = (200, 200, 200)
BLOOD_RED = (139, 0, 0)
==================== 字体处理 ====================
def get_font(size, bold=False):
return pygame.font.Font(None, size)
==================== 音效生成 ====================
def generate_sound(frequency, duration, volume=0.5, wave_type='square'):
if not _HAS_NUMPY:
return DummySound()
sample_rate = 22050
n_samples = int(sample_rate * duration)
buf = np.zeros((n_samples, 2), dtype=np.int16)
for i in range(n_samples):
t = i / sample_rate
if wave_type == 'square':
val = 1.0 if math.sin(2 * math.pi * frequency * t) >= 0 else -1.0
elif wave_type == 'sine':
val = math.sin(2 * math.pi * frequency * t)
elif wave_type == 'noise':
val = random.uniform(-1, 1)
else:
val = math.sin(2 * math.pi * frequency * t)
val = int(val * volume * 32767)
buf[i] = (val, val)
return pygame.sndarray.make_sound(buf)
预生成常用音效
sfx_shoot = generate_sound(800, 0.05, 0.3, 'square') or DummySound()
sfx_hit = generate_sound(200, 0.1, 0.5, 'noise') or DummySound()
sfx_levelup = generate_sound(600, 0.2, 0.7, 'sine') or DummySound()
sfx_die = generate_sound(100, 0.3, 0.8, 'sine') or DummySound()
sfx_pickup = generate_sound(1000, 0.08, 0.4, 'square') or DummySound()
==================== 辅助函数 ====================
def distance(x1, y1, x2, y2):
return math.hypot(x2 - x1, y2 - y1)
def normalize(dx, dy):
"""返回单位向量"""
dist = math.hypot(dx, dy)
if dist == 0:
return 0, 0
return dx / dist, dy / dist
def random_edge_position():
"""在屏幕边缘生成随机位置"""
side = random.randint(0, 3)
if side == 0: # 上边
x = random.randint(0, WIDTH)
y = -20
elif side == 1: # 右边
x = WIDTH + 20
y = random.randint(0, HEIGHT)
elif side == 2: # 下边
x = random.randint(0, WIDTH)
y = HEIGHT + 20
else: # 左边
x = -20
y = random.randint(0, HEIGHT)
return x, y
==================== 粒子系统 ====================
class Particle:
def init(self, x, y, color, speed, lifetime, size=3):
self.x = x
self.y = y
self.color = color
angle = random.uniform(0, 2 * math.pi)
self.vx = math.cos(angle) * speed
self.vy = math.sin(angle) * speed
self.lifetime = lifetime
self.max_lifetime = lifetime
self.size = size
def update(self):
self.x += self.vx
self.y += self.vy
self.lifetime -= 1
self.vx *= 0.98
self.vy *= 0.98
return self.lifetime > 0
def draw(self, surface):
alpha = int(255 * (self.lifetime / self.max_lifetime))
color = (self.color, alpha)
s = pygame.Surface((self.size2, self.size*2), pygame.SRCALPHA)
pygame.draw.circle(s, color, (self.size, self.size), self.size)
surface.blit(s, (int(self.x - self.size), int(self.y - self.size)))
class ParticleSystem:
def init(self):
self.particles = []
def emit(self, x, y, color, count=10, speed=2, lifetime=20, size=3):
for _ in range(count):
self.particles.append(Particle(x, y, color, speed, lifetime, size))
def update(self):
self.particles = [p for p in self.particles if p.update()]
def draw(self, surface):
for p in self.particles:
p.draw(surface)
==================== 游戏对象 ====================
class Player:
def init(self):
self.x = WIDTH // 2
self.y = HEIGHT // 2
self.radius = 18
self.base_speed = 5
self.speed = self.base_speed
self.max_hp = 100
self.hp = self.max_hp
self.armor = 0
self.level = 1
self.exp = 0
self.exp_to_level = 30
self.attack_cd = 30
self.attack_timer = 0
self.bullet_speed = 10
self.bullet_damage = 25
self.bullet_count = 1
self.bullet_pierce = 0
self.weapon_type = "normal"
self.alive = True
self.invincible_timer = 0
self.dash_cooldown = 0
self.dash_duration = 0
self.dash_direction = (0, 0)
def move(self, keys):
if not self.alive:
return
dx, dy = 0, 0
if keys[pygame.K_w] or keys[pygame.K_UP]:
dy -= 1
if keys[pygame.K_s] or keys[pygame.K_DOWN]:
dy += 1
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
dx -= 1
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
dx += 1
冲刺状态处理优化:冲刺时仍保留方向输入(可选)
if self.dash_duration > 0:
self.dash_duration -= 1
# 如果有方向输入,更新冲刺方向(让冲刺更可控)
if dx != 0 or dy != 0:
self.dash_direction = normalize(dx, dy)
self.x += self.dash_direction[0] * 15
self.y += self.dash_direction[1] * 15
else:
if dx != 0 or dy != 0:
dx, dy = normalize(dx, dy)
self.x += dx * self.speed
self.y += dy * self.speed
边界检测(确保玩家不出屏)
self.x = max(self.radius, min(WIDTH - self.radius, self.x))
self.y = max(self.radius, min(HEIGHT - self.radius, self.y))
if self.dash_cooldown > 0:
self.dash_cooldown -= 1
def dash(self):
if self.dash_cooldown <= 0 and self.dash_duration <= 0 and self.alive:
keys = pygame.key.get_pressed()
dx, dy = 0, 0
if keys[pygame.K_w] or keys[pygame.K_UP]: dy -= 1
if keys[pygame.K_s] or keys[pygame.K_DOWN]: dy += 1
if keys[pygame.K_a] or keys[pygame.K_LEFT]: dx -= 1
if keys[pygame.K_d] or keys[pygame.K_RIGHT]: dx += 1
if dx != 0 or dy != 0:
dx, dy = normalize(dx, dy)
self.dash_direction = (dx, dy)
self.dash_duration = 8
self.dash_cooldown = 60
else:
self.dash_direction = (0, 1)
self.dash_duration = 8
self.dash_cooldown = 60
def take_damage(self, damage):
if self.invincible_timer > 0 or not self.alive:
return
reduced = damage * (1 - self.armor / 100)
self.hp -= max(1, reduced)
self.invincible_timer = 30
if self.hp <= 0:
self.hp = 0
self.alive = False
sfx_die.play()
def update(self):
if self.invincible_timer > 0:
self.invincible_timer -= 1
if self.attack_timer > 0:
self.attack_timer -= 1
if self.dash_cooldown > 0:
self.dash_cooldown -= 1
def can_attack(self):
return self.attack_timer <= 0 and self.alive
def reset_attack_cooldown(self):
self.attack_timer = self.attack_cd
def draw(self, surface, camera=(0, 0)):
if not self.alive:
return
x, y = int(self.x), int(self.y)
time_ms = pygame.time.get_ticks()
基础光晕(最外层)
glow_radius = self.radius + 6 + int(3 * math.sin(time_ms * 0.01))
glow_surf = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA)
for i in range(3):
r = glow_radius - i * 2
alpha = 40 - i * 10
pygame.draw.circle(glow_surf, (0, 255, 255, max(0, alpha)),
(glow_radius, glow_radius), r)
surface.blit(glow_surf, (x - glow_radius, y - glow_radius))
无敌状态额外光环
if self.invincible_timer > 0:
inv_radius = self.radius + 12
inv_surf = pygame.Surface((inv_radius * 2, inv_radius * 2), pygame.SRCALPHA)
flash = abs(math.sin(time_ms * 0.02)) # 闪烁
color = (255, 215, 0, int(150 * flash)) # 金色
pygame.draw.circle(inv_surf, color, (inv_radius, inv_radius), inv_radius, 3)
surface.blit(inv_surf, (x - inv_radius, y - inv_radius))
冲刺拖尾(简单实现:在冲刺方向后方绘制半透明圆)
if self.dash_duration > 0:
trail_surf = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)
alpha = int(100 * (self.dash_duration / 8))
color = (0, 191, 255, alpha) # 深天蓝
在玩家后方2个位置绘制拖尾
for offset in [0.5, 1.0]:
tx = x - self.dash_direction[0] * (self.speed * 2) * offset
ty = y - self.dash_direction[1] * (self.speed * 2) * offset
pygame.draw.circle(trail_surf, color,
(int(tx - x + self.radius), int(ty - y + self.radius)),
self.radius - 4)
surface.blit(trail_surf, (x - self.radius, y - self.radius))
主体外层(深色边框)
pygame.draw.circle(surface, (0, 50, 150), (x, y), self.radius)
主体内层(亮蓝渐变效果——使用半透明圆叠加)
pygame.draw.circle(surface, (30, 144, 255), (x, y), self.radius - 2)
pygame.draw.circle(surface, (0, 191, 255), (x, y), self.radius - 5)
中心高光
highlight_surf = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)
pygame.draw.circle(highlight_surf, (255, 255, 255, 100),
(self.radius - 4, self.radius - 5), self.radius // 3)
surface.blit(highlight_surf, (x - self.radius, y - self.radius))
能量环(旋转的光点)
ring_radius = self.radius + 2
for i in range(3):
angle = time_ms * 0.003 + i * 2.094 # 120度间隔
px = x + int(ring_radius * math.cos(angle))
py = y + int(ring_radius * math.sin(angle))
pygame.draw.circle(surface, (0, 255, 255), (px, py), 3)
能量环细线
pygame.draw.circle(surface, (0, 255, 255), (x, y), ring_radius, 1)
护盾指示器(头顶小三角)
tri_points = [(x, y - self.radius - 8),
(x - 5, y - self.radius - 2),
(x + 5, y - self.radius - 2)]
pygame.draw.polygon(surface, (0, 255, 255), tri_points)
眼睛
eye = y - 4
眼白
pygame.draw.circle(surface, (255, 255, 255), (x - 5, eye), 4)
pygame.draw.circle(surface, (255, 255, 255), (x + 5, eye), 4)
瞳孔
pygame.draw.circle(surface, (0, 0, 0), (x - 5, eye), 2)
pygame.draw.circle(surface, (0, 0, 0), (x + 5, eye), 2)
高光点
pygame.draw.circle(surface, (255, 255, 255), (x - 6, eye - 1), 1)
pygame.draw.circle(surface, (255, 255, 255), (x + 4, eye - 1), 1)
class Gem:
"""经验宝石"""
def init(self, x, y, value=5):
self.x = x
self.y = y
self.value = value
self.radius = 6
self.collect_speed = 0.5
self.lifetime = 300
self.max_lifetime = 300
self.collected = False
self.float_offset = 0
self.float_speed = random.uniform(0.05, 0.1)
def update(self, player_x, player_y):
self.float_offset += self.float_speed
dist = distance(self.x, self.y, player_x, player_y)
if dist < 100:
dx, dy = normalize(player_x - self.x, player_y - self.y)
speed = 5 + (100 - dist) * 0.1
self.x += dx * speed
self.y += dy * speed
self.lifetime -= 1
return self.lifetime > 0 and not self.collected
def draw(self, surface):
if self.lifetime < 60 and self.lifetime % 10 < 5:
return
y_off = math.sin(self.float_offset) * 3
pos = (int(self.x), int(self.y + y_off))
pygame.draw.circle(surface, YELLOW, pos, self.radius)
pygame.draw.circle(surface, ORANGE, pos, self.radius - 2)
class Monster:
"""敌人基类"""
def init(self, x, y, monster_type="normal"):
self.x = x
self.y = y
self.type = monster_type
self.alive = True
self.speed = 1.8
self.hp = 20
self.max_hp = 20
self.damage = 10
self.radius = 14
self.color = RED
self.exp_value = 5
self.attack_cooldown = 0
self.bullets = []
if monster_type == "fast":
self.speed = 3.5
self.hp = 10
self.max_hp = 10
self.damage = 8
self.radius = 10
self.color = ORANGE
self.exp_value = 8
elif monster_type == "tank":
self.speed = 1.2
self.hp = 60
self.max_hp = 60
self.damage = 20
self.radius = 22
self.color = BLOOD_RED
self.exp_value = 15
elif monster_type == "shooter":
self.speed = 1.2
self.hp = 25
self.max_hp = 25
self.damage = 12
self.radius = 16
self.color = PURPLE
self.exp_value = 12
self.attack_cooldown = 90
def update(self, player_x, player_y, player):
if not self.alive:
return
dx, dy = normalize(player_x - self.x, player_y - self.y)
self.x += dx * self.speed
self.y += dy * self.speed
if self.type == "shooter" and self.attack_cooldown > 0:
self.attack_cooldown -= 1
if self.attack_cooldown <= 0:
self.bullets.append(EnemyBullet(self.x, self.y, player_x, player_y))
self.attack_cooldown = random.randint(60, 120)
for b in self.bullets[:]:
b.update()
if b.x < -50 or b.x > WIDTH+50 or b.y < -50 or b.y > HEIGHT+50:
self.bullets.remove(b)
def take_damage(self, damage):
self.hp -= damage
if self.hp <= 0:
self.alive = False
return True
return False
def draw(self, surface):
if not self.alive:
return
x, y = int(self.x), int(self.y)
time_ms = pygame.time.get_ticks()
===== 阴影 =====
shadow_surf = pygame.Surface((self.radius * 3, self.radius // 2), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surf, (0, 0, 0, 60), shadow_surf.get_rect())
surface.blit(shadow_surf, (x - self.radius * 1.5, y + self.radius // 2))
===== 外发光 =====
glow_radius = self.radius + 4 + int(2 * math.sin(time_ms * 0.01))
glow_surf = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA)
for i in range(3):
r = glow_radius - i * 2
alpha = 30 - i * 8
pygame.draw.circle(glow_surf, (*self.color, max(0, alpha)),
(glow_radius, glow_radius), r)
surface.blit(glow_surf, (x - glow_radius, y - glow_radius))
===== 主体绘制 =====
根据类型绘制不同形状
if self.type == "tank":
坦克型:带尖刺的圆角多边形
spike_count = 8
inner_radius = self.radius - 2
outer_radius = self.radius + 4
points = []
for i in range(spike_count * 2):
angle = math.pi * i / spike_count + time_ms * 0.002
r = outer_radius if i % 2 == 0 else inner_radius
px = x + int(r * math.cos(angle))
py = y + int(r * math.sin(angle))
points.append((px, py))
pygame.draw.polygon(surface, self.color, points)
内部暗色区域
pygame.draw.circle(surface,
(max(0, self.color[0] - 50), max(0, self.color[1] - 50), max(0, self.color[2] - 50)),
(x, y), self.radius - 5)
elif self.type == "fast":
快速型:拉长的身体 + 运动线
angle = time_ms * 0.005 # 旋转方向并不重要,只是装饰
dx = int(4 * math.cos(angle))
dy = int(4 * math.sin(angle))
主体椭圆
ellipse_rect = pygame.Rect(x - self.radius - 3, y - self.radius + 2,
self.radius * 2 + 6, self.radius * 2 - 4)
pygame.draw.ellipse(surface, self.color, ellipse_rect)
运动拖尾线
for i in range(1, 4):
alpha = 150 - i * 30
trail_surf = pygame.Surface((6, 6), pygame.SRCALPHA)
pygame.draw.circle(trail_surf, (*self.color, alpha), (3, 3), 3)
surface.blit(trail_surf, (x - dx * i - 3, y - dy * i - 3))
elif self.type == "shooter":
射击型:主体圆 + 头顶炮管
pygame.draw.circle(surface, self.color, (x, y), self.radius)
炮管(随距离指向玩家的方向?这里简单用时间旋转)
cannon_angle = time_ms * 0.003
cannon_length = self.radius + 6
end_x = x + int(cannon_length * math.cos(cannon_angle))
end_y = y - int(cannon_length * math.sin(cannon_angle))
pygame.draw.line(surface, self.color, (x, y - self.radius // 3), (end_x, end_y), 4)
pygame.draw.circle(surface, (255, 255, 255), (end_x, end_y), 3)
触角(两侧)
for side in [-1, 1]:
ant_angle = cannon_angle + side * 0.6 + math.sin(time_ms * 0.01) * 0.3
ant_x = x + int(self.radius * 0.8 * math.cos(ant_angle))
ant_y = y - int(self.radius * 0.8 * math.sin(ant_angle))
pygame.draw.circle(surface, (255, 255, 255), (ant_x, ant_y), 3)
else: # normal
普通型:光滑圆形 + 内圈高光
pygame.draw.circle(surface, self.color, (x, y), self.radius)
inner_color = tuple(min(255, c + 40) for c in self.color)
pygame.draw.circle(surface, inner_color, (x - 1, y - 2), self.radius - 4)
高光点
highlight_surf = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)
pygame.draw.circle(highlight_surf, (255, 255, 255, 80),
(self.radius - 4, self.radius - 5), self.radius // 3)
surface.blit(highlight_surf, (x - self.radius, y - self.radius))
===== 眼睛(所有类型共用,但快速型眼睛会拉长)=====
eye_dx = self.radius * 0.3
eye_dy = self.radius * 0.4
if self.type == "fast":
快速型眼睛呈椭圆形
for ex, ey in [(x - eye_dx, y - eye_dy), (x + eye_dx, y - eye_dy)]:
pygame.draw.ellipse(surface, (0, 0, 0), (ex - 3, ey - 2, 6, 5))
pygame.draw.ellipse(surface, (255, 255, 255), (ex - 2, ey - 1, 3, 3))
else:
标准眼睛
pygame.draw.circle(surface, (0, 0, 0), (int(x - eye_dx), int(y - eye_dy)), 3)
pygame.draw.circle(surface, (0, 0, 0), (int(x + eye_dx), int(y - eye_dy)), 3)
高光
pygame.draw.circle(surface, (255, 255, 255), (int(x - eye_dx - 1), int(y - eye_dy - 1)), 1)
pygame.draw.circle(surface, (255, 255, 255), (int(x + eye_dx - 1), int(y - eye_dy - 1)), 1)
===== 血条美化 =====
if self.hp < self.max_hp:
bar_width = self.radius * 2
bar_height = 5
bar_x = x - bar_width // 2
bar_y = y - self.radius - 12
血条背景
bg_rect = pygame.Rect(bar_x - 1, bar_y - 1, bar_width + 2, bar_height + 2)
pygame.draw.rect(surface, (20, 20, 20), bg_rect, border_radius=2)
血量填充(渐变色:红->绿)
fill_ratio = self.hp / self.max_hp
fill_width = int(bar_width * fill_ratio)
if fill_width > 0:
fill_color = (
int(255 * (1 - fill_ratio)),
int(255 * fill_ratio),
0
)
fill_rect = pygame.Rect(bar_x, bar_y, fill_width, bar_height)
pygame.draw.rect(surface, fill_color, fill_rect, border_radius=2)
边框
pygame.draw.rect(surface, (255, 255, 255), (bar_x - 1, bar_y - 1, bar_width + 2, bar_height + 2), 1,
border_radius=2)
class EnemyBullet:
"""敌人发射的子弹"""
def init(self, x, y, tx, ty):
self.x = x
self.y = y
speed = 4
dx, dy = normalize(tx - x, ty - y)
self.vx = dx * speed
self.vy = dy * speed
self.radius = 5
self.color = PURPLE
def update(self):
self.x += self.vx
self.y += self.vy
def draw(self, surface):
pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.radius)
class Bullet:
"""玩家子弹"""
def init(self, x, y, tx, ty, speed=10, damage=25, pierce=0):
self.x = x
self.y = y
dx, dy = normalize(tx - x, ty - y)
self.vx = dx * speed
self.vy = dy * speed
self.radius = 5
self.damage = damage
self.pierce = pierce
self.alive = True
self.trail = deque(maxlen=5)
def update(self):
self.trail.append((self.x, self.y))
self.x += self.vx
self.y += self.vy
if self.x < -20 or self.x > WIDTH+20 or self.y < -20 or self.y > HEIGHT+20:
self.alive = False
def draw(self, surface):
for i, pos in enumerate(self.trail):
alpha = (i+1) / len(self.trail) * 0.5
s = pygame.Surface((6,6), pygame.SRCALPHA)
pygame.draw.circle(s, (0, 255, 0, int(100*alpha)), (3,3), 3)
surface.blit(s, (int(pos[0]-3), int(pos[1]-3)))
pygame.draw.circle(surface, GREEN, (int(self.x), int(self.y)), self.radius)
==================== 升级系统 ====================
class UpgradeType(Enum):
MAX_HP = "Max HP +20"
SPEED = "Speed +0.5"
ATTACK_SPEED = "Attack Speed Up"
BULLET_DAMAGE = "Bullet Damage +5"
BULLET_COUNT = "Bullets +1"
BULLET_PIERCE = "Pierce +1"
BULLET_SPEED = "Bullet Speed +2"
ARMOR = "Armor +5"
HEAL = "Heal 30 HP"
UPGRADE_POOL = list(UpgradeType)
class UpgradeChoice:
"""升级选项"""
def init(self, upgrade_type):
self.type = upgrade_type
self.description = upgrade_type.value
def apply(self, player):
if self.type == UpgradeType.MAX_HP:
player.max_hp += 20
player.hp = min(player.hp + 20, player.max_hp)
elif self.type == UpgradeType.SPEED:
player.speed += 0.5
elif self.type == UpgradeType.ATTACK_SPEED:
player.attack_cd = max(8, player.attack_cd - 3)
elif self.type == UpgradeType.BULLET_DAMAGE:
player.bullet_damage += 5
elif self.type == UpgradeType.BULLET_COUNT:
player.bullet_count += 1
elif self.type == UpgradeType.BULLET_PIERCE:
player.bullet_pierce += 1
elif self.type == UpgradeType.BULLET_SPEED:
player.bullet_speed += 2
elif self.type == UpgradeType.ARMOR:
player.armor = min(70, player.armor + 5)
elif self.type == UpgradeType.HEAL:
player.hp = min(player.max_hp, player.hp + 30)
==================== 游戏管理 ====================
class Game:
def init(self):
self.reset()
self.stars = [(random.randint(0, WIDTH), random.randint(0, HEIGHT), random.random() * 2 + 0.5) for _ in range(80)]
self.ambient_particles = []
for _ in range(30):
self.ambient_particles.append({
'x': random.randint(0, WIDTH),
'y': random.randint(0, HEIGHT),
'vx': random.uniform(-0.2, 0.2),
'vy': random.uniform(-0.2, 0.2),
'size': random.randint(2, 4),
'color': (random.randint(100, 200), random.randint(100, 200), random.randint(100, 200))
})
self.kills = 0
self.high_score = 0
self.high_wave = 0
self.load_high_score()
def reset(self):
self.player = Player()
self.monsters = []
self.bullets = []
self.gems = []
self.particles = ParticleSystem()
self.spawn_timer = 0
self.base_spawn_interval = 50
self.spawn_interval = self.base_spawn_interval
self.wave = 1
self.wave_timer = 0
self.score = 0
self.time_elapsed = 0
self.game_state = "playing" # playing, levelup, gameover
self.upgrade_options = []
self.camera_shake = 0
self.kill_combo = 0
self.combo_timer = 0
self.kills = 0
def spawn_monster(self):
x, y = random_edge_position()
r = random.random()
if self.wave >= 3 and r < 0.15:
m_type = "shooter"
elif self.wave >= 2 and r < 0.25:
m_type = "tank"
elif r < 0.4:
m_type = "fast"
else:
m_type = "normal"
self.monsters.append(Monster(x, y, m_type))
def player_attack(self):
if not self.player.can_attack() or not self.monsters:
return
nearest = min(self.monsters, key=lambda m: distance(self.player.x, self.player.y, m.x, m.y))
tx, ty = nearest.x, nearest.y
count = self.player.bullet_count
base_angle = math.atan2(ty - self.player.y, tx - self.player.x)
spread = 0.2
for i in range(count):
if count == 1:
angle = base_angle
else:
angle = base_angle + spread * (i - (count-1)/2)
dx = math.cos(angle)
dy = math.sin(angle)
target_x = self.player.x + dx * 100
target_y = self.player.y + dy * 100
self.bullets.append(Bullet(self.player.x, self.player.y,
target_x, target_y,
speed=self.player.bullet_speed,
damage=self.player.bullet_damage,
pierce=self.player.bullet_pierce))
self.player.reset_attack_cooldown()
sfx_shoot.play()
def check_level_up(self):
if self.player.exp >= self.player.exp_to_level and self.player.alive:
self.player.level += 1
self.player.exp -= self.player.exp_to_level
self.player.exp_to_level = int(self.player.exp_to_level * 1.15)
self.upgrade_options = random.sample(UPGRADE_POOL, min(3, len(UPGRADE_POOL)))
self.upgrade_options = [UpgradeChoice(opt) for opt in self.upgrade_options]
self.game_state = "levelup"
sfx_levelup.play()
self.particles.emit(self.player.x, self.player.y, YELLOW, 30, 4, 30, 5)
def apply_upgrade(self, choice):
choice.apply(self.player)
self.game_state = "playing"
self.player.invincible_timer = 20
def update(self):
if self.game_state == "gameover":
return
keys = pygame.key.get_pressed()
self.player.move(keys)
self.player.update()
if self.game_state == "playing":
self.player_attack()
self.spawn_timer += 1
dynamic_interval = max(10, self.base_spawn_interval - self.wave * 3)
if self.spawn_timer >= dynamic_interval:
self.spawn_monster()
self.spawn_timer = 0
if random.random() < 0.2 * self.wave:
self.spawn_monster()
self.wave_timer += 1
if self.wave_timer >= 60 * 30:
self.wave += 1
self.wave_timer = 0
self.player.hp = min(self.player.max_hp, self.player.hp + 10)
for b in self.bullets[:]:
b.update()
if not b.alive:
self.bullets.remove(b)
continue
hit = False
for m in self.monsters[:]:
if not m.alive:
continue
if distance(b.x, b.y, m.x, m.y) < b.radius + m.radius:
m.take_damage(b.damage)
self.particles.emit(b.x, b.y, WHITE, 5, 2, 10, 2)
sfx_hit.play()
if b.pierce > 0:
b.pierce -= 1
else:
self.bullets.remove(b)
hit = True
if not m.alive:
self.monsters.remove(m)
self.player.exp += m.exp_value
self.score += m.exp_value * 10
self.kills += 1 # 新增
self.kill_combo += 1
self.combo_timer = 60
if random.random() < 0.7:
self.gems.append(Gem(m.x, m.y, m.exp_value))
self.particles.emit(m.x, m.y, m.color, 15, 3, 20, 4)
self.camera_shake = max(self.camera_shake, 3)
break
if hit:
continue
for m in self.monsters:
m.update(self.player.x, self.player.y, self.player)
if self.player.alive and distance(m.x, m.y, self.player.x, self.player.y) < m.radius + self.player.radius:
self.player.take_damage(m.damage)
dx, dy = normalize(self.player.x - m.x, self.player.y - m.y)
self.player.x += dx * 20
self.player.y += dy * 20
self.particles.emit(self.player.x, self.player.y, RED, 8, 2, 15, 3)
self.camera_shake = max(self.camera_shake, 5)
for eb in m.bullets[:]:
if distance(eb.x, eb.y, self.player.x, self.player.y) < eb.radius + self.player.radius:
self.player.take_damage(10)
m.bullets.remove(eb)
self.particles.emit(self.player.x, self.player.y, PURPLE, 5, 2, 10, 2)
self.camera_shake = max(self.camera_shake, 4)
for gem in self.gems[:]:
if not gem.update(self.player.x, self.player.y):
self.gems.remove(gem)
elif distance(gem.x, gem.y, self.player.x, self.player.y) < gem.radius + self.player.radius:
self.player.exp += gem.value
self.score += gem.value * 5
self.gems.remove(gem)
sfx_pickup.play()
self.particles.emit(gem.x, gem.y, YELLOW, 8, 2, 15, 2)
if self.combo_timer > 0:
self.combo_timer -= 1
if self.combo_timer == 0:
self.kill_combo = 0
self.particles.update()
if self.camera_shake > 0:
self.camera_shake *= 0.9
if self.camera_shake < 0.5:
self.camera_shake = 0
self.check_level_up()
if not self.player.alive and self.game_state != "gameover":
self.game_state = "gameover"
self.save_high_score()
self.particles.emit(self.player.x, self.player.y, RED, 50, 5, 40, 6)
self.camera_shake = 10
self.time_elapsed += 1
for p in self.ambient_particles:
p['x'] += p['vx']
p['y'] += p['vy']
if p['x'] < 0: p['x'] = WIDTH
if p['x'] > WIDTH: p['x'] = 0
if p['y'] < 0: p['y'] = HEIGHT
if p['y'] > HEIGHT: p['y'] = 0
def draw(self, surface):
shake_x = random.randint(-int(self.camera_shake), int(self.camera_shake)) if self.camera_shake else 0
shake_y = random.randint(-int(self.camera_shake), int(self.camera_shake)) if self.camera_shake else 0
game_surf = pygame.Surface((WIDTH, HEIGHT))
game_surf.fill(BLACK)
for x in range(0, WIDTH, 40):
pygame.draw.line(game_surf, DARK_GRAY, (x, 0), (x, HEIGHT), 1)
for y in range(0, HEIGHT, 40):
pygame.draw.line(game_surf, DARK_GRAY, (0, y), (WIDTH, y), 1)
for star in self.stars:
x, y, b = star
brightness = int(128 + 127 * math.sin(pygame.time.get_ticks() * 0.001 * b))
color = (brightness, brightness, brightness)
pygame.draw.circle(game_surf, color, (x, y), 1)
for p in self.ambient_particles:
s = pygame.Surface((p['size'] * 2, p['size'] * 2), pygame.SRCALPHA)
pygame.draw.circle(s, (*p['color'], 60), (p['size'], p['size']), p['size'])
game_surf.blit(s, (int(p['x'] - p['size']), int(p['y'] - p['size'])))
for gem in self.gems:
gem.draw(game_surf)
for b in self.bullets:
b.draw(game_surf)
for m in self.monsters:
m.draw(game_surf)
for eb in m.bullets:
eb.draw(game_surf)
self.player.draw(game_surf)
self.particles.draw(game_surf)
screen.blit(game_surf, (shake_x, shake_y))
self.draw_ui(screen)
if self.game_state == "levelup":
self.draw_upgrade_menu(screen)
if self.game_state == "gameover":
self.draw_gameover(screen)
def draw_ui(self, surface):
bar_width = 200
bar_height = 15
bar_x = 20
bar_y = 20
pygame.draw.rect(surface, DARK_GRAY, (bar_x, bar_y, bar_width, bar_height))
hp_ratio = self.player.hp / self.player.max_hp
hp_color = (int(255(1-hp_ratio)), int(255hp_ratio), 0)
pygame.draw.rect(surface, hp_color, (bar_x, bar_y, bar_width * hp_ratio, bar_height))
pygame.draw.rect(surface, WHITE, (bar_x, bar_y, bar_width, bar_height), 2)
exp_ratio = self.player.exp / self.player.exp_to_level
pygame.draw.rect(surface, DARK_GRAY, (bar_x, bar_y + 22, bar_width, 10))
pygame.draw.rect(surface, YELLOW, (bar_x, bar_y + 22, bar_width * exp_ratio, 10))
pygame.draw.rect(surface, WHITE, (bar_x, bar_y + 22, bar_width, 10), 1)
font = get_font(24)
text_hp = font.render(f"HP: {int(self.player.hp)}/{self.player.max_hp}", True, WHITE)
surface.blit(text_hp, (bar_x + 5, bar_y - 2))
font_small = get_font(20)
text_lv = font_small.render(f"Lv.{self.player.level}", True, WHITE)
surface.blit(text_lv, (bar_x, bar_y + 35))
font_score = get_font(24)
text_score = font_score.render(f"Score: {self.score}", True, WHITE)
surface.blit(text_score, (WIDTH - 160, 20))
text_wave = font_score.render(f"Wave: {self.wave}", True, ORANGE)
surface.blit(text_wave, (WIDTH - 160, 50))
if self.kill_combo >= 3:
combo_text = get_font(30).render(f"COMBO x{self.kill_combo}", True, RED)
surface.blit(combo_text, (WIDTH//2 - combo_text.get_width()//2, 80))
cd_ratio = max(0, self.player.dash_cooldown / 60)
dash_size = 40
dash_x = 20
dash_y = HEIGHT - 70
pygame.draw.rect(surface, DARK_GRAY, (dash_x, dash_y, dash_size, dash_size))
pygame.draw.rect(surface, BLUE, (dash_x, dash_y, dash_size * cd_ratio, dash_size))
pygame.draw.rect(surface, WHITE, (dash_x, dash_y, dash_size, dash_size), 2)
dash_label = get_font(16).render("SPACE", True, WHITE)
surface.blit(dash_label, (dash_x + 2, dash_y + 12))
def draw_upgrade_menu(self, surface):
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
surface.blit(overlay, (0, 0))
font_title = get_font(48, bold=True)
font_opt = get_font(26)
font_instruction = get_font(18)
title = font_title.render("LEVEL UP!", True, YELLOW)
surface.blit(title, (WIDTH // 2 - title.get_width() // 2, 100))
box_width = 250
box_height = 80
spacing = 20
total_width = len(self.upgrade_options) * box_width + (len(self.upgrade_options) - 1) * spacing
start_x = WIDTH // 2 - total_width // 2
y = HEIGHT // 2 - box_height // 2
mouse_pos = pygame.mouse.get_pos()
mouse_click = pygame.mouse.get_pressed()
for i, opt in enumerate(self.upgrade_options):
x = start_x + i * (box_width + spacing)
rect = pygame.Rect(x, y, box_width, box_height)
if rect.collidepoint(mouse_pos):
pygame.draw.rect(surface, (50, 50, 50), rect, border_radius=10)
pygame.draw.rect(surface, WHITE, rect, 3, border_radius=10)
if mouse_click[0]:
self.apply_upgrade(opt)
return
else:
pygame.draw.rect(surface, DARK_GRAY, rect, border_radius=10)
pygame.draw.rect(surface, LIGHT_GRAY, rect, 2, border_radius=10)
desc = opt.description
shadow_surf = font_opt.render(desc, True, BLACK)
shadow_rect = shadow_surf.get_rect(center=(x + box_width // 2 + 2, y + box_height // 2 + 2))
surface.blit(shadow_surf, shadow_rect)
text_surf = font_opt.render(desc, True, WHITE)
text_rect = text_surf.get_rect(center=(x + box_width // 2, y + box_height // 2))
surface.blit(text_surf, text_rect)
instruction = font_instruction.render("Click to choose upgrade", True, LIGHT_GRAY)
surface.blit(instruction, (WIDTH // 2 - instruction.get_width() // 2, y + box_height + 20))
def draw_gameover(self, surface):
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0,0,0,200))
surface.blit(overlay, (0,0))
font_big = get_font(72, bold=True)
text_go = font_big.render("GAME OVER", True, RED)
surface.blit(text_go, (WIDTH//2 - text_go.get_width()//2, 150))
font_info = get_font(36)
info_lines = [
f"Level: {self.player.level}",
f"Score: {self.score}",
f"Wave: {self.wave}",
f"Kills: {self.kills}" ]
y = 280
for line in info_lines:
text = font_info.render(line, True, WHITE)
surface.blit(text, (WIDTH//2 - text.get_width()//2, y))
y += 50
font_restart = get_font(28)
最高分显示
high_score_text = font_info.render(f"Best: {self.high_score} (Wave {self.high_wave})", True, YELLOW)
surface.blit(high_score_text, (WIDTH // 2 - high_score_text.get_width() // 2, y))
y += 50
text_restart = font_restart.render("Press R to restart ESC to quit", True, LIGHT_GRAY)
surface.blit(text_restart, (WIDTH//2 - text_restart.get_width()//2, y + 30))
def load_high_score(self):
"""从文件读取最高分"""
try:
with open("highscore.json", "r") as f:
data = json.load(f)
self.high_score = data.get("score", 0)
self.high_wave = data.get("wave", 0)
except (FileNotFoundError, json.JSONDecodeError):
self.high_score = 0
self.high_wave = 0
def save_high_score(self):
"""如果当前分数更高则保存"""
if self.score > self.high_score:
self.high_score = self.score
self.high_wave = self.wave
try:
with open("highscore.json", "w") as f:
json.dump({"score": self.high_score, "wave": self.high_wave}, f)
except Exception as e:
print("Could not save high score:", e)
==================== 主循环 ====================
def main():
game = Game()
running = True
while running:
clock.tick(FPS)
始终处理事件(包括升级界面)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and game.game_state == "playing":
game.player.dash()
if event.key == pygame.K_r and game.game_state == "gameover":
game.reset()
if event.key == pygame.K_ESCAPE:
running = False
if game.game_state == "levelup":
只更新玩家内部计时器,不移动
game.player.update()
仍然更新粒子和环境,保持视觉动态
game.particles.update()
for p in game.ambient_particles:
p['x'] += p['vx']
p['y'] += p['vy']
if p['x'] < 0: p['x'] = WIDTH
if p['x'] > WIDTH: p['x'] = 0
if p['y'] < 0: p['y'] = HEIGHT
if p['y'] > HEIGHT: p['y'] = 0
else:
game.update()
screen.fill(BLACK)
game.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()
if name == "main":
main()
浙公网安备 33010602011771号