You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
5.4 KiB
190 lines
5.4 KiB
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()
|
|
|