extends Panel # 游戏常量 const CELL_SIZE = 40 const DATA_FILE_PATH = "user://playergamedata.json" # 地图元素 enum CellType { EMPTY, # 空地 WALL, # 墙壁 TARGET, # 目标点 BOX, # 箱子 PLAYER, # 玩家 BOX_ON_TARGET # 箱子在目标点上 } # 颜色配置 const CELL_COLORS = { CellType.EMPTY: Color.WHITE, CellType.WALL: Color.DARK_GRAY, CellType.TARGET: Color.LIGHT_BLUE, CellType.BOX: Color.SADDLE_BROWN, CellType.PLAYER: Color.GREEN, CellType.BOX_ON_TARGET: Color.DARK_GREEN } # 关卡数据 const LEVELS = [ # 关卡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 - 大师级挑战 [ "#################", "#...............#", "#.#############.#", "#.#*.*.*.*.*.*#.#", "#.#$.$.$.$.$.$#.#", "#.#.###########.#", "#.#.#*.*.*.*.*#.#", "#.#.#$.$.$.$.$#.#", "#.#.#.#######.#.#", "#.#.#.#*.*.*#.#.#", "#.#.#.#$.$.$#.#.#", "#.#.#.#####.#.#.#", "#.#.#.......#.#.#", "#.#.#########.#.#", "#.#...........#.#", "#.#############.#", "#.........@.....#", "#################" ] ] # 游戏变量 var current_level = 0 var level_data = [] var player_pos = Vector2() 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(): # 重置游戏状态 level_completed = false moves = 0 # 加载当前关卡 load_level(current_level) # 更新UI update_ui() win_label.visible = false queue_redraw() func load_level(level_index: int): if level_index >= LEVELS.size(): level_index = LEVELS.size() - 1 var level_strings = LEVELS[level_index] map_height = level_strings.size() map_width = level_strings[0].length() # 初始化关卡数据 level_data.clear() for y in range(map_height): var row = [] for x in range(map_width): row.append(CellType.EMPTY) level_data.append(row) # 解析关卡字符串 for y in range(map_height): var line = level_strings[y] for x in range(line.length()): var char = line[x] match char: '#': # 墙壁 level_data[y][x] = CellType.WALL '*': # 目标点 level_data[y][x] = CellType.TARGET '$': # 箱子 level_data[y][x] = CellType.BOX '@': # 玩家 level_data[y][x] = CellType.PLAYER player_pos = Vector2(x, y) '+': # 箱子在目标点上 level_data[y][x] = CellType.BOX_ON_TARGET '.': # 空地 level_data[y][x] = CellType.EMPTY func _input(event): if event is InputEventKey and event.pressed: if level_completed: match event.keycode: KEY_N: next_level() KEY_R: init_level() return # 移动控制 var direction = Vector2.ZERO match event.keycode: KEY_UP, KEY_W: direction = Vector2(0, -1) KEY_DOWN, KEY_S: direction = Vector2(0, 1) KEY_LEFT, KEY_A: direction = Vector2(-1, 0) KEY_RIGHT, KEY_D: direction = Vector2(1, 0) KEY_R: init_level() return KEY_P: prev_level() return KEY_N: next_level() return if direction != Vector2.ZERO: move_player(direction) func move_player(direction: Vector2): var new_pos = player_pos + direction # 检查边界 if new_pos.x < 0 or new_pos.x >= map_width or new_pos.y < 0 or new_pos.y >= map_height: return var target_cell = level_data[new_pos.y][new_pos.x] # 检查是否撞墙 if target_cell == CellType.WALL: return # 检查是否推箱子 if target_cell == CellType.BOX or target_cell == CellType.BOX_ON_TARGET: var box_new_pos = new_pos + direction # 检查箱子新位置是否有效 if box_new_pos.x < 0 or box_new_pos.x >= map_width or box_new_pos.y < 0 or box_new_pos.y >= map_height: return var box_target_cell = level_data[box_new_pos.y][box_new_pos.x] # 箱子不能推到墙上或其他箱子上 if box_target_cell == CellType.WALL or box_target_cell == CellType.BOX or box_target_cell == CellType.BOX_ON_TARGET: return # 移动箱子 var was_on_target = (target_cell == CellType.BOX_ON_TARGET) var moving_to_target = (box_target_cell == CellType.TARGET) # 更新箱子原位置 if was_on_target: level_data[new_pos.y][new_pos.x] = CellType.TARGET else: level_data[new_pos.y][new_pos.x] = CellType.EMPTY # 更新箱子新位置 if moving_to_target: level_data[box_new_pos.y][box_new_pos.x] = CellType.BOX_ON_TARGET else: level_data[box_new_pos.y][box_new_pos.x] = CellType.BOX # 移动玩家 # 恢复玩家原位置(检查是否在目标点上) var level_strings = LEVELS[current_level] if player_pos.y < level_strings.size() and player_pos.x < level_strings[player_pos.y].length(): var original_char = level_strings[player_pos.y][player_pos.x] if original_char == '*': # 玩家原来在目标点上 level_data[player_pos.y][player_pos.x] = CellType.TARGET else: level_data[player_pos.y][player_pos.x] = CellType.EMPTY else: level_data[player_pos.y][player_pos.x] = CellType.EMPTY # 更新玩家位置 player_pos = new_pos level_data[player_pos.y][player_pos.x] = CellType.PLAYER # 增加步数 moves += 1 total_moves += 1 # 检查是否过关 check_win_condition() # 更新UI和重绘 update_ui() save_player_data() queue_redraw() func check_win_condition(): # 检查是否所有箱子都在目标点上 for y in range(map_height): for x in range(map_width): if level_data[y][x] == CellType.BOX: return # 还有箱子不在目标点上 # 所有箱子都在目标点上,过关! 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: current_level -= 1 init_level() func update_ui(): 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 # 绘制地图 for y in range(map_height): for x in range(map_width): var cell_x = start_x + x * CELL_SIZE var cell_y = start_y + y * CELL_SIZE var rect = Rect2(cell_x, cell_y, CELL_SIZE, CELL_SIZE) var cell_type = level_data[y][x] # 绘制单元格阴影 draw_rect(rect.grow(1), Color(0, 0, 0, 0.2), true) # 根据类型绘制不同效果 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) # 绘制网格线(淡色) 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