ed-game 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ed_game-1.0.0/LICENSE +21 -0
- ed_game-1.0.0/PKG-INFO +18 -0
- ed_game-1.0.0/README.md +6 -0
- ed_game-1.0.0/pyproject.toml +18 -0
- ed_game-1.0.0/setup.cfg +4 -0
- ed_game-1.0.0/src/ed_game/__init__.py +29 -0
- ed_game-1.0.0/src/ed_game/game_1.py +637 -0
- ed_game-1.0.0/src/ed_game.egg-info/PKG-INFO +18 -0
- ed_game-1.0.0/src/ed_game.egg-info/SOURCES.txt +11 -0
- ed_game-1.0.0/src/ed_game.egg-info/dependency_links.txt +1 -0
- ed_game-1.0.0/src/ed_game.egg-info/entry_points.txt +2 -0
- ed_game-1.0.0/src/ed_game.egg-info/requires.txt +1 -0
- ed_game-1.0.0/src/ed_game.egg-info/top_level.txt +1 -0
ed_game-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ed_game-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ed-game
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Earth Defense Game
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: pygame-ce>=2.4
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# 地球防御 · 星辰守卫者
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install earth-defense
|
ed_game-1.0.0/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ed-game"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Earth Defense Game"
|
|
9
|
+
authors = [{name = "Your Name", email = "your@email.com"}]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = {text = "MIT"}
|
|
13
|
+
dependencies = ["pygame-ce>=2.4"]
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["src"]
|
|
17
|
+
[project.scripts]
|
|
18
|
+
ed-game = "ed_game.game_1:run"
|
ed_game-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""地球防御 · 星辰守卫者射击游戏"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
def _check_pygame():
|
|
6
|
+
"""检查 pygame-ce 是否已安装"""
|
|
7
|
+
try:
|
|
8
|
+
import pygame
|
|
9
|
+
return True
|
|
10
|
+
except ImportError:
|
|
11
|
+
print("=" * 50)
|
|
12
|
+
print("❌ 错误:未找到 pygame-ce!")
|
|
13
|
+
print("=" * 50)
|
|
14
|
+
print("请先安装 pygame-ce:")
|
|
15
|
+
print(" pip install pygame-ce")
|
|
16
|
+
print("")
|
|
17
|
+
print("或者使用国内镜像源:")
|
|
18
|
+
print(" pip install pygame-ce -i https://pypi.tuna.tsinghua.edu.cn/simple")
|
|
19
|
+
print("=" * 50)
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
# 模块加载时自动检查
|
|
23
|
+
if not _check_pygame():
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
from .game_1 import run
|
|
27
|
+
|
|
28
|
+
__version__ = "1.0.0"
|
|
29
|
+
__all__ = ["run"]
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import random
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
# 初始化
|
|
8
|
+
pygame.init()
|
|
9
|
+
|
|
10
|
+
# 游戏配置
|
|
11
|
+
WIDTH = 1800
|
|
12
|
+
HEIGHT = 1050
|
|
13
|
+
FPS = 60
|
|
14
|
+
|
|
15
|
+
# 颜色定义
|
|
16
|
+
COLOR_BG = (2, 6, 23)
|
|
17
|
+
COLOR_ENEMY = (255, 58, 111)
|
|
18
|
+
COLOR_ENEMY_ELITE = (255, 136, 68)
|
|
19
|
+
COLOR_ENEMY_BOSS = (180, 50, 255) # 紫色巨型敌人
|
|
20
|
+
COLOR_BULLET = (255, 255, 128)
|
|
21
|
+
COLOR_BULLET_CORE = (255, 204, 68)
|
|
22
|
+
COLOR_PLAYER_MAIN = (62, 255, 250)
|
|
23
|
+
COLOR_PLAYER_CORE = (0, 204, 255)
|
|
24
|
+
COLOR_PLAYER_FIRE = (255, 102, 0)
|
|
25
|
+
COLOR_TEXT_SCORE = (0, 255, 255)
|
|
26
|
+
COLOR_TEXT_LIVES = (255, 51, 102)
|
|
27
|
+
COLOR_TEXT_STATUS = (255, 215, 0)
|
|
28
|
+
COLOR_TEXT_GAMEOVER = (255, 68, 102)
|
|
29
|
+
COLOR_WHITE = (255, 255, 255)
|
|
30
|
+
COLOR_GOLD = (255, 215, 0)
|
|
31
|
+
COLOR_HEART = (255, 80, 120)
|
|
32
|
+
|
|
33
|
+
# 速度参数
|
|
34
|
+
BASE_ENEMY_SPEED = 0.31
|
|
35
|
+
MAX_ENEMY_SPEED = 0.65
|
|
36
|
+
ELITE_SPEED_FACTOR = 0.84
|
|
37
|
+
BOSS_SPEED_FACTOR = 0.2 # 慢5倍 = 0.2倍速
|
|
38
|
+
|
|
39
|
+
BULLET_SPEED = -7.2
|
|
40
|
+
PLAYER_SPEED = 6.2
|
|
41
|
+
|
|
42
|
+
PLAYER_RADIUS = 15
|
|
43
|
+
PLAYER_START_POS = (WIDTH // 2, HEIGHT - 60)
|
|
44
|
+
|
|
45
|
+
INVINCIBLE_DURATION = 55
|
|
46
|
+
SHOOT_COOLDOWN_FRAMES = 9
|
|
47
|
+
|
|
48
|
+
BASE_SPAWN_DELAY = 48
|
|
49
|
+
STAR_COUNT = 220
|
|
50
|
+
|
|
51
|
+
# 巨型敌人参数
|
|
52
|
+
BOSS_SIZE_MULTIPLIER = 4 # 大4倍
|
|
53
|
+
BOSS_HP_MULTIPLIER = 16 # 血量16倍
|
|
54
|
+
BOSS_SPEED_MULTIPLIER = 0.2 # 速度慢5倍
|
|
55
|
+
BOSS_SPAWN_INTERVAL = 20 * 1000
|
|
56
|
+
|
|
57
|
+
# 全局状态
|
|
58
|
+
game_active = True
|
|
59
|
+
score = 0
|
|
60
|
+
lives = 3
|
|
61
|
+
invincible = 0
|
|
62
|
+
wave_level = 1
|
|
63
|
+
enemy_speed_current = BASE_ENEMY_SPEED
|
|
64
|
+
spawn_delay_current = BASE_SPAWN_DELAY
|
|
65
|
+
|
|
66
|
+
player_x, player_y = PLAYER_START_POS
|
|
67
|
+
left_pressed = False
|
|
68
|
+
right_pressed = False
|
|
69
|
+
shoot_pressed = False
|
|
70
|
+
shoot_cooldown = 0
|
|
71
|
+
|
|
72
|
+
bullets = []
|
|
73
|
+
enemies = []
|
|
74
|
+
powerups = []
|
|
75
|
+
explosions = []
|
|
76
|
+
particles = []
|
|
77
|
+
|
|
78
|
+
stars = []
|
|
79
|
+
status_message = "极慢模式 | 敌人每秒仅移动5毫米"
|
|
80
|
+
status_msg_timer = 0
|
|
81
|
+
enemy_spawn_timer = 20
|
|
82
|
+
|
|
83
|
+
# 巨型敌人计时器
|
|
84
|
+
last_boss_spawn_time = 0
|
|
85
|
+
boss_spawn_timer = 0
|
|
86
|
+
|
|
87
|
+
# Pygame 相关
|
|
88
|
+
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
|
89
|
+
pygame.display.set_caption("星辰守卫者 · 极慢漂移版")
|
|
90
|
+
clock = pygame.time.Clock()
|
|
91
|
+
|
|
92
|
+
# 加载字体(支持中文)
|
|
93
|
+
try:
|
|
94
|
+
font_large = pygame.font.Font("C:/Windows/Fonts/simhei.ttf", 48)
|
|
95
|
+
font_medium = pygame.font.Font("C:/Windows/Fonts/simhei.ttf", 28)
|
|
96
|
+
font_small = pygame.font.Font("C:/Windows/Fonts/simhei.ttf", 20)
|
|
97
|
+
font_tiny = pygame.font.Font("C:/Windows/Fonts/simhei.ttf", 14)
|
|
98
|
+
except:
|
|
99
|
+
font_large = pygame.font.SysFont("simhei", 48)
|
|
100
|
+
font_medium = pygame.font.SysFont("simhei", 28)
|
|
101
|
+
font_small = pygame.font.SysFont("simhei", 20)
|
|
102
|
+
font_tiny = pygame.font.SysFont("simhei", 14)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def draw_text(text, x, y, color, center=False, font=None):
|
|
106
|
+
if font is None:
|
|
107
|
+
font = font_medium
|
|
108
|
+
surface = font.render(text, True, color)
|
|
109
|
+
rect = surface.get_rect()
|
|
110
|
+
if center:
|
|
111
|
+
rect.center = (x, y)
|
|
112
|
+
else:
|
|
113
|
+
rect.topleft = (x, y)
|
|
114
|
+
screen.blit(surface, rect)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def draw_circle(x, y, radius, color, width=0):
|
|
118
|
+
pygame.draw.circle(screen, color, (int(x), int(y)), int(radius), width)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def draw_star(cx, cy, size):
|
|
122
|
+
"""画五角星"""
|
|
123
|
+
points = []
|
|
124
|
+
outer_r = size
|
|
125
|
+
inner_r = size * 0.4
|
|
126
|
+
for i in range(5):
|
|
127
|
+
angle = math.radians(90 - i * 72)
|
|
128
|
+
x = cx + outer_r * math.cos(angle)
|
|
129
|
+
y = cy - outer_r * math.sin(angle)
|
|
130
|
+
points.append((x, y))
|
|
131
|
+
angle = math.radians(90 - (i + 0.5) * 72)
|
|
132
|
+
x = cx + inner_r * math.cos(angle)
|
|
133
|
+
y = cy - inner_r * math.sin(angle)
|
|
134
|
+
points.append((x, y))
|
|
135
|
+
pygame.draw.polygon(screen, COLOR_GOLD, points)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def draw_heart(cx, cy, size):
|
|
139
|
+
"""画心形"""
|
|
140
|
+
points = []
|
|
141
|
+
scale = size / 15
|
|
142
|
+
for t in range(0, 360, 10):
|
|
143
|
+
rad = math.radians(t)
|
|
144
|
+
x = 16 * math.sin(rad) ** 3
|
|
145
|
+
y = 13 * math.cos(rad) - 5 * math.cos(2*rad) - 2 * math.cos(3*rad) - math.cos(4*rad)
|
|
146
|
+
points.append((cx + x * scale, cy - y * scale))
|
|
147
|
+
if len(points) > 2:
|
|
148
|
+
pygame.draw.polygon(screen, COLOR_HEART, points)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def draw_crown(cx, cy, size):
|
|
152
|
+
"""画王冠(巨型敌人标记)"""
|
|
153
|
+
points = [
|
|
154
|
+
(cx - size, cy + size),
|
|
155
|
+
(cx - size*0.5, cy - size),
|
|
156
|
+
(cx, cy - size*0.5),
|
|
157
|
+
(cx + size*0.5, cy - size),
|
|
158
|
+
(cx + size, cy + size),
|
|
159
|
+
(cx + size*0.6, cy + size*0.3),
|
|
160
|
+
(cx, cy),
|
|
161
|
+
(cx - size*0.6, cy + size*0.3),
|
|
162
|
+
]
|
|
163
|
+
pygame.draw.polygon(screen, COLOR_GOLD, points)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def reset_game():
|
|
167
|
+
global game_active, score, lives, invincible, wave_level
|
|
168
|
+
global enemy_speed_current, spawn_delay_current
|
|
169
|
+
global player_x, player_y, bullets, enemies, powerups, explosions, particles
|
|
170
|
+
global shoot_cooldown, status_message, status_msg_timer, enemy_spawn_timer
|
|
171
|
+
global last_boss_spawn_time, boss_spawn_timer
|
|
172
|
+
|
|
173
|
+
game_active = True
|
|
174
|
+
score = 0
|
|
175
|
+
lives = 3
|
|
176
|
+
invincible = 0
|
|
177
|
+
wave_level = 1
|
|
178
|
+
enemy_speed_current = BASE_ENEMY_SPEED
|
|
179
|
+
spawn_delay_current = BASE_SPAWN_DELAY
|
|
180
|
+
|
|
181
|
+
player_x, player_y = PLAYER_START_POS
|
|
182
|
+
|
|
183
|
+
bullets.clear()
|
|
184
|
+
enemies.clear()
|
|
185
|
+
powerups.clear()
|
|
186
|
+
explosions.clear()
|
|
187
|
+
particles.clear()
|
|
188
|
+
|
|
189
|
+
shoot_cooldown = 0
|
|
190
|
+
enemy_spawn_timer = 20
|
|
191
|
+
|
|
192
|
+
last_boss_spawn_time = pygame.time.get_ticks()
|
|
193
|
+
boss_spawn_timer = 0
|
|
194
|
+
|
|
195
|
+
status_message = "重启极慢领域 | 巨型BOSS每分钟降临一次"
|
|
196
|
+
status_msg_timer = 120
|
|
197
|
+
|
|
198
|
+
for _ in range(3):
|
|
199
|
+
spawn_enemy()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def add_explosion(px, py, is_boss=False):
|
|
203
|
+
explosions.append({
|
|
204
|
+
'x': px, 'y': py,
|
|
205
|
+
'life': 6 if not is_boss else 20,
|
|
206
|
+
'max_life': 6 if not is_boss else 20,
|
|
207
|
+
'radius': 12 if not is_boss else 30
|
|
208
|
+
})
|
|
209
|
+
particle_count = 8 if not is_boss else 30
|
|
210
|
+
for _ in range(particle_count):
|
|
211
|
+
particles.append({
|
|
212
|
+
'x': px, 'y': py,
|
|
213
|
+
'vx': (random.random() - 0.5) * 3.5,
|
|
214
|
+
'vy': (random.random() - 0.5) * 3 - 1,
|
|
215
|
+
'life': 6 if not is_boss else 20,
|
|
216
|
+
'size': random.random() * 3 + 1.5,
|
|
217
|
+
'color': (random.randint(200, 255), random.randint(100, 180), random.randint(50, 150))
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def spawn_powerup(x, y, is_boss=False):
|
|
222
|
+
# BOSS 必掉多个道具
|
|
223
|
+
if is_boss:
|
|
224
|
+
for _ in range(3):
|
|
225
|
+
if random.random() < 0.8:
|
|
226
|
+
powerups.append({
|
|
227
|
+
'x': x + random.randint(-20, 20),
|
|
228
|
+
'y': y,
|
|
229
|
+
'radius': 9,
|
|
230
|
+
'type': 'heart',
|
|
231
|
+
'speed_y': 1.2
|
|
232
|
+
})
|
|
233
|
+
else:
|
|
234
|
+
if random.random() < 0.12:
|
|
235
|
+
powerups.append({
|
|
236
|
+
'x': x, 'y': y,
|
|
237
|
+
'radius': 9,
|
|
238
|
+
'type': 'heart',
|
|
239
|
+
'speed_y': 1.2
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def spawn_enemy(is_boss=False):
|
|
244
|
+
if is_boss:
|
|
245
|
+
radius = 12 * BOSS_SIZE_MULTIPLIER # 48
|
|
246
|
+
x = random.randint(radius, WIDTH - radius)
|
|
247
|
+
speed = enemy_speed_current * BOSS_SPEED_MULTIPLIER
|
|
248
|
+
hp = 50 # 100倍血量
|
|
249
|
+
score_val = 200
|
|
250
|
+
color = COLOR_ENEMY_BOSS
|
|
251
|
+
|
|
252
|
+
enemies.append({
|
|
253
|
+
'x': x, 'y': -radius,
|
|
254
|
+
'radius': radius,
|
|
255
|
+
'speed_y': speed,
|
|
256
|
+
'color': color,
|
|
257
|
+
'hp': hp,
|
|
258
|
+
'max_hp': hp,
|
|
259
|
+
'score_value': score_val,
|
|
260
|
+
'elite': False,
|
|
261
|
+
'is_boss': True
|
|
262
|
+
})
|
|
263
|
+
status_message = "⚠️ 巨型主宰者降临!⚠️"
|
|
264
|
+
status_msg_timer = 90
|
|
265
|
+
else:
|
|
266
|
+
radius = 12
|
|
267
|
+
x = random.randint(radius, WIDTH - radius)
|
|
268
|
+
speed = enemy_speed_current
|
|
269
|
+
hp = 1
|
|
270
|
+
score_val = 10
|
|
271
|
+
is_elite = (wave_level >= 2 and random.random() < 0.12)
|
|
272
|
+
color = COLOR_ENEMY_ELITE if is_elite else COLOR_ENEMY
|
|
273
|
+
|
|
274
|
+
if is_elite:
|
|
275
|
+
radius = 16
|
|
276
|
+
hp = 2
|
|
277
|
+
score_val = 30
|
|
278
|
+
speed = speed * ELITE_SPEED_FACTOR
|
|
279
|
+
if speed < 0.22:
|
|
280
|
+
speed = 0.22
|
|
281
|
+
|
|
282
|
+
enemies.append({
|
|
283
|
+
'x': x, 'y': -radius,
|
|
284
|
+
'radius': radius,
|
|
285
|
+
'speed_y': speed,
|
|
286
|
+
'color': color,
|
|
287
|
+
'hp': hp,
|
|
288
|
+
'max_hp': hp,
|
|
289
|
+
'score_value': score_val,
|
|
290
|
+
'elite': is_elite,
|
|
291
|
+
'is_boss': False
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def update_difficulty():
|
|
296
|
+
global wave_level, enemy_speed_current, spawn_delay_current, status_message, status_msg_timer
|
|
297
|
+
raw_wave = 1 + score // 550
|
|
298
|
+
raw_wave = min(raw_wave, 9)
|
|
299
|
+
if raw_wave > wave_level:
|
|
300
|
+
wave_level = raw_wave
|
|
301
|
+
new_speed = BASE_ENEMY_SPEED + (wave_level - 1) * 0.035
|
|
302
|
+
if new_speed > MAX_ENEMY_SPEED:
|
|
303
|
+
new_speed = MAX_ENEMY_SPEED
|
|
304
|
+
enemy_speed_current = new_speed
|
|
305
|
+
spawn_delay_current = max(32, 52 - int(wave_level * 1.5))
|
|
306
|
+
speed_mm = enemy_speed_current * 60 * 0.2646
|
|
307
|
+
status_message = f"烈度提升 Lv.{wave_level} | 速度 ≈ {speed_mm:.1f} mm/秒"
|
|
308
|
+
status_msg_timer = 120
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def shoot_bullet():
|
|
312
|
+
global shoot_cooldown
|
|
313
|
+
if not game_active:
|
|
314
|
+
return
|
|
315
|
+
bullets.append({
|
|
316
|
+
'x': player_x, 'y': player_y - 12,
|
|
317
|
+
'radius': 5,
|
|
318
|
+
'speed_y': BULLET_SPEED,
|
|
319
|
+
'damage': 1
|
|
320
|
+
})
|
|
321
|
+
if score >= 600 and random.random() < 0.45:
|
|
322
|
+
bullets.append({
|
|
323
|
+
'x': player_x - 7, 'y': player_y - 8,
|
|
324
|
+
'radius': 4.2, 'speed_y': BULLET_SPEED, 'damage': 1
|
|
325
|
+
})
|
|
326
|
+
bullets.append({
|
|
327
|
+
'x': player_x + 7, 'y': player_y - 8,
|
|
328
|
+
'radius': 4.2, 'speed_y': BULLET_SPEED, 'damage': 1
|
|
329
|
+
})
|
|
330
|
+
shoot_cooldown = SHOOT_COOLDOWN_FRAMES
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def update():
|
|
334
|
+
global player_x, player_y, invincible, shoot_cooldown
|
|
335
|
+
global game_active, lives, score, status_message, status_msg_timer, enemy_spawn_timer
|
|
336
|
+
global last_boss_spawn_time, boss_spawn_timer
|
|
337
|
+
|
|
338
|
+
if not game_active:
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
if invincible > 0:
|
|
342
|
+
invincible -= 1
|
|
343
|
+
if shoot_cooldown > 0:
|
|
344
|
+
shoot_cooldown -= 1
|
|
345
|
+
|
|
346
|
+
if left_pressed:
|
|
347
|
+
player_x -= PLAYER_SPEED
|
|
348
|
+
if right_pressed:
|
|
349
|
+
player_x += PLAYER_SPEED
|
|
350
|
+
player_x = max(PLAYER_RADIUS + 5, min(WIDTH - PLAYER_RADIUS - 5, player_x))
|
|
351
|
+
|
|
352
|
+
if shoot_pressed and shoot_cooldown == 0:
|
|
353
|
+
shoot_bullet()
|
|
354
|
+
|
|
355
|
+
# 检查巨型敌人生成(1分钟一次)
|
|
356
|
+
current_time = pygame.time.get_ticks()
|
|
357
|
+
if game_active and current_time - last_boss_spawn_time >= BOSS_SPAWN_INTERVAL:
|
|
358
|
+
spawn_enemy(is_boss=True)
|
|
359
|
+
last_boss_spawn_time = current_time
|
|
360
|
+
status_message = "👑 巨型主宰者出现! 血量100倍,速度极慢! 👑"
|
|
361
|
+
status_msg_timer = 120
|
|
362
|
+
|
|
363
|
+
# 子弹更新
|
|
364
|
+
for b in bullets[:]:
|
|
365
|
+
b['y'] += b['speed_y']
|
|
366
|
+
if b['y'] + b['radius'] < 0 or b['y'] - b['radius'] > HEIGHT:
|
|
367
|
+
bullets.remove(b)
|
|
368
|
+
|
|
369
|
+
# 敌人移动
|
|
370
|
+
for e in enemies[:]:
|
|
371
|
+
e['y'] += e['speed_y']
|
|
372
|
+
if e['y'] + e['radius'] > HEIGHT + 40:
|
|
373
|
+
enemies.remove(e)
|
|
374
|
+
if game_active and invincible <= 0 and not e.get('is_boss', False):
|
|
375
|
+
lives -= 1
|
|
376
|
+
if lives <= 0:
|
|
377
|
+
lives = 0
|
|
378
|
+
game_active = False
|
|
379
|
+
status_message = "战舰沉没 | 极慢世界也需谨慎"
|
|
380
|
+
return
|
|
381
|
+
else:
|
|
382
|
+
invincible = INVINCIBLE_DURATION
|
|
383
|
+
add_explosion(player_x, player_y - 8)
|
|
384
|
+
status_message = "护盾紧急启动!"
|
|
385
|
+
status_msg_timer = 60
|
|
386
|
+
|
|
387
|
+
# 子弹碰撞
|
|
388
|
+
for b in bullets[:]:
|
|
389
|
+
hit = None
|
|
390
|
+
for e in enemies:
|
|
391
|
+
if math.hypot(b['x'] - e['x'], b['y'] - e['y']) < b['radius'] + e['radius']:
|
|
392
|
+
hit = e
|
|
393
|
+
break
|
|
394
|
+
if hit:
|
|
395
|
+
bullets.remove(b)
|
|
396
|
+
hit['hp'] -= b['damage']
|
|
397
|
+
if hit['hp'] <= 0:
|
|
398
|
+
score += hit['score_value']
|
|
399
|
+
update_difficulty()
|
|
400
|
+
add_explosion(hit['x'], hit['y'], is_boss=hit.get('is_boss', False))
|
|
401
|
+
spawn_powerup(hit['x'], hit['y'], is_boss=hit.get('is_boss', False))
|
|
402
|
+
enemies.remove(hit)
|
|
403
|
+
else:
|
|
404
|
+
particle_mult = 4 if not hit.get('is_boss', False) else 12
|
|
405
|
+
for _ in range(particle_mult):
|
|
406
|
+
particles.append({
|
|
407
|
+
'x': hit['x'], 'y': hit['y'],
|
|
408
|
+
'vx': (random.random()-0.5)*2.5,
|
|
409
|
+
'vy': (random.random()-0.5)*2.5,
|
|
410
|
+
'life': 8,
|
|
411
|
+
'size': 2 if not hit.get('is_boss', False) else 4,
|
|
412
|
+
'color': (255, 170, 51)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
# 玩家碰撞
|
|
416
|
+
for e in enemies[:]:
|
|
417
|
+
if math.hypot(player_x - e['x'], player_y - e['y']) < PLAYER_RADIUS + e['radius']:
|
|
418
|
+
if invincible <= 0 and game_active:
|
|
419
|
+
lives -= 1
|
|
420
|
+
if lives <= 0:
|
|
421
|
+
lives = 0
|
|
422
|
+
game_active = False
|
|
423
|
+
status_message = "极慢败北 | 按 R 重启"
|
|
424
|
+
add_explosion(player_x, player_y)
|
|
425
|
+
return
|
|
426
|
+
else:
|
|
427
|
+
invincible = INVINCIBLE_DURATION
|
|
428
|
+
add_explosion(player_x, player_y - 6)
|
|
429
|
+
status_message = "撞击伤害!短暂无敌"
|
|
430
|
+
status_msg_timer = 60
|
|
431
|
+
add_explosion(e['x'], e['y'])
|
|
432
|
+
enemies.remove(e)
|
|
433
|
+
# 道具拾取
|
|
434
|
+
for p in powerups[:]:
|
|
435
|
+
p['y'] += p['speed_y']
|
|
436
|
+
|
|
437
|
+
# 碰到下边界 → 自动拾取
|
|
438
|
+
if p['y'] + p['radius'] > HEIGHT + 30:
|
|
439
|
+
if p['type'] == 'heart':
|
|
440
|
+
if lives < 5:
|
|
441
|
+
lives += 1
|
|
442
|
+
status_message = "自动接住 +1 耐久"
|
|
443
|
+
else:
|
|
444
|
+
score += 25
|
|
445
|
+
status_message = "自动接住 +25 积分"
|
|
446
|
+
status_msg_timer = 60
|
|
447
|
+
add_explosion(p['x'], p['y'])
|
|
448
|
+
powerups.remove(p)
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
# 玩家主动拾取
|
|
452
|
+
if math.hypot(player_x - p['x'], player_y - p['y']) < PLAYER_RADIUS + p['radius']:
|
|
453
|
+
if p['type'] == 'heart':
|
|
454
|
+
if lives < 5:
|
|
455
|
+
lives += 1
|
|
456
|
+
status_message = "维修核心 +1 耐久"
|
|
457
|
+
status_msg_timer = 60
|
|
458
|
+
else:
|
|
459
|
+
score += 25
|
|
460
|
+
status_message = "能量满载 +25 积分"
|
|
461
|
+
status_msg_timer = 60
|
|
462
|
+
add_explosion(p['x'], p['y'])
|
|
463
|
+
powerups.remove(p)
|
|
464
|
+
|
|
465
|
+
# 敌人生成
|
|
466
|
+
if enemy_spawn_timer <= 0:
|
|
467
|
+
count = 2 if (wave_level >= 4 and random.random() < 0.3) else 1
|
|
468
|
+
for _ in range(count):
|
|
469
|
+
spawn_enemy()
|
|
470
|
+
enemy_spawn_timer = max(34, spawn_delay_current - int(wave_level * 1.2))
|
|
471
|
+
else:
|
|
472
|
+
enemy_spawn_timer -= 1
|
|
473
|
+
# 额外精英
|
|
474
|
+
if wave_level >= 2 and random.random() < 0.008 and len(enemies) < 20:
|
|
475
|
+
spawn_enemy()
|
|
476
|
+
|
|
477
|
+
# 粒子/爆炸更新
|
|
478
|
+
for p in particles[:]:
|
|
479
|
+
p['x'] += p['vx']
|
|
480
|
+
p['y'] += p['vy']
|
|
481
|
+
p['life'] -= 1
|
|
482
|
+
if p['life'] <= 0:
|
|
483
|
+
particles.remove(p)
|
|
484
|
+
for ex in explosions[:]:
|
|
485
|
+
ex['life'] -= 1
|
|
486
|
+
if ex['life'] <= 0:
|
|
487
|
+
explosions.remove(ex)
|
|
488
|
+
|
|
489
|
+
if status_msg_timer > 0:
|
|
490
|
+
status_msg_timer -= 1
|
|
491
|
+
if status_msg_timer == 0 and game_active:
|
|
492
|
+
status_message = "极慢星河 · 悠闲射击"
|
|
493
|
+
|
|
494
|
+
if lives <= 0:
|
|
495
|
+
game_active = False
|
|
496
|
+
status_message = "极慢世界终结 | 按 R 继续"
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def draw():
|
|
500
|
+
screen.fill(COLOR_BG)
|
|
501
|
+
|
|
502
|
+
# 星星
|
|
503
|
+
for s in stars:
|
|
504
|
+
s['y'] += s['speed'] * 0.3
|
|
505
|
+
if s['y'] > HEIGHT:
|
|
506
|
+
s['y'] = 0
|
|
507
|
+
draw_circle(s['x'], s['y'], s['radius'], s['color'])
|
|
508
|
+
|
|
509
|
+
# 敌人
|
|
510
|
+
for e in enemies:
|
|
511
|
+
draw_circle(e['x'], e['y'], e['radius']-1, e['color'])
|
|
512
|
+
draw_circle(e['x'], e['y'], e['radius']-4, (170, 34, 34))
|
|
513
|
+
|
|
514
|
+
if e.get('is_boss', False):
|
|
515
|
+
# BOSS 画王冠
|
|
516
|
+
draw_crown(e['x'], e['y'] - e['radius']//2, 12)
|
|
517
|
+
# BOSS 血条
|
|
518
|
+
bar_width = 80 * (e['hp'] / e['max_hp'])
|
|
519
|
+
pygame.draw.rect(screen, (255, 50, 50), (e['x']-40, e['y']-e['radius']-8, 80, 6))
|
|
520
|
+
pygame.draw.rect(screen, (0, 255, 0), (e['x']-40, e['y']-e['radius']-8, bar_width, 6))
|
|
521
|
+
elif e['elite']:
|
|
522
|
+
# 精英画星星
|
|
523
|
+
draw_star(e['x'], e['y'] - 8, 8)
|
|
524
|
+
if e['hp'] > 1:
|
|
525
|
+
bar_width = 20 * (e['hp'] / 2)
|
|
526
|
+
pygame.draw.rect(screen, (255, 170, 85), (e['x']-10, e['y']-e['radius']-4, bar_width, 4))
|
|
527
|
+
|
|
528
|
+
# 道具 - 画心形
|
|
529
|
+
for p in powerups:
|
|
530
|
+
draw_circle(p['x'], p['y'], p['radius']-2, (255, 94, 126))
|
|
531
|
+
draw_heart(p['x'], p['y'] + 2, 10)
|
|
532
|
+
|
|
533
|
+
# 子弹
|
|
534
|
+
for b in bullets:
|
|
535
|
+
draw_circle(b['x'], b['y'], b['radius']-1, COLOR_BULLET)
|
|
536
|
+
draw_circle(b['x'], b['y'], b['radius']-2, COLOR_BULLET_CORE)
|
|
537
|
+
|
|
538
|
+
# 玩家飞船
|
|
539
|
+
flash = (invincible > 0 and (pygame.time.get_ticks() // 100 % 2 == 0))
|
|
540
|
+
if not flash or not game_active:
|
|
541
|
+
draw_circle(player_x, player_y, 16, COLOR_PLAYER_MAIN)
|
|
542
|
+
draw_circle(player_x, player_y-4, 6, COLOR_PLAYER_CORE)
|
|
543
|
+
draw_circle(player_x-3, player_y-6, 2, COLOR_WHITE)
|
|
544
|
+
draw_circle(player_x+3, player_y-6, 2, COLOR_WHITE)
|
|
545
|
+
draw_circle(player_x, player_y+18, 6, COLOR_PLAYER_FIRE)
|
|
546
|
+
draw_circle(player_x-3, player_y+16, 4, COLOR_PLAYER_FIRE)
|
|
547
|
+
draw_circle(player_x+3, player_y+16, 4, COLOR_PLAYER_FIRE)
|
|
548
|
+
draw_circle(player_x, player_y, 19, (0, 255, 255, 100), width=1)
|
|
549
|
+
|
|
550
|
+
# 爆炸与粒子
|
|
551
|
+
for ex in explosions:
|
|
552
|
+
ratio = ex['life'] / ex['max_life']
|
|
553
|
+
r = ex['radius'] * (0.5 + ratio * 0.5)
|
|
554
|
+
draw_circle(ex['x'], ex['y'], r, (255, 100, 30))
|
|
555
|
+
draw_circle(ex['x'], ex['y'], r*0.6, (255, 200, 50))
|
|
556
|
+
for p in particles:
|
|
557
|
+
draw_circle(p['x'], p['y'], p['size'], p['color'])
|
|
558
|
+
|
|
559
|
+
# UI 文字
|
|
560
|
+
draw_text(f"积分: {score}", 30, 20, COLOR_TEXT_SCORE, font=font_medium)
|
|
561
|
+
draw_text(f"战舰耐久: {lives}", WIDTH-220, 20, COLOR_TEXT_LIVES, font=font_medium)
|
|
562
|
+
draw_text(f"烈度: {min(10, wave_level)}", WIDTH//2-50, 20, COLOR_TEXT_SCORE, font=font_medium)
|
|
563
|
+
draw_text(status_message, WIDTH//2, HEIGHT-30, COLOR_TEXT_STATUS, center=True, font=font_small)
|
|
564
|
+
|
|
565
|
+
# 显示BOSS倒计时
|
|
566
|
+
current_time = pygame.time.get_ticks()
|
|
567
|
+
time_since_boss = current_time - last_boss_spawn_time
|
|
568
|
+
time_to_next_boss = max(0, BOSS_SPAWN_INTERVAL - time_since_boss) // 1000
|
|
569
|
+
draw_text(f"下一巨型BOSS: {time_to_next_boss}秒", WIDTH//2, HEIGHT-55, (200, 200, 100), center=True, font=font_tiny)
|
|
570
|
+
|
|
571
|
+
draw_text("A/D ←→ | 射击 空格/K/↑ | R 重启", WIDTH//2, HEIGHT-10, (127, 140, 141), center=True, font=font_tiny)
|
|
572
|
+
|
|
573
|
+
if not game_active:
|
|
574
|
+
draw_text("GAME OVER", WIDTH//2, HEIGHT//2-30, COLOR_TEXT_GAMEOVER, center=True, font=font_large)
|
|
575
|
+
draw_text("按下 [ R ] 重新开始", WIDTH//2, HEIGHT//2+20, (187, 221, 255), center=True, font=font_small)
|
|
576
|
+
|
|
577
|
+
pygame.display.flip()
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def handle_events():
|
|
581
|
+
global left_pressed, right_pressed, shoot_pressed, game_active
|
|
582
|
+
|
|
583
|
+
for event in pygame.event.get():
|
|
584
|
+
if event.type == pygame.QUIT:
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
elif event.type == pygame.KEYDOWN:
|
|
588
|
+
if event.key == pygame.K_LEFT or event.key == pygame.K_a:
|
|
589
|
+
left_pressed = True
|
|
590
|
+
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
|
|
591
|
+
right_pressed = True
|
|
592
|
+
elif event.key == pygame.K_SPACE or event.key == pygame.K_UP or event.key == pygame.K_k:
|
|
593
|
+
shoot_pressed = True
|
|
594
|
+
elif event.key == pygame.K_r:
|
|
595
|
+
reset_game()
|
|
596
|
+
elif event.key == pygame.K_ESCAPE:
|
|
597
|
+
return False
|
|
598
|
+
|
|
599
|
+
elif event.type == pygame.KEYUP:
|
|
600
|
+
if event.key == pygame.K_LEFT or event.key == pygame.K_a:
|
|
601
|
+
left_pressed = False
|
|
602
|
+
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
|
|
603
|
+
right_pressed = False
|
|
604
|
+
elif event.key == pygame.K_SPACE or event.key == pygame.K_UP or event.key == pygame.K_k:
|
|
605
|
+
shoot_pressed = False
|
|
606
|
+
|
|
607
|
+
return True
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
# 初始化星星
|
|
611
|
+
for _ in range(STAR_COUNT):
|
|
612
|
+
stars.append({
|
|
613
|
+
'x': random.randint(0, WIDTH),
|
|
614
|
+
'y': random.randint(0, HEIGHT),
|
|
615
|
+
'radius': random.uniform(1, 3.2),
|
|
616
|
+
'speed': random.uniform(0.2, 0.6),
|
|
617
|
+
'color': (255, 240, 200)
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
reset_game()
|
|
621
|
+
last_boss_spawn_time = pygame.time.get_ticks()
|
|
622
|
+
|
|
623
|
+
# 主循环
|
|
624
|
+
def run():
|
|
625
|
+
global running
|
|
626
|
+
running = True
|
|
627
|
+
while running:
|
|
628
|
+
running = handle_events()
|
|
629
|
+
update()
|
|
630
|
+
draw()
|
|
631
|
+
clock.tick(FPS)
|
|
632
|
+
pygame.quit()
|
|
633
|
+
sys.exit()
|
|
634
|
+
|
|
635
|
+
if __name__ == "__main__":
|
|
636
|
+
run()
|
|
637
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ed-game
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Earth Defense Game
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: pygame-ce>=2.4
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# 地球防御 · 星辰守卫者
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install earth-defense
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/ed_game/__init__.py
|
|
5
|
+
src/ed_game/game_1.py
|
|
6
|
+
src/ed_game.egg-info/PKG-INFO
|
|
7
|
+
src/ed_game.egg-info/SOURCES.txt
|
|
8
|
+
src/ed_game.egg-info/dependency_links.txt
|
|
9
|
+
src/ed_game.egg-info/entry_points.txt
|
|
10
|
+
src/ed_game.egg-info/requires.txt
|
|
11
|
+
src/ed_game.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pygame-ce>=2.4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ed_game
|