-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
640 lines (531 loc) · 28.5 KB
/
main.py
File metadata and controls
640 lines (531 loc) · 28.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
"""
弹幕游戏主入口 - 负责初始化和游戏主循环
"""
import sys
import os
import json
import moderngl
# 添加src目录到Python路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
from src.core.window import GameWindow, FrameClock, EVENT_QUIT, EVENT_KEYDOWN
from src.core.input_manager import KeyboardState, KEY_UP, KEY_DOWN, KEY_z, KEY_ESCAPE, KEY_c
from src.core.audio_backend import init_audio_backend
from src.render import Renderer
from src.game.bullet import BulletPool
from src.game.player import Player, check_collisions, load_player
from src.game.stage import StageManager
from src.game.boss import BossManager
from src.game.laser import LaserPool, get_laser_texture_data
from src.game.item import ItemPool, ItemConfig
from src.game.audio import GameAudioBank, AudioManager
from src.resource.sprite import SpriteManager
from src.core import (
GameConfig, get_config, init_config,
CollisionManager, get_collision_manager
)
from src.resource.texture_asset import (
TextureAssetManager,
get_texture_asset_manager,
init_texture_asset_manager
)
from src.render.item_renderer import ItemRenderer
from src.ui import HUD, UIRenderer
from src.ui.dialog_gl_renderer import DialogGLRenderer
from src.ui.loading_renderer import LoadingScreenRenderer
from src.ui.main_menu_renderer import MainMenuRenderer
from src.ui.pause_menu_renderer import PauseMenuRenderer
from src.ui.main_menu_layout import load_layout as load_main_menu_layout
from src.ui.hud import load_hud_layout
from src.ui.bitmap_font import get_font_manager
from game_content.stages.stage1.stage_script import Stage1
# ===== Debug 模式 =====
DEBUG_MODE = "--debug" in sys.argv
def build_debug_menu(stage_class):
"""从 StageScript 子类构建 Debug 跳转菜单"""
from src.game.stage.stage_base import BossDef
from src.game.stage.boss_base import BossPhaseType
entries = [{"label": "从头开始", "target": None}]
# 扫描类属性,查找 BossDef(保持声明顺序)
boss_attrs = []
for attr_name in vars(stage_class):
if attr_name.startswith('_'):
continue
attr = getattr(stage_class, attr_name, None)
if isinstance(attr, BossDef):
boss_attrs.append((attr_name, attr))
for attr_name, boss_def in boss_attrs:
is_midboss = "mid" in attr_name.lower()
effective_type = "midboss" if is_midboss else "boss"
type_label = "道中 Boss" if is_midboss else "Boss"
# Boss 入口(从第 0 阶段开始)
entries.append({
"label": f"{type_label}: {boss_def.name} ({boss_def.id})",
"target": {"type": effective_type, "phase": 0}
})
# 每个阶段
for i, phase in enumerate(boss_def.phases):
if phase.phase_type == BossPhaseType.SPELLCARD:
phase_label = phase.name or f"符卡 {i}"
else:
phase_label = f"通常攻撃 {i + 1}"
entries.append({
"label": f" └ Phase {i}: {phase_label}",
"target": {"type": effective_type, "phase": i}
})
return entries
def run_debug_menu(window, ctx, screen_size, stage_class):
"""在 GUI 中显示 Debug 跳转菜单,处理用户输入并返回选择的 target 或 None"""
from src.ui.main_menu_renderer import MainMenuRenderer
from src.core.window import FrameClock, EVENT_QUIT, EVENT_KEYDOWN
from src.core.input_manager import KEY_UP, KEY_DOWN, KEY_z, KEY_ESCAPE
stage_name = getattr(stage_class, 'name', stage_class.__name__)
entries = build_debug_menu(stage_class)
# 构建适合 Debug 菜单的布局
# 为了容纳更多选项,缩小字体,减小间距,并将整体上移
layout = {
"bg_gradient": {"top": [20, 10, 10], "bottom": [40, 20, 20]}, # 偏红背景区别于主菜单
"title": {
"text": f"Debug: {stage_name}",
"font_size": 36,
"color": [255, 200, 200],
"y_ratio": 0.05
},
"options": [{"text": entry["label"]} for entry in entries],
"option_spacing": 26, # 间距更紧凑
"option_font_size": 22, # 字体更小
"option_colors": {"normal": [180, 180, 180], "selected": [255, 255, 100]},
"hint": {
"text": "方向键 ↑↓ 选择 Z 确认 ESC 从头开始",
"font_size": 18,
"color": [150, 150, 150],
"y_offset": -30
}
}
renderer = MainMenuRenderer(ctx, screen_size[0], screen_size[1])
num_options = len(entries)
selected_index = 0
clock = FrameClock()
while True:
clock.tick(60)
for event in window.poll_events():
if event['type'] == EVENT_QUIT:
renderer.cleanup()
return None
if event['type'] == EVENT_KEYDOWN:
if event['key'] == KEY_UP:
selected_index = (selected_index - 1) % num_options
elif event['key'] == KEY_DOWN:
selected_index = (selected_index + 1) % num_options
elif event['key'] == KEY_z:
renderer.cleanup()
return entries[selected_index]["target"]
elif event['key'] == KEY_ESCAPE:
renderer.cleanup()
return None # 默认不跳过
ctx.viewport = (0, 0, screen_size[0], screen_size[1])
ctx.clear(0.0, 0.0, 0.0)
renderer.render(selected_index, layout=layout)
window.swap_buffers()
def initialize_window_and_context():
"""初始化GLFW窗口和ModernGL上下文"""
init_audio_backend()
config = init_config()
base_size = (config.base_width, config.base_height)
screen_size = (config.window_width, config.window_height)
game_viewport = config.game_viewport
window = GameWindow(screen_size[0], screen_size[1], "弹幕游戏")
ctx = moderngl.create_context()
ctx.enable(moderngl.BLEND)
ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA
return window, ctx, base_size, screen_size, game_viewport
def load_resources(ctx, texture_asset_manager: TextureAssetManager):
"""
加载游戏资源(使用新的统一纹理资产管理系统)
Args:
ctx: ModernGL上下文
texture_asset_manager: 纹理资产管理器
Returns:
tuple: (textures, sprite_uv_map)
"""
sprite_config_folder = "assets/images"
if not texture_asset_manager.load_sprite_config_folder(sprite_config_folder):
print("Failed to load sprite configurations!")
sys.exit()
all_sprite_ids = texture_asset_manager.get_all_sprite_ids()
default_sprite_id = 'star_small1' if 'star_small1' in all_sprite_ids else next(iter(all_sprite_ids), None)
textures = texture_asset_manager.create_all_gl_textures(ctx, flip_y=True)
sprite_uv_map = texture_asset_manager.compute_all_sprite_uvs(flip_y=True)
stats = texture_asset_manager.get_stats()
print(f"资源加载完成: {stats['atlases']} 图集, {stats['sprites']} 精灵, {stats['animations']} 动画")
print(f"Loaded {len(textures)} texture(s) successfully")
return textures, sprite_uv_map
def initialize_game_objects(audio_manager=None, background_renderer=None):
"""初始化游戏对象(玩家、子弹池、关卡管理器等)"""
player = load_player("tao")
print(f"已加载玩家: {player.name}")
bullet_pool = BulletPool(max_bullets=50000)
laser_pool = LaserPool(max_lasers=100)
item_pool = ItemPool(max_items=1000)
boss_manager = BossManager()
stage_manager = StageManager()
stage_manager.set_boss_manager(boss_manager)
stage_manager.bind_engine(
bullet_pool=bullet_pool,
player=player,
audio_manager=audio_manager,
item_pool=item_pool,
)
stage_manager.load_stage(Stage1)
return player, bullet_pool, laser_pool, item_pool, stage_manager
def run_main_menu(window, ctx, screen_size) -> bool:
"""
显示主菜单,处理用户输入。
Returns:
True: 用户选择开始游戏
False: 用户选择退出
"""
main_menu_renderer = MainMenuRenderer(ctx, screen_size[0], screen_size[1])
main_menu_layout = load_main_menu_layout("assets/ui/main_menu_layout.json")
num_options = max(1, len(main_menu_layout.get("options", [])))
selected_index = 0
clock = FrameClock()
while True:
dt = clock.tick(60)
for event in window.poll_events():
if event['type'] == EVENT_QUIT:
main_menu_renderer.cleanup()
return False
if event['type'] == EVENT_KEYDOWN:
if event['key'] == KEY_UP:
selected_index = (selected_index - 1) % num_options
elif event['key'] == KEY_DOWN:
selected_index = (selected_index + 1) % num_options
elif event['key'] == KEY_z:
main_menu_renderer.cleanup()
return selected_index == 0
elif event['key'] == KEY_ESCAPE:
main_menu_renderer.cleanup()
return False
ctx.viewport = (0, 0, screen_size[0], screen_size[1])
ctx.clear(0.0, 0.0, 0.0)
main_menu_renderer.render(selected_index, layout=main_menu_layout)
window.swap_buffers()
def main():
"""游戏主函数"""
window, ctx, base_size, screen_size, game_viewport = initialize_window_and_context()
while True:
if not run_main_menu(window, ctx, screen_size):
window.destroy()
sys.exit(0)
while True:
texture_asset_manager = init_texture_asset_manager(asset_root="assets")
laser_tex_data = get_laser_texture_data()
laser_tex_data.load_config("assets/images/laser/laser_config.json")
sprite_manager = SpriteManager()
textures, sprite_uv_map = load_resources(ctx, texture_asset_manager)
sprite_manager._sync_from_asset_manager()
renderer = Renderer(ctx, base_size, sprite_manager, textures, sprite_uv_map)
font_manager = get_font_manager()
font_manager.load_font('score', 'assets/images/ui/font/score.fnt')
hud_layout_cfg = load_hud_layout('assets/ui/hud_layout.json')
panel_cfg = hud_layout_cfg.get('panel', {}) if hud_layout_cfg else {}
gap_to_game = panel_cfg.get('gap_to_game', 16)
margin_right = panel_cfg.get('margin_right', 32)
bg_color = tuple(panel_cfg.get('bg_color', [16, 16, 32]))
bg_alpha = panel_cfg.get('bg_alpha', 0.6)
layout_override = hud_layout_cfg.get('layout') if hud_layout_cfg else None
panel_origin_x = game_viewport[0] + game_viewport[2] + gap_to_game
panel_origin_y = game_viewport[1]
available_width = screen_size[0] - panel_origin_x - margin_right
default_panel_size = [max(200, available_width), game_viewport[3]]
panel_size_cfg = panel_cfg.get('size', default_panel_size)
panel_width = panel_size_cfg[0]
panel_height = panel_size_cfg[1] if len(panel_size_cfg) > 1 else default_panel_size[1]
hud = HUD(screen_width=screen_size[0], screen_height=screen_size[1],
panel_origin=(panel_origin_x, panel_origin_y),
panel_size=(panel_width, panel_height),
game_origin=(game_viewport[0], game_viewport[1]),
game_size=(game_viewport[2], game_viewport[3]),
bg_color=bg_color, bg_alpha=bg_alpha,
layout_override=layout_override)
ui_renderer = UIRenderer(ctx, screen_width=screen_size[0], screen_height=screen_size[1])
dialog_gl_renderer = DialogGLRenderer(ctx, screen_size[0], screen_size[1], game_viewport)
loading_renderer = LoadingScreenRenderer(ctx, screen_size[0], screen_size[1])
pause_menu_renderer = PauseMenuRenderer(ctx, screen_size[0], screen_size[1])
item_renderer = ItemRenderer(ctx, base_size)
item_renderer.load_texture("assets/images/item/item.png")
background_renderer = None
try:
from src.game.background_render import BackgroundRenderer
background_renderer = BackgroundRenderer(ctx, base_size)
background_renderer.load_background('lake')
renderer.set_background_renderer(background_renderer)
print("背景系统初始化成功")
except Exception as e:
print(f"背景系统初始化失败(可选功能): {e}")
import traceback
traceback.print_exc()
game_audio = GameAudioBank()
game_audio.load_defaults()
audio_manager = AudioManager(game_audio)
player, bullet_pool, laser_pool, item_pool, stage_manager = initialize_game_objects(
audio_manager=audio_manager,
background_renderer=background_renderer
)
# ===== Debug: 显示 GUI 跳转菜单 =====
if DEBUG_MODE:
debug_target = run_debug_menu(window, ctx, screen_size, Stage1)
if debug_target:
stage_manager.debug_skip_to = debug_target
# 加载高分记录
item_pool.stats.load_hiscore()
# 连接 bomb 回调:通知 Boss 积分系统
def _on_player_bomb():
if stage_manager.current_stage and stage_manager.current_stage._current_boss:
boss = stage_manager.current_stage._current_boss
if boss._active:
boss.on_player_bomb()
player.on_bomb_callback = _on_player_bomb
clock = FrameClock()
running = True
paused = False
pause_menu_index = 0
game_result_state = None
while running:
dt = clock.tick(60)
for event in window.poll_events():
if event['type'] == EVENT_QUIT:
running = False
elif event['type'] == EVENT_KEYDOWN:
if event['key'] == KEY_ESCAPE:
paused = not paused
if paused:
audio_manager.pause_bgm()
pause_menu_index = 0
else:
audio_manager.unpause_bgm()
if paused:
if event['key'] == KEY_UP:
pause_menu_index = (pause_menu_index - 1) % 3
elif event['key'] == KEY_DOWN:
pause_menu_index = (pause_menu_index + 1) % 3
elif event['key'] == KEY_z:
if pause_menu_index == 0:
# 继续游戏
paused = False
audio_manager.unpause_bgm()
elif pause_menu_index == 1:
# 重新开始
game_result_state = "RESTART"
running = False
elif pause_menu_index == 2:
# 返回主菜单
game_result_state = "MAIN_MENU"
running = False
else:
if event['key'] == KEY_c:
dialog_active = False
if stage_manager.current_stage:
dialog_state = stage_manager.current_stage.get_dialog_renderer()
dialog_active = dialog_state and hasattr(dialog_state, 'is_active') and dialog_state.is_active()
if not dialog_active and not stage_manager.loading_info:
_player_cycle = {"tao": "orin", "orin": "tenshi", "tenshi": "tao"}
new_name = _player_cycle.get(player.name, "tao")
old_pos = list(player.pos)
old_lives = player.lives
old_power = player.power
old_invinc = player.invincible_timer
player = load_player(new_name)
player.pos = old_pos
player.lives = old_lives
player.power = old_power
player.invincible_timer = max(old_invinc, 0.5) # 给个短暂无敌防判定死
player.on_bomb_callback = _on_player_bomb
# 强制清空渲染器的纹理缓存,以便下一次渲染加载新自机的图片
if hasattr(renderer, 'player_texture') and renderer.player_texture:
renderer.player_texture.release()
renderer.player_texture = None
if hasattr(renderer, 'player_bullet_texture') and renderer.player_bullet_texture:
renderer.player_bullet_texture.release()
renderer.player_bullet_texture = None
stage_manager._engine_refs['player'] = player
if stage_manager.current_context:
stage_manager.current_context.player = player
keys = KeyboardState(window.get_key_states())
# ===== 加载画面模式 =====
if stage_manager.loading_info:
ctx.viewport = (0, 0, screen_size[0], screen_size[1])
ctx.clear(0.0, 0.0, 0.0)
loading_renderer.render(stage_manager.loading_info)
hud.state.fps = round(clock.get_fps())
hud.state.max_fps = round(clock.get_max_fps())
window.swap_buffers()
if not paused:
stage_manager.update(dt, bullet_pool, player)
continue
# ===== 正常游戏模式 =====
dialog_active = False
if stage_manager.current_stage:
dialog_state = stage_manager.current_stage.get_dialog_renderer()
if dialog_state and hasattr(dialog_state, 'is_active') and dialog_state.is_active():
dialog_active = True
if not paused:
if not dialog_active:
# 收集敌人列表用于追踪弹
_enemies_for_homing = []
if stage_manager.current_context:
_enemies_for_homing.extend(stage_manager.current_context.get_enemy_scripts())
if stage_manager.current_stage and stage_manager.current_stage._current_boss:
_boss = stage_manager.current_stage._current_boss
if _boss._active:
_enemies_for_homing.append(_boss)
player.update(dt, keys, enemies=_enemies_for_homing or None)
bullet_pool.update(dt)
laser_pool.update()
item_pool.update(player.pos[0], player.pos[1], dt)
stage_manager.update(dt, bullet_pool, player)
if stage_manager.loading_info:
ctx.viewport = (0, 0, screen_size[0], screen_size[1])
ctx.clear(0.0, 0.0, 0.0)
loading_renderer.render(stage_manager.loading_info)
hud.state.fps = round(clock.get_fps())
hud.state.max_fps = round(clock.get_max_fps())
window.swap_buffers()
continue
player.score = item_pool.stats.score
player.power = item_pool.stats.get_power_float()
player.lives = item_pool.stats.lives
collision_mgr = get_collision_manager()
if not paused and not dialog_active:
hit_x, hit_y = player.get_hit_position()
if player.invincible_timer <= 0:
bullet_result = collision_mgr.check_player_vs_bullets(
hit_x, hit_y, player.hit_radius, bullet_pool
)
if bullet_result.occurred:
if player.take_damage():
print(f"Player hit by bullet! Lives left: {player.lives}")
bullet_pool.data['alive'][bullet_result.index] = 0
# Notify boss scoring system
_ab = stage_manager.current_stage._current_boss if stage_manager.current_stage else None
if _ab and _ab._active:
_ab.on_player_miss()
if player.invincible_timer <= 0:
laser_result = collision_mgr.check_player_vs_lasers(
hit_x, hit_y, player.hit_radius, laser_pool
)
if laser_result.occurred:
if player.take_damage():
print(f"Player hit by laser! Lives left: {player.lives}")
# Notify boss scoring system
_ab = stage_manager.current_stage._current_boss if stage_manager.current_stage else None
if _ab and _ab._active:
_ab.on_player_miss()
collision_targets = []
if stage_manager.current_context:
collision_targets.extend(stage_manager.current_context.get_enemy_scripts())
if stage_manager.current_stage and stage_manager.current_stage._current_boss:
boss = stage_manager.current_stage._current_boss
if boss._active:
collision_targets.append(boss)
if collision_targets:
hits, active_targets = collision_mgr.check_player_bullets_vs_targets(
player.bullet_pool,
collision_targets,
hit_radius=0.02,
)
for hit in hits:
if 0 <= hit.target_idx < len(active_targets):
target = active_targets[hit.target_idx]
target.damage(int(hit.damage))
# +10 per bullet hit (matches LuaSTG)
item_pool.stats.score += 10
if player.script and hasattr(player.script, 'on_bullet_hit_enemy'):
player.script.on_bullet_hit_enemy(
hit.bullet_idx, target, hit.damage)
# === 自机激光碰撞检测 ===
player_lasers = getattr(player, 'player_lasers', None)
if player_lasers:
for laser_data in player_lasers:
lx = laser_data['x']
ly = laser_data.get('y', -1.0)
laser_dmg = laser_data.get('damage', 2)
# laser精灵旋转90°后的光柱半宽
_spr = {}
_all_spr = {**getattr(player, 'sprites', {}), **getattr(player, 'bullet_sprites', {})}
_spr = _all_spr.get(laser_data.get('sprite', ''), {})
_rect = _spr.get('rect', [0, 0, 6, 12])
beam_hw = _rect[3] / 192.0 / 2.0
for target in collision_targets:
alive_f = getattr(target, '_active', getattr(target, 'alive', True))
if not alive_f:
continue
t_pos = getattr(target, 'pos', None)
if t_pos is not None:
tx, ty = float(t_pos[0]), float(t_pos[1])
elif hasattr(target, 'x') and hasattr(target, 'y'):
tx, ty = float(target.x), float(target.y)
else:
continue
hit_r = getattr(target, 'hitbox_radius', getattr(target, 'hit_radius', 0.05))
if abs(tx - lx) < beam_hw + hit_r and ty > ly:
target.damage(int(laser_dmg))
item_pool.stats.score += 10
# === Graze detection ===
graze_count = collision_mgr.check_player_graze(
hit_x, hit_y, player.graze_radius, bullet_pool
)
if graze_count > 0:
item_pool.stats.graze += graze_count
item_pool.stats.update_point_rate()
player.add_graze(graze_count)
hud.update_from_player(player)
hud.state.graze = item_pool.stats.graze
hud.state.bombs = item_pool.stats.bombs
hud.state.point_value = item_pool.stats.point_rate
active_boss = stage_manager.get_active_boss()
if active_boss:
hud.update_from_boss(active_boss)
enemy_scripts = None
if hasattr(stage_manager, 'current_context') and stage_manager.current_context:
enemy_scripts = stage_manager.current_context.get_enemy_scripts()
renderer.render_frame(
bullet_pool, player, stage_manager, laser_pool,
viewport_rect=game_viewport,
item_renderer=item_renderer,
item_pool=item_pool,
dt=0 if paused else dt,
enemy_scripts=enemy_scripts
)
ctx.viewport = (0, 0, screen_size[0], screen_size[1])
ui_renderer.render_hud(hud)
if stage_manager.current_stage:
dialog_state = stage_manager.current_stage.get_dialog_renderer()
if dialog_state:
dialog_gl_renderer.render(dialog_state)
if paused:
pause_menu_renderer.render(pause_menu_index)
hud.state.fps = round(clock.get_fps())
hud.state.max_fps = round(clock.get_max_fps())
window.swap_buffers()
# 保存高分记录
item_pool.stats.save_hiscore()
audio_manager.cleanup()
renderer.cleanup()
item_renderer.cleanup()
ui_renderer.cleanup()
dialog_gl_renderer.cleanup()
loading_renderer.cleanup()
pause_menu_renderer.cleanup()
if background_renderer:
background_renderer.cleanup()
texture_asset_manager.clear_all()
if game_result_state == "MAIN_MENU":
break # Break inner loop, go back to top `while True:` where `run_main_menu` is
elif game_result_state == "RESTART":
pass # Just continue inner loop, re-initializing everything except Main Menu
else:
window.destroy()
sys.exit()
if __name__ == "__main__":
main()