extends Node2D ## 游戏主控:状态机 + 关卡列表 + 分数/生命权威数据 + 信号连接。 ## 状态:MENU / PLAYING / LEVEL_CLEAR / GAME_OVER ## ## 流程: ## MENU → 空格开始 → PLAYING(关卡 1) ## PLAYING → 通关 → LEVEL_CLEAR(空格下一关) ## → 命数归零 → GAME_OVER(空格重开/回菜单) ## LEVEL_CLEAR → 最后一关 → 显示"通关" → 回 MENU ## GAME_OVER → 空格回 MENU enum GameState { MENU, PLAYING, LEVEL_CLEAR, GAME_OVER } # 关卡数据(按顺序加载) const LEVELS: Array = [ preload("res://data/level_1.gd"), preload("res://data/level_2.gd"), preload("res://data/level_3.gd"), ] # 场景切换 const MainMenuScene: PackedScene = preload("res://scenes/ui/main_menu.tscn") const PowerUpScene: PackedScene = preload("res://scenes/power_up.tscn") @export var initial_lives: int = 3 @onready var world: Node2D = $World @onready var level: Node2D = $World/Level @onready var paddle: CharacterBody2D = $World/Paddle @onready var ball: CharacterBody2D = $World/Ball @onready var lose_zone: Area2D = $World/LoseZone @onready var hud: CanvasLayer = $UI/HUD @onready var audio: Node = $Audio @onready var power_ups: Node2D = $World/PowerUps var state: GameState = GameState.MENU var score: int = 0 var lives: int = initial_lives var current_level_index: int = 0 var _remaining_bricks: int = 0 func _ready() -> void: # 信号连接 ball.lost.connect(_on_ball_lost) ball.brick_hit.connect(_on_ball_hit_brick) lose_zone.body_entered.connect(_on_lose_zone_body_entered) # 初始状态:菜单 _show_menu() func _process(_delta: float) -> void: match state: GameState.MENU: pass # 菜单场景自己处理输入 GameState.LEVEL_CLEAR: if Input.is_key_pressed(KEY_SPACE): _go_to_next_level() GameState.GAME_OVER: if Input.is_key_pressed(KEY_SPACE): _show_menu() # ───────── 状态切换 ───────── func _show_menu() -> void: state = GameState.MENU clear_power_ups() world.visible = false hud.visible = false # 加载菜单场景作为 UI 子节点(如果还没在) var menu: Control = $UI.get_node_or_null("MainMenu") if menu == null: menu = MainMenuScene.instantiate() menu.name = "MainMenu" $UI.add_child(menu) menu.start_pressed.connect(start_game) menu.quit_pressed.connect(_on_quit_pressed) menu.visible = true # 重新显示(可能之前被 hidden) func _on_quit_pressed() -> void: get_tree().quit() func start_game() -> void: # 主菜单"开始"按钮调用 # 关闭主菜单(先隐藏,再删除) var menu: Control = $UI.get_node_or_null("MainMenu") if menu: menu.visible = false menu.queue_free() score = 0 lives = initial_lives current_level_index = 0 paddle.reset_state() world.visible = true hud.visible = true hud.update_score(score) hud.update_lives(lives) _load_current_level() state = GameState.PLAYING func _load_current_level() -> void: clear_power_ups() paddle.reset_state() level.load_layout(LEVELS[current_level_index].LAYOUT) _remaining_bricks = level.get_child_count() ball.reset_to_paddle() hud.show_message("Level %d — 按 空格 发球" % (current_level_index + 1)) func _go_to_next_level() -> void: current_level_index += 1 if current_level_index >= LEVELS.size(): # 通关 _game_complete() return _load_current_level() state = GameState.PLAYING func _game_complete() -> void: state = GameState.GAME_OVER hud.show_message("通关!总分 %d — 按 空格 回主菜单" % score) func _game_over() -> void: state = GameState.GAME_OVER hud.show_message("Game Over — 按 空格 回主菜单") # ───────── 信号 ───────── func _on_ball_lost() -> void: audio.play_lost() lives -= 1 hud.update_lives(lives) if lives <= 0: _game_over() else: ball.reset_to_paddle() hud.show_message("按 空格 重新发球") func _on_ball_hit_brick(brick: Node) -> void: if not brick.has_method("hit"): return if not brick.destroyed.is_connected(_on_brick_destroyed): brick.destroyed.connect(_on_brick_destroyed) if not brick.power_up_dropped.is_connected(_on_brick_power_up_dropped): brick.power_up_dropped.connect(_on_brick_power_up_dropped) audio.play_hit() brick.hit() func _on_brick_power_up_dropped(spawn_position: Vector2) -> void: spawn_power_up(spawn_position) func _on_brick_destroyed(points: int) -> void: score += points _remaining_bricks -= 1 hud.update_score(score) audio.play_break() if _remaining_bricks <= 0 and state == GameState.PLAYING: state = GameState.LEVEL_CLEAR hud.show_message("Level Clear! — 按 空格 进下一关") audio.play_win() func _on_lose_zone_body_entered(body: Node) -> void: if body == ball: ball.lost.emit() # ───────── 道具 ───────── func spawn_power_up(position: Vector2) -> void: var pu: Area2D = PowerUpScene.instantiate() power_ups.add_child(pu) pu.position = position pu.collected.connect(_on_power_up_collected) func _on_power_up_collected(kind: String) -> void: audio.play_break() # 蹭原来的音效;有需要再单独加 match kind: "wide_paddle": paddle.widen() hud.show_message("挡板变宽!") "multi_ball": hud.show_message("多球(暂未实现)") "slow_ball": hud.show_message("减速(暂未实现)") "extra_life": lives += 1 hud.update_lives(lives) hud.show_message("+1 命") _: pass func clear_power_ups() -> void: for child in power_ups.get_children(): child.queue_free()