318 lines
7.4 KiB
GDScript
318 lines
7.4 KiB
GDScript
extends Panel
|
|
|
|
# 游戏常量
|
|
const CELL_SIZE = 40
|
|
|
|
# 地图元素
|
|
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 - 更复杂的布局
|
|
[
|
|
"############",
|
|
"#..........#",
|
|
"#..######..#",
|
|
"#..*....#..#",
|
|
"#..$....#..#",
|
|
"#.......#..#",
|
|
"#..#....#..#",
|
|
"#..*....#..#",
|
|
"#..$......@#",
|
|
"#..........#",
|
|
"############"
|
|
]
|
|
]
|
|
|
|
# 游戏变量
|
|
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
|
|
|
|
# 节点引用
|
|
@onready var game_area = $GameArea
|
|
@onready var level_label = $LevelLabel
|
|
@onready var moves_label = $MovesLabel
|
|
@onready var win_label = $WinLabel
|
|
|
|
func _ready():
|
|
# 设置游戏区域样式
|
|
game_area.modulate = Color(0.9, 0.9, 0.9)
|
|
|
|
# 初始化游戏
|
|
init_level()
|
|
|
|
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
|
|
|
|
# 检查是否过关
|
|
check_win_condition()
|
|
|
|
# 更新UI和重绘
|
|
update_ui()
|
|
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
|
|
win_label.visible = true
|
|
|
|
func next_level():
|
|
if current_level < LEVELS.size() - 1:
|
|
current_level += 1
|
|
init_level()
|
|
|
|
func prev_level():
|
|
if current_level > 0:
|
|
current_level -= 1
|
|
init_level()
|
|
|
|
func update_ui():
|
|
level_label.text = "关卡: " + str(current_level + 1)
|
|
moves_label.text = "步数: " + str(moves)
|
|
|
|
func _draw():
|
|
if not game_area:
|
|
return
|
|
|
|
# 获取游戏区域位置
|
|
var area_pos = game_area.position
|
|
|
|
# 计算起始绘制位置(居中)
|
|
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]
|
|
var color = CELL_COLORS[cell_type]
|
|
|
|
# 绘制单元格
|
|
draw_rect(rect, color, true)
|
|
|
|
# 绘制边框
|
|
draw_rect(rect, Color.BLACK, false, 1)
|
|
|
|
# 特殊处理:如果是玩家在目标点上,需要先绘制目标点
|
|
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)
|