添加小游戏面板

This commit is contained in:
2025-08-28 10:23:13 +08:00
parent 633c1cac44
commit ca5685df52
24 changed files with 4569 additions and 1250 deletions

View File

@@ -2,6 +2,7 @@ extends Panel
# 游戏常量
const CELL_SIZE = 40
const DATA_FILE_PATH = "user://playergamedata.json"
# 地图元素
enum CellType {
@@ -72,6 +73,328 @@ const LEVELS = [
"#..$......@#",
"#..........#",
"############"
],
# 关卡5 - 角落挑战
[
"#########",
"#*......#",
"#.##....#",
"#.#$....#",
"#.#.....#",
"#.#.....#",
"#.......#",
"#......@#",
"#########"
],
# 关卡6 - 多箱子排列
[
"##########",
"#........#",
"#.*.*.*..#",
"#........#",
"#.$.$.$..#",
"#........#",
"#....@...#",
"#........#",
"##########"
],
# 关卡7 - 迷宫式
[
"############",
"#..........#",
"#.##.##.##.#",
"#.*#.#*.#*.#",
"#..#.#..#..#",
"#.$#.#$.#$.#",
"#..#.#..#..#",
"#..#.#..#..#",
"#..........#",
"#....@.....#",
"############"
],
# 关卡8 - 紧密配合
[
"#########",
"#.......#",
"#.##*##.#",
"#.#$.$#.#",
"#.#*.*#.#",
"#.#$.$#.#",
"#.##*##.#",
"#...@...#",
"#########"
],
# 关卡9 - 长廊挑战
[
"##############",
"#............#",
"#.##########.#",
"#.*........*.#",
"#.$.......$.#",
"#............#",
"#............#",
"#.$.......$.#",
"#.*........*.#",
"#.##########.#",
"#......@.....#",
"##############"
],
# 关卡10 - 螺旋结构
[
"###########",
"#.........#",
"#.#######.#",
"#.#*...#.#",
"#.#.#$.#.#",
"#.#.#*.#.#",
"#.#.#$.#.#",
"#.#...#.#",
"#.#####.#",
"#...@...#",
"###########"
],
# 关卡11 - 对称美学
[
"############",
"#..........#",
"#.*#....#*.#",
"#.$#....#$.#",
"#..#....#..#",
"#..........#",
"#..........#",
"#..#....#..#",
"#.$#....#$.#",
"#.*#....#*.#",
"#.....@....#",
"############"
],
# 关卡12 - 十字路口
[
"###########",
"#.........#",
"#....#....#",
"#.*..#..*#",
"#.$.###.$.#",
"#...#@#...#",
"#.$.###.$.#",
"#.*..#..*#",
"#....#....#",
"#.........#",
"###########"
],
# 关卡13 - 复杂迷宫
[
"##############",
"#............#",
"#.##.####.##.#",
"#.*#......#*.#",
"#.$#.####.#$.#",
"#..#.#..#.#..#",
"#....#..#....#",
"#..#.#..#.#..#",
"#.$#.####.#$.#",
"#.*#......#*.#",
"#.##.####.##.#",
"#......@.....#",
"##############"
],
# 关卡14 - 精密操作
[
"##########",
"#........#",
"#.######.#",
"#.#*..*.#",
"#.#$..$.#",
"#.#....#.#",
"#.#$..$.#",
"#.#*..*.#",
"#.######.#",
"#...@....#",
"##########"
],
# 关卡15 - 终极挑战
[
"###############",
"#.............#",
"#.###.###.###.#",
"#.*#*.*#*.*#*.#",
"#.$#$.$#$.$#$.#",
"#.###.###.###.#",
"#.............#",
"#.###.###.###.#",
"#.$#$.$#$.$#$.#",
"#.*#*.*#*.*#*.#",
"#.###.###.###.#",
"#.......@.....#",
"###############"
],
# 关卡16 - 狭窄通道
[
"#############",
"#...........#",
"#.#.#.#.#.#.#",
"#*#*#*#*#*#*#",
"#$#$#$#$#$#$#",
"#.#.#.#.#.#.#",
"#...........#",
"#.#.#.#.#.#.#",
"#$#$#$#$#$#$#",
"#*#*#*#*#*#*#",
"#.#.#.#.#.#.#",
"#.....@.....#",
"#############"
],
# 关卡17 - 环形结构
[
"##############",
"#............#",
"#.##########.#",
"#.#........#.#",
"#.#.######.#.#",
"#.#.#*..*.#.#.#",
"#.#.#$..$.#.#.#",
"#.#.#....#.#.#",
"#.#.######.#.#",
"#.#........#.#",
"#.##########.#",
"#......@.....#",
"##############"
],
# 关卡18 - 多层迷宫
[
"################",
"#..............#",
"#.############.#",
"#.#*........*.#.#",
"#.#$........$.#.#",
"#.#..########..#.#",
"#.#..#*....*.#..#.#",
"#.#..#$....$.#..#.#",
"#.#..########..#.#",
"#.#$........$.#.#",
"#.#*........*.#.#",
"#.############.#",
"#........@.....#",
"################"
],
# 关卡19 - 钻石形状
[
"#########",
"#.......#",
"#...*...#",
"#..*$*..#",
"#.*$@$*.#",
"#..*$*..#",
"#...*...#",
"#.......#",
"#########"
],
# 关卡20 - 复杂交叉
[
"###############",
"#.............#",
"#.#.#.#.#.#.#.#",
"#*#*#*#*#*#*#*#",
"#$#$#$#$#$#$#$#",
"#.#.#.#.#.#.#.#",
"#.............#",
"#.#.#.#.#.#.#.#",
"#$#$#$#$#$#$#$#",
"#*#*#*#*#*#*#*#",
"#.#.#.#.#.#.#.#",
"#.............#",
"#.#.#.#@#.#.#.#",
"#.............#",
"###############"
],
# 关卡21 - 螺旋深渊
[
"#############",
"#...........#",
"#.#########.#",
"#.#.......#.#",
"#.#.#####.#.#",
"#.#.#*..#.#.#",
"#.#.#$#.#.#.#",
"#.#.#*#.#.#.#",
"#.#.#$#.#.#.#",
"#.#.###.#.#.#",
"#.#.....#.#.#",
"#.#######.#.#",
"#.........#.#",
"#.....@...#.#",
"#############"
],
# 关卡22 - 双重挑战
[
"##############",
"#............#",
"#.####..####.#",
"#.#*.#..#.*#.#",
"#.#$.#..#.$#.#",
"#.#..####..#.#",
"#.#........#.#",
"#.#........#.#",
"#.#..####..#.#",
"#.#$.#..#.$#.#",
"#.#*.#..#.*#.#",
"#.####..####.#",
"#......@.....#",
"##############"
],
# 关卡23 - 星形布局
[
"###########",
"#.........#",
"#....#....#",
"#.#.*#*.#.#",
"#.#$###$#.#",
"#.*#.@.#*.#",
"#.#$###$#.#",
"#.#.*#*.#.#",
"#....#....#",
"#.........#",
"###########"
],
# 关卡24 - 终极迷宫
[
"################",
"#..............#",
"#.############.#",
"#.#*.........*.#",
"#.#$#########$#.#",
"#.#.#*......*.#.#",
"#.#.#$######$#.#.#",
"#.#.#.#*..*.#.#.#.#",
"#.#.#.#$..$.#.#.#.#",
"#.#.#.######.#.#.#",
"#.#.#........#.#.#",
"#.#.##########.#.#",
"#.#............#.#",
"#.##############.#",
"#........@.......#",
"################"
],
# 关卡25 - 大师级挑战
[
"#################",
"#...............#",
"#.#############.#",
"#.#*.*.*.*.*.*#.#",
"#.#$.$.$.$.$.$#.#",
"#.#.###########.#",
"#.#.#*.*.*.*.*#.#",
"#.#.#$.$.$.$.$#.#",
"#.#.#.#######.#.#",
"#.#.#.#*.*.*#.#.#",
"#.#.#.#$.$.$#.#.#",
"#.#.#.#####.#.#.#",
"#.#.#.......#.#.#",
"#.#.#########.#.#",
"#.#...........#.#",
"#.#############.#",
"#.........@.....#",
"#################"
]
]
@@ -83,19 +406,31 @@ var moves = 0
var level_completed = false
var map_width = 0
var map_height = 0
var total_moves = 0
var levels_completed = 0
var best_moves_per_level = {}
var player_data = {}
# 节点引用
@onready var game_area = $GameArea
@onready var level_label = $LevelLabel
@onready var moves_label = $MovesLabel
@onready var win_label = $WinLabel
@onready var stats_label = $StatsLabel
@onready var virtual_controls = $VirtualControls
func _ready():
# 设置游戏区域样式
game_area.modulate = Color(0.9, 0.9, 0.9)
# 加载玩家数据
load_player_data()
# 初始化游戏
init_level()
# 设置虚拟按键
setup_virtual_controls()
func init_level():
# 重置游戏状态
@@ -242,12 +577,14 @@ func move_player(direction: Vector2):
# 增加步数
moves += 1
total_moves += 1
# 检查是否过关
check_win_condition()
# 更新UI和重绘
update_ui()
save_player_data()
queue_redraw()
func check_win_condition():
@@ -259,12 +596,22 @@ func check_win_condition():
# 所有箱子都在目标点上,过关!
level_completed = true
levels_completed += 1
# 记录最佳步数
var level_key = str(current_level + 1)
if not best_moves_per_level.has(level_key) or moves < best_moves_per_level[level_key]:
best_moves_per_level[level_key] = moves
win_label.text = "恭喜过关!\n步数: " + str(moves) + "\n最佳: " + str(best_moves_per_level.get(level_key, moves)) + "\n按N进入下一关\n按R重新开始"
win_label.visible = true
func next_level():
if current_level < LEVELS.size() - 1:
current_level += 1
init_level()
else:
win_label.text = "恭喜!你已完成所有关卡!\n总步数: " + str(total_moves) + "\n按R重新开始第一关"
func prev_level():
if current_level > 0:
@@ -272,16 +619,98 @@ func prev_level():
init_level()
func update_ui():
level_label.text = "关卡: " + str(current_level + 1)
level_label.text = "关卡: " + str(current_level + 1) + "/" + str(LEVELS.size())
moves_label.text = "步数: " + str(moves)
if stats_label:
stats_label.text = "已完成: " + str(levels_completed) + " | 总步数: " + str(total_moves)
func setup_virtual_controls():
if not virtual_controls:
return
# 连接虚拟按键信号
var up_btn = virtual_controls.get_node("UpButton")
var down_btn = virtual_controls.get_node("DownButton")
var left_btn = virtual_controls.get_node("LeftButton")
var right_btn = virtual_controls.get_node("RightButton")
var reset_btn = virtual_controls.get_node("ResetButton")
if up_btn:
up_btn.pressed.connect(_on_virtual_button_pressed.bind(Vector2(0, -1)))
if down_btn:
down_btn.pressed.connect(_on_virtual_button_pressed.bind(Vector2(0, 1)))
if left_btn:
left_btn.pressed.connect(_on_virtual_button_pressed.bind(Vector2(-1, 0)))
if right_btn:
right_btn.pressed.connect(_on_virtual_button_pressed.bind(Vector2(1, 0)))
if reset_btn:
reset_btn.pressed.connect(init_level)
func _on_virtual_button_pressed(direction: Vector2):
if not level_completed:
move_player(direction)
func load_player_data():
if FileAccess.file_exists(DATA_FILE_PATH):
var file = FileAccess.open(DATA_FILE_PATH, FileAccess.READ)
if file:
var json_string = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_string)
if parse_result == OK:
player_data = json.data
if player_data.has("pushbox"):
var game_data = player_data["pushbox"]
current_level = game_data.get("current_level", 0)
total_moves = game_data.get("total_moves", 0)
levels_completed = game_data.get("levels_completed", 0)
best_moves_per_level = game_data.get("best_moves_per_level", {})
func save_player_data():
if not player_data.has("pushbox"):
player_data["pushbox"] = {}
player_data["pushbox"]["current_level"] = current_level
player_data["pushbox"]["max_level_reached"] = max(current_level, player_data.get("pushbox", {}).get("max_level_reached", 0))
player_data["pushbox"]["total_moves"] = total_moves
player_data["pushbox"]["levels_completed"] = levels_completed
player_data["pushbox"]["best_moves_per_level"] = best_moves_per_level
# 更新全局数据
if not player_data.has("global"):
player_data["global"] = {}
player_data["global"]["last_played"] = Time.get_datetime_string_from_system()
var file = FileAccess.open(DATA_FILE_PATH, FileAccess.WRITE)
if file:
var json_string = JSON.stringify(player_data)
file.store_string(json_string)
file.close()
func _draw():
if not game_area:
return
# 绘制背景渐变
var gradient = Gradient.new()
gradient.add_point(0.0, Color(0.15, 0.25, 0.35, 0.9))
gradient.add_point(1.0, Color(0.1, 0.15, 0.25, 0.95))
draw_rect(Rect2(Vector2.ZERO, size), gradient.sample(0.5), true)
# 获取游戏区域位置
var area_pos = game_area.position
# 绘制游戏区域阴影
var shadow_offset = Vector2(6, 6)
var area_rect = Rect2(area_pos + shadow_offset, game_area.size)
draw_rect(area_rect, Color(0, 0, 0, 0.4), true)
# 绘制游戏区域背景
area_rect = Rect2(area_pos, game_area.size)
draw_rect(area_rect, Color(0.8, 0.75, 0.7, 0.95), true)
# 计算起始绘制位置(居中)
var start_x = area_pos.x + (game_area.size.x - map_width * CELL_SIZE) / 2
var start_y = area_pos.y + (game_area.size.y - map_height * CELL_SIZE) / 2
@@ -294,24 +723,89 @@ func _draw():
var rect = Rect2(cell_x, cell_y, CELL_SIZE, CELL_SIZE)
var cell_type = level_data[y][x]
var color = CELL_COLORS[cell_type]
# 绘制单元格
draw_rect(rect, color, true)
# 绘制单元格阴影
draw_rect(rect.grow(1), Color(0, 0, 0, 0.2), true)
# 绘制边框
draw_rect(rect, Color.BLACK, false, 1)
# 根据类型绘制不同效果
match cell_type:
CellType.EMPTY:
draw_rect(rect, Color(0.9, 0.85, 0.8, 0.7), true)
CellType.WALL:
# 绘制立体墙壁效果
draw_rect(rect, Color(0.3, 0.3, 0.3), true)
# 高光
var highlight_rect = Rect2(rect.position, Vector2(rect.size.x, rect.size.y * 0.3))
draw_rect(highlight_rect, Color(0.5, 0.5, 0.5, 0.8), true)
CellType.TARGET:
# 绘制目标点(带光晕效果)
draw_rect(rect, Color(0.6, 0.8, 1.0, 0.8), true)
# 内圈
var inner_rect = rect.grow(-8)
draw_rect(inner_rect, Color(0.4, 0.6, 0.9, 0.9), true)
CellType.BOX:
# 绘制立体箱子
draw_rect(rect, Color(0.7, 0.5, 0.3), true)
# 高光
var box_highlight = Rect2(rect.position + Vector2(2, 2), Vector2(rect.size.x - 4, rect.size.y * 0.3))
draw_rect(box_highlight, Color(0.9, 0.7, 0.5, 0.8), true)
# 边框
draw_rect(rect, Color(0.5, 0.3, 0.1), false, 2)
CellType.PLAYER:
# 检查玩家下面是否有目标点
var level_strings = LEVELS[current_level]
if y < level_strings.size() and x < level_strings[y].length():
var original_char = level_strings[y][x]
if original_char == '*': # 玩家在目标点上
# 先绘制目标点
draw_rect(rect, Color(0.6, 0.8, 1.0, 0.8), true)
var inner_rect = rect.grow(-8)
draw_rect(inner_rect, Color(0.4, 0.6, 0.9, 0.9), true)
else:
draw_rect(rect, Color(0.9, 0.85, 0.8, 0.7), true)
else:
draw_rect(rect, Color(0.9, 0.85, 0.8, 0.7), true)
# 绘制玩家(圆形)
var center = rect.get_center()
var radius = min(rect.size.x, rect.size.y) * 0.3
# 阴影
draw_circle(center + Vector2(1, 1), radius, Color(0, 0, 0, 0.3))
# 玩家主体
draw_circle(center, radius, Color(0.2, 0.8, 0.2))
# 高光
draw_circle(center - Vector2(2, 2), radius * 0.5, Color(0.6, 1.0, 0.6, 0.7))
CellType.BOX_ON_TARGET:
# 绘制目标点背景
draw_rect(rect, Color(0.6, 0.8, 1.0, 0.8), true)
var inner_rect = rect.grow(-8)
draw_rect(inner_rect, Color(0.4, 0.6, 0.9, 0.9), true)
# 绘制完成的箱子(绿色)
var box_rect = rect.grow(-4)
draw_rect(box_rect, Color(0.2, 0.7, 0.2), true)
# 高光
var box_highlight = Rect2(box_rect.position + Vector2(2, 2), Vector2(box_rect.size.x - 4, box_rect.size.y * 0.3))
draw_rect(box_highlight, Color(0.4, 0.9, 0.4, 0.8), true)
# 边框
draw_rect(box_rect, Color(0.1, 0.5, 0.1), false, 2)
# 特殊处理:如果是玩家在目标点上,需要先绘制目标点
if cell_type == CellType.PLAYER:
# 检查玩家下面是否有目标点(通过检查原始关卡数据)
var level_strings = LEVELS[current_level]
if y < level_strings.size() and x < level_strings[y].length():
var original_char = level_strings[y][x]
if original_char == '*': # 玩家在目标点上
draw_rect(rect, CELL_COLORS[CellType.TARGET], true)
draw_rect(rect, Color.BLACK, false, 1)
# 再绘制玩家(半透明)
var player_color = CELL_COLORS[CellType.PLAYER]
player_color.a = 0.8
draw_rect(rect, player_color, true)
# 绘制网格线(淡色)
draw_rect(rect, Color(0.6, 0.6, 0.6, 0.3), false, 1)
#手机端下一关
func _on_next_button_pressed() -> void:
next_level()
pass
#手机端上一关
func _on_last_button_pressed() -> void:
prev_level()
pass
#关闭推箱子游戏界面
func _on_quit_button_pressed() -> void:
self.hide()
get_parent().remove_child(self)
queue_free()
pass