From ca5685df52a2ebbe7c38aa76e8967a9882e758e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Thu, 28 Aug 2025 10:23:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B0=8F=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CopyItems/item_button.tscn | 4 +- GUI/MainMenuPanel.tscn | 9 + MainGame.tscn | 2 +- Scene/BigPanel/PlayGamePanel.tscn | 175 ++- Scene/SmallGame/2048Game.gd | 192 ++- Scene/SmallGame/2048Game.tscn | 65 +- Scene/SmallGame/PushBox.gd | 532 ++++++- Scene/SmallGame/PushBox.tscn | 159 ++- Scene/SmallGame/SnakeGame.gd | 414 +++++- Scene/SmallGame/SnakeGame.tscn | 125 +- Scene/SmallGame/Tetris.gd | 402 +++++- Scene/SmallGame/Tetris.tscn | 192 ++- Script/BigPanel/CropStorePanel.gd | 17 +- Script/BigPanel/CropWarehousePanel.gd | 18 +- Script/BigPanel/PlayGamePanel.gd | 26 + Script/BigPanel/PlayerBagPanel.gd | 18 +- Server/TCPGameServer.py | 12 +- .../ConsoleCommandsAPI.cpython-313.pyc | Bin 50945 -> 50945 bytes Server/crop_data_debug.json | 1071 -------------- Server/test/玩家农场/2143323382.json | 1071 ++++++++++++++ Server/test/玩家农场/3205788256.json | 1248 +++++++++++++++++ Test/playergamedata.json | 1 + assets/作物/咖啡豆/未成熟.webp.import | 34 + playergamedata.json | 32 + 24 files changed, 4569 insertions(+), 1250 deletions(-) delete mode 100644 Server/crop_data_debug.json create mode 100644 Server/test/玩家农场/2143323382.json create mode 100644 Server/test/玩家农场/3205788256.json create mode 100644 Test/playergamedata.json create mode 100644 assets/作物/咖啡豆/未成熟.webp.import create mode 100644 playergamedata.json diff --git a/CopyItems/item_button.tscn b/CopyItems/item_button.tscn index 35ff9ad..071a1b6 100644 --- a/CopyItems/item_button.tscn +++ b/CopyItems/item_button.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=3 format=3 uid="uid://ibl5wbbw3pwc"] +[gd_scene load_steps=2 format=3 uid="uid://ibl5wbbw3pwc"] [ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_e25nh"] -[ext_resource type="Texture2D" uid="uid://bl48i2h22htyd" path="res://assets/作物/人参/收获物.webp" id="2_abhds"] [node name="item_button" type="Button"] custom_minimum_size = Vector2(400, 400) @@ -14,7 +13,6 @@ script = ExtResource("1_e25nh") [node name="CropImage" type="Sprite2D" parent="."] position = Vector2(200, 200) scale = Vector2(1.5625, 1.5625) -texture = ExtResource("2_abhds") [node name="Title" type="Label" parent="."] layout_mode = 0 diff --git a/GUI/MainMenuPanel.tscn b/GUI/MainMenuPanel.tscn index 49e22c2..45d575f 100644 --- a/GUI/MainMenuPanel.tscn +++ b/GUI/MainMenuPanel.tscn @@ -87,6 +87,15 @@ offset_bottom = 718.0 theme_override_font_sizes/font_size = 30 text = "版本号:v1.0.1" +[node name="GameVersionLabel2" type="Label" parent="GUI"] +layout_mode = 0 +offset_left = 575.0 +offset_top = 676.0 +offset_right = 875.0 +offset_bottom = 718.0 +theme_override_font_sizes/font_size = 30 +text = "学习项目,偏休闲娱乐" + [node name="Developer" type="RichTextLabel" parent="GUI"] layout_mode = 0 offset_left = 1194.0 diff --git a/MainGame.tscn b/MainGame.tscn index 0e8f39a..2a888a2 100644 --- a/MainGame.tscn +++ b/MainGame.tscn @@ -505,7 +505,7 @@ texture = ExtResource("5_5b81d") expand_mode = 2 [node name="tip" type="RichTextLabel" parent="UI/GUI/GameInfoHBox3"] -custom_minimum_size = Vector2(300, 0) +custom_minimum_size = Vector2(500, 0) layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_outline_color = Color(0, 0, 0, 1) diff --git a/Scene/BigPanel/PlayGamePanel.tscn b/Scene/BigPanel/PlayGamePanel.tscn index c52ad9f..f97240f 100644 --- a/Scene/BigPanel/PlayGamePanel.tscn +++ b/Scene/BigPanel/PlayGamePanel.tscn @@ -48,11 +48,11 @@ size_flags_horizontal = 3 size_flags_vertical = 3 columns = 5 -[node name="VBox" type="VBoxContainer" parent="ScrollContainer/Grid"] +[node name="2048Game" type="VBoxContainer" parent="ScrollContainer/Grid"] layout_mode = 2 alignment = 1 -[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox"] +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/2048Game"] layout_mode = 2 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -61,24 +61,24 @@ theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 35 -text = "贪吃蛇" +text = "2048" horizontal_alignment = 1 vertical_alignment = 1 -[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox"] +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/2048Game"] layout_mode = 2 texture = ExtResource("2_wlugc") -[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox"] +[node name="2048GameButton" type="Button" parent="ScrollContainer/Grid/2048Game"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "点击游玩" -[node name="VBox2" type="VBoxContainer" parent="ScrollContainer/Grid"] +[node name="PushBox" type="VBoxContainer" parent="ScrollContainer/Grid"] layout_mode = 2 alignment = 1 -[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox2"] +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/PushBox"] layout_mode = 2 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -87,24 +87,24 @@ theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 35 -text = "贪吃蛇" +text = "推箱子" horizontal_alignment = 1 vertical_alignment = 1 -[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox2"] +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/PushBox"] layout_mode = 2 texture = ExtResource("2_wlugc") -[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox2"] +[node name="PushBoxButton" type="Button" parent="ScrollContainer/Grid/PushBox"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "点击游玩" -[node name="VBox3" type="VBoxContainer" parent="ScrollContainer/Grid"] +[node name="SnakeGame" type="VBoxContainer" parent="ScrollContainer/Grid"] layout_mode = 2 alignment = 1 -[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox3"] +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/SnakeGame"] layout_mode = 2 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -117,20 +117,20 @@ text = "贪吃蛇" horizontal_alignment = 1 vertical_alignment = 1 -[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox3"] +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/SnakeGame"] layout_mode = 2 texture = ExtResource("2_wlugc") -[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox3"] +[node name="SnakeGameButton" type="Button" parent="ScrollContainer/Grid/SnakeGame"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "点击游玩" -[node name="VBox4" type="VBoxContainer" parent="ScrollContainer/Grid"] +[node name="Tetris" type="VBoxContainer" parent="ScrollContainer/Grid"] layout_mode = 2 alignment = 1 -[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox4"] +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/Tetris"] layout_mode = 2 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -139,15 +139,15 @@ theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 35 -text = "贪吃蛇" +text = "俄罗斯方块" horizontal_alignment = 1 vertical_alignment = 1 -[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox4"] +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/Tetris"] layout_mode = 2 texture = ExtResource("2_wlugc") -[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox4"] +[node name="TetrisButton" type="Button" parent="ScrollContainer/Grid/Tetris"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "点击游玩" @@ -165,7 +165,7 @@ theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 35 -text = "贪吃蛇" +text = "敬请期待" horizontal_alignment = 1 vertical_alignment = 1 @@ -177,3 +177,138 @@ texture = ExtResource("2_wlugc") layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "点击游玩" + +[node name="VBox6" type="VBoxContainer" parent="ScrollContainer/Grid"] +layout_mode = 2 +alignment = 1 + +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox6"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 35 +text = "敬请期待" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox6"] +layout_mode = 2 +texture = ExtResource("2_wlugc") + +[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox6"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "点击游玩" + +[node name="VBox7" type="VBoxContainer" parent="ScrollContainer/Grid"] +layout_mode = 2 +alignment = 1 + +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox7"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 35 +text = "敬请期待" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox7"] +layout_mode = 2 +texture = ExtResource("2_wlugc") + +[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox7"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "点击游玩" + +[node name="VBox8" type="VBoxContainer" parent="ScrollContainer/Grid"] +layout_mode = 2 +alignment = 1 + +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox8"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 35 +text = "敬请期待" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox8"] +layout_mode = 2 +texture = ExtResource("2_wlugc") + +[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox8"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "点击游玩" + +[node name="VBox9" type="VBoxContainer" parent="ScrollContainer/Grid"] +layout_mode = 2 +alignment = 1 + +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox9"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 35 +text = "敬请期待" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox9"] +layout_mode = 2 +texture = ExtResource("2_wlugc") + +[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox9"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "点击游玩" + +[node name="VBox10" type="VBoxContainer" parent="ScrollContainer/Grid"] +layout_mode = 2 +alignment = 1 + +[node name="GameTitle" type="Label" parent="ScrollContainer/Grid/VBox10"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 35 +text = "敬请期待" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/Grid/VBox10"] +layout_mode = 2 +texture = ExtResource("2_wlugc") + +[node name="Button" type="Button" parent="ScrollContainer/Grid/VBox10"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "点击游玩" + +[connection signal="pressed" from="ScrollContainer/Grid/2048Game/2048GameButton" to="." method="_on_game_button_pressed"] +[connection signal="pressed" from="ScrollContainer/Grid/PushBox/PushBoxButton" to="." method="_on_push_box_button_pressed"] +[connection signal="pressed" from="ScrollContainer/Grid/SnakeGame/SnakeGameButton" to="." method="_on_snake_game_button_pressed"] +[connection signal="pressed" from="ScrollContainer/Grid/Tetris/TetrisButton" to="." method="_on_tetris_button_pressed"] diff --git a/Scene/SmallGame/2048Game.gd b/Scene/SmallGame/2048Game.gd index 26f0a5b..a76f6f3 100644 --- a/Scene/SmallGame/2048Game.gd +++ b/Scene/SmallGame/2048Game.gd @@ -4,6 +4,8 @@ extends Panel const GRID_SIZE = 4 const CELL_SIZE = 90 const CELL_MARGIN = 10 +const SWIPE_THRESHOLD = 50 +const DATA_FILE_PATH = "user://playergamedata.json" # 数字颜色配置 const NUMBER_COLORS = { @@ -18,7 +20,11 @@ const NUMBER_COLORS = { 256: Color(0.93, 0.80, 0.38), 512: Color(0.93, 0.78, 0.31), 1024: Color(0.93, 0.77, 0.25), - 2048: Color(0.93, 0.76, 0.18) + 2048: Color(0.93, 0.76, 0.18), + 4096: Color(0.93, 0.70, 0.15), + 8192: Color(0.93, 0.65, 0.12), + 16384: Color(0.93, 0.60, 0.10), + 32768: Color(0.93, 0.55, 0.08) } const TEXT_COLORS = { @@ -32,7 +38,11 @@ const TEXT_COLORS = { 256: Color.WHITE, 512: Color.WHITE, 1024: Color.WHITE, - 2048: Color.WHITE + 2048: Color.WHITE, + 4096: Color.WHITE, + 8192: Color.WHITE, + 16384: Color.WHITE, + 32768: Color.WHITE } # 游戏变量 @@ -42,6 +52,14 @@ var best_score = 0 var game_over = false var won = false var can_continue = true +var highest_tile = 0 +var games_played = 0 +var total_moves = 0 +var player_data = {} + +# 触摸控制变量 +var touch_start_pos = Vector2.ZERO +var is_touching = false # 节点引用 @onready var game_board = $GameBoard @@ -49,11 +67,15 @@ var can_continue = true @onready var best_label = $BestLabel @onready var game_over_label = $GameOverLabel @onready var win_label = $WinLabel +@onready var stats_label = $StatsLabel func _ready(): # 设置游戏板样式 game_board.modulate = Color(0.7, 0.6, 0.5) + # 加载玩家数据 + load_player_data() + # 初始化游戏 init_game() @@ -63,6 +85,7 @@ func init_game(): won = false can_continue = true score = 0 + games_played += 1 # 初始化网格 grid.clear() @@ -83,6 +106,7 @@ func init_game(): queue_redraw() func _input(event): + # 键盘输入 if event is InputEventKey and event.pressed: if game_over: if event.keycode == KEY_R: @@ -111,10 +135,28 @@ func _input(event): return if moved: - add_random_number() - update_ui() - check_game_state() - queue_redraw() + handle_successful_move() + + # 触摸输入 + elif event is InputEventScreenTouch: + if event.pressed: + touch_start_pos = event.position + is_touching = true + else: + if is_touching: + handle_swipe(event.position) + is_touching = false + + # 鼠标输入(用于桌面测试) + elif event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_LEFT: + if event.pressed: + touch_start_pos = event.position + is_touching = true + else: + if is_touching: + handle_swipe(event.position) + is_touching = false func move_left() -> bool: var moved = false @@ -216,14 +258,53 @@ func add_random_number(): var value = 2 if randf() < 0.9 else 4 grid[random_cell.y][random_cell.x] = value +func handle_successful_move(): + total_moves += 1 + add_random_number() + update_ui() + check_game_state() + save_player_data() + queue_redraw() + +func handle_swipe(end_pos: Vector2): + if game_over or (won and not can_continue): + return + + var delta = end_pos - touch_start_pos + var moved = false + + if abs(delta.x) > SWIPE_THRESHOLD or abs(delta.y) > SWIPE_THRESHOLD: + if abs(delta.x) > abs(delta.y): + # 水平滑动 + if delta.x > 0: + moved = move_right() + else: + moved = move_left() + else: + # 垂直滑动 + if delta.y > 0: + moved = move_down() + else: + moved = move_up() + + if moved: + handle_successful_move() + func check_game_state(): - # 检查是否达到2048 + # 更新最高数字 + for y in range(GRID_SIZE): + for x in range(GRID_SIZE): + if grid[y][x] > highest_tile: + highest_tile = grid[y][x] + + # 检查是否达到2048或更高目标 if not won: for y in range(GRID_SIZE): for x in range(GRID_SIZE): if grid[y][x] == 2048: won = true can_continue = false + win_label.text = "恭喜!达到2048!\n按C继续挑战更高目标" win_label.visible = true return @@ -257,38 +338,102 @@ func can_move() -> bool: func update_ui(): score_label.text = "分数: " + str(score) best_label.text = "最高分: " + str(best_score) + if stats_label: + stats_label.text = "游戏次数: " + str(games_played) + " | 总步数: " + str(total_moves) func hide_labels(): game_over_label.visible = false win_label.visible = false +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("2048"): + var game_data = player_data["2048"] + best_score = game_data.get("best_score", 0) + games_played = game_data.get("games_played", 0) + highest_tile = game_data.get("highest_tile", 0) + total_moves = game_data.get("total_moves", 0) + +func save_player_data(): + if not player_data.has("2048"): + player_data["2048"] = {} + + player_data["2048"]["best_score"] = best_score + player_data["2048"]["current_score"] = score + player_data["2048"]["games_played"] = games_played + player_data["2048"]["highest_tile"] = highest_tile + player_data["2048"]["total_moves"] = total_moves + + # 更新全局数据 + 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_board: return + # 绘制背景渐变 + var gradient = Gradient.new() + gradient.add_point(0.0, Color(0.2, 0.3, 0.5, 0.8)) + gradient.add_point(1.0, Color(0.1, 0.2, 0.4, 0.9)) + draw_rect(Rect2(Vector2.ZERO, size), gradient.sample(0.5), true) + # 获取游戏板位置 var board_pos = game_board.position - # 绘制网格背景 + # 绘制游戏板阴影 + var shadow_offset = Vector2(4, 4) + var board_rect = Rect2(board_pos + shadow_offset, game_board.size) + draw_rect(board_rect, Color(0, 0, 0, 0.3), true, 8) + + # 绘制游戏板背景 + board_rect = Rect2(board_pos, game_board.size) + draw_rect(board_rect, Color(0.7, 0.6, 0.5, 0.9), true, 8) + + # 绘制网格 for y in range(GRID_SIZE): for x in range(GRID_SIZE): var cell_x = board_pos.x + x * (CELL_SIZE + CELL_MARGIN) + CELL_MARGIN var cell_y = board_pos.y + y * (CELL_SIZE + CELL_MARGIN) + CELL_MARGIN var rect = Rect2(cell_x, cell_y, CELL_SIZE, CELL_SIZE) + # 绘制单元格阴影 + draw_rect(rect.grow(2), Color(0, 0, 0, 0.2), true) + # 绘制单元格背景 - draw_rect(rect, Color(0.8, 0.7, 0.6), true) + draw_rect(rect, Color(0.8, 0.7, 0.6, 0.8), true) # 绘制数字 var value = grid[y][x] if value > 0: - # 绘制数字背景 + # 绘制数字背景(带渐变效果) var bg_color = NUMBER_COLORS.get(value, Color.GOLD) draw_rect(rect, bg_color, true) + # 绘制高光效果 + var highlight_rect = Rect2(rect.position, Vector2(rect.size.x, rect.size.y * 0.3)) + var highlight_color = Color(1, 1, 1, 0.3) + draw_rect(highlight_rect, highlight_color, true) + # 绘制数字文本 var text = str(value) - var font_size = 24 if value < 100 else (20 if value < 1000 else 16) + var font_size = 24 if value < 100 else (20 if value < 1000 else (16 if value < 10000 else 14)) var text_color = TEXT_COLORS.get(value, Color.WHITE) # 获取默认字体 @@ -303,5 +448,30 @@ func _draw(): cell_y + (CELL_SIZE - text_size.y) / 2 + text_size.y ) + # 绘制文本阴影 + draw_string(font, text_pos + Vector2(1, 1), text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.5)) # 绘制文本 draw_string(font, text_pos, text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color) + + +#退出2048游戏界面 +func _on_quit_button_pressed() -> void: + self.hide() + get_parent().remove_child(self) + queue_free() + pass + +#手机端继续游戏按钮 +func _on_continue_button_pressed() -> void: + if won and not can_continue: + can_continue = true + win_label.visible = false + return + pass + +#手机端重置游戏按钮 +func _on_reast_button_pressed() -> void: + if game_over: + init_game() + return + pass diff --git a/Scene/SmallGame/2048Game.tscn b/Scene/SmallGame/2048Game.tscn index 2188250..404b122 100644 --- a/Scene/SmallGame/2048Game.tscn +++ b/Scene/SmallGame/2048Game.tscn @@ -14,10 +14,10 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -200.0 -offset_top = -200.0 -offset_right = 200.0 -offset_bottom = 200.0 +offset_left = -217.0 +offset_top = -250.0 +offset_right = 183.0 +offset_bottom = 150.0 grow_horizontal = 2 grow_vertical = 2 @@ -90,13 +90,64 @@ anchor_left = 1.0 anchor_top = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = -300.0 -offset_top = -150.0 +offset_left = -350.0 +offset_top = -180.0 offset_right = -50.0 offset_bottom = -50.0 grow_horizontal = 0 grow_vertical = 0 text = "操作说明: WASD或方向键 - 移动 +滑动屏幕 - 移动(手机) R - 重新开始 -C - 继续游戏(达到2048后)" +C - 继续游戏(达到2048后) + +目标: 合并数字达到2048或更高!" + +[node name="StatsLabel" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 50.0 +offset_top = -40.0 +offset_right = 300.0 +offset_bottom = -10.0 +grow_vertical = 0 +text = "游戏次数: 0 | 总步数: 0" + +[node name="ReastButton" type="Button" parent="."] +layout_mode = 0 +offset_left = 19.0 +offset_top = 19.0 +offset_right = 97.0 +offset_bottom = 76.0 +theme_override_font_sizes/font_size = 35 +text = "重置" + +[node name="ContinueButton" type="Button" parent="."] +layout_mode = 0 +offset_left = 97.0 +offset_top = 19.0 +offset_right = 175.0 +offset_bottom = 76.0 +theme_override_font_sizes/font_size = 35 +text = "继续" + +[node name="QuitButton" type="Button" parent="."] +self_modulate = Color(1, 0.247059, 0, 1) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -129.0 +offset_right = -46.0 +offset_bottom = 57.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "关闭" + +[connection signal="pressed" from="ReastButton" to="." method="_on_reast_button_pressed"] +[connection signal="pressed" from="ContinueButton" to="." method="_on_continue_button_pressed"] +[connection signal="pressed" from="QuitButton" to="." method="_on_quit_button_pressed"] diff --git a/Scene/SmallGame/PushBox.gd b/Scene/SmallGame/PushBox.gd index 8f72743..a279267 100644 --- a/Scene/SmallGame/PushBox.gd +++ b/Scene/SmallGame/PushBox.gd @@ -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 diff --git a/Scene/SmallGame/PushBox.tscn b/Scene/SmallGame/PushBox.tscn index 9dc6cf3..1932dba 100644 --- a/Scene/SmallGame/PushBox.tscn +++ b/Scene/SmallGame/PushBox.tscn @@ -73,13 +73,170 @@ anchor_top = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 offset_left = -300.0 -offset_top = -150.0 +offset_top = -180.0 offset_right = -50.0 offset_bottom = -50.0 grow_horizontal = 0 grow_vertical = 0 text = "操作说明: WASD或方向键 - 移动 +虚拟按钮 - 移动(手机) R - 重新开始当前关卡 N - 下一关(过关后) P - 上一关" + +[node name="ObjectiveLabel" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -1402.0 +offset_right = -1051.0 +offset_bottom = 35.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 25 +text = "目标: 将所有箱子推到目标位置!" +horizontal_alignment = 1 + +[node name="StatsLabel" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 50.0 +offset_top = -40.0 +offset_right = 400.0 +offset_bottom = -10.0 +grow_vertical = 0 +text = "完成关卡: 0 | 总步数: 0" + +[node name="VirtualControls" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -1289.0 +offset_top = -472.0 +offset_right = -1089.0 +offset_bottom = -272.0 +grow_horizontal = 0 +grow_vertical = 0 + +[node name="UpButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(90, 90) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -144.0 +offset_top = -72.0 +offset_right = -54.0 +offset_bottom = 18.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 35 +text = "W" + +[node name="DownButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(90, 90) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -144.0 +offset_top = 108.0 +offset_right = -54.0 +offset_bottom = 198.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 35 +text = "S" + +[node name="LeftButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(90, 90) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -236.0 +offset_top = 18.0 +offset_right = -146.0 +offset_bottom = 108.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 35 +text = "A" + +[node name="RightButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(90, 90) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -54.0 +offset_top = 18.0 +offset_right = 36.0 +offset_bottom = 108.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 35 +text = "D" + +[node name="ResetButton" type="Button" parent="VirtualControls"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = 819.0 +offset_top = 62.0 +offset_right = 902.0 +offset_bottom = 119.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "重置" + +[node name="NextButton" type="Button" parent="VirtualControls"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = 819.0 +offset_top = -110.0 +offset_right = 902.0 +offset_bottom = -53.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "下一关" + +[node name="LastButton" type="Button" parent="VirtualControls"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = 819.0 +offset_top = -24.0 +offset_right = 902.0 +offset_bottom = 33.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "上一关" + +[node name="QuitButton" type="Button" parent="VirtualControls"] +self_modulate = Color(1, 0.247059, 0, 1) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = 947.0 +offset_top = -248.0 +offset_right = 1030.0 +offset_bottom = -191.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "关闭" + +[connection signal="pressed" from="VirtualControls/NextButton" to="." method="_on_next_button_pressed"] +[connection signal="pressed" from="VirtualControls/LastButton" to="." method="_on_last_button_pressed"] +[connection signal="pressed" from="VirtualControls/QuitButton" to="." method="_on_quit_button_pressed"] diff --git a/Scene/SmallGame/SnakeGame.gd b/Scene/SmallGame/SnakeGame.gd index d8fe41c..c09d1a0 100644 --- a/Scene/SmallGame/SnakeGame.gd +++ b/Scene/SmallGame/SnakeGame.gd @@ -4,6 +4,7 @@ extends Panel const GRID_SIZE = 20 const GRID_WIDTH = 30 const GRID_HEIGHT = 30 +const DATA_FILE_PATH = "user://playergamedata.json" # 方向枚举 enum Direction { @@ -13,36 +14,67 @@ enum Direction { RIGHT } +# 食物类型枚举 +enum FoodType { + NORMAL, # 普通食物 +10分 + GOLDEN, # 金色食物 +50分 + SPEED, # 加速食物 +20分,临时加速 + SLOW, # 减速食物 +30分,临时减速 + BONUS # 奖励食物 +100分 +} + # 游戏变量 var snake_body = [] var snake_direction = Direction.RIGHT var next_direction = Direction.RIGHT var food_position = Vector2() +var food_type = FoodType.NORMAL var score = 0 +var best_score = 0 +var level = 1 +var speed_multiplier = 1.0 +var speed_effect_timer = 0.0 var game_over = false +var game_started = false # 添加游戏开始状态 +var obstacles = [] +var games_played = 0 +var total_food_eaten = 0 +var player_data = {} # 节点引用 @onready var game_area = $GameArea @onready var score_label = $ScoreLabel @onready var game_over_label = $GameOverLabel @onready var game_timer = $GameTimer +@onready var virtual_controls = $VirtualControls func _ready(): # 连接定时器信号 game_timer.timeout.connect(_on_game_timer_timeout) # 设置游戏区域样式 - game_area.modulate = Color(0.2, 0.2, 0.2, 1.0) + game_area.modulate = Color(0.1, 0.1, 0.15, 0.9) - # 初始化游戏 - init_game() + # 连接虚拟按键信号 + setup_virtual_controls() + + # 加载玩家数据 + load_player_data() + + # 显示游戏开始界面 + show_start_screen() func init_game(): # 重置游戏状态 game_over = false + game_started = true score = 0 + level = 1 + speed_multiplier = 1.0 + speed_effect_timer = 0.0 snake_direction = Direction.RIGHT next_direction = Direction.RIGHT + games_played += 1 # 初始化蛇身 snake_body.clear() @@ -50,42 +82,74 @@ func init_game(): snake_body.append(Vector2(4, 5)) snake_body.append(Vector2(3, 5)) - # 生成食物 + # 清空障碍物 + obstacles.clear() + + # 生成食物和障碍物 generate_food() + generate_obstacles() # 更新UI update_score() game_over_label.visible = false - # 启动定时器 + # 设置定时器速度 + game_timer.wait_time = 0.2 / speed_multiplier game_timer.start() func _input(event): if event is InputEventKey and event.pressed: + # 游戏未开始时,按Q键开始游戏 + if not game_started: + if event.keycode == KEY_Q: + init_game() + return + if game_over: if event.keycode == KEY_SPACE: init_game() + elif event.keycode == KEY_Q: + init_game() return # 控制蛇的方向 match event.keycode: KEY_UP, KEY_W: - if snake_direction != Direction.DOWN: - next_direction = Direction.UP + change_direction(Direction.UP) KEY_DOWN, KEY_S: - if snake_direction != Direction.UP: - next_direction = Direction.DOWN + change_direction(Direction.DOWN) KEY_LEFT, KEY_A: - if snake_direction != Direction.RIGHT: - next_direction = Direction.LEFT + change_direction(Direction.LEFT) KEY_RIGHT, KEY_D: - if snake_direction != Direction.LEFT: - next_direction = Direction.RIGHT + change_direction(Direction.RIGHT) + +func change_direction(new_direction: Direction): + # 防止蛇反向移动 + match new_direction: + Direction.UP: + if snake_direction != Direction.DOWN: + next_direction = Direction.UP + Direction.DOWN: + if snake_direction != Direction.UP: + next_direction = Direction.DOWN + Direction.LEFT: + if snake_direction != Direction.RIGHT: + next_direction = Direction.LEFT + Direction.RIGHT: + if snake_direction != Direction.LEFT: + next_direction = Direction.RIGHT func _on_game_timer_timeout(): - if game_over: + if not game_started or game_over: return + # 处理速度效果 + if speed_effect_timer > 0: + speed_effect_timer -= game_timer.wait_time + if speed_effect_timer <= 0: + speed_multiplier = 1.0 + game_timer.wait_time = 0.2 / speed_multiplier + # 更新方向 snake_direction = next_direction @@ -118,12 +182,14 @@ func move_snake(): # 检查是否吃到食物 if new_head == food_position: - # 增加分数 - score += 10 - update_score() + # 根据食物类型增加分数和效果 + eat_food() # 生成新食物 generate_food() + + # 检查等级提升 + check_level_up() else: # 移除尾部 snake_body.pop_back() @@ -143,6 +209,13 @@ func check_collisions(): game_over = true show_game_over() return + + # 检查障碍物碰撞 + for obstacle in obstacles: + if head == obstacle: + game_over = true + show_game_over() + return func generate_food(): var attempts = 0 @@ -152,56 +225,337 @@ func generate_food(): randi() % GRID_HEIGHT ) - # 确保食物不在蛇身上 - var food_on_snake = false + # 确保食物不在蛇身上和障碍物上 + var food_blocked = false for segment in snake_body: if segment == food_position: - food_on_snake = true + food_blocked = true break - if not food_on_snake: + if not food_blocked: + for obstacle in obstacles: + if obstacle == food_position: + food_blocked = true + break + + if not food_blocked: break attempts += 1 + + # 随机生成食物类型 + var rand = randf() + if rand < 0.6: # 60% 普通食物 + food_type = FoodType.NORMAL + elif rand < 0.75: # 15% 金色食物 + food_type = FoodType.GOLDEN + elif rand < 0.85: # 10% 加速食物 + food_type = FoodType.SPEED + elif rand < 0.95: # 10% 减速食物 + food_type = FoodType.SLOW + else: # 5% 奖励食物 + food_type = FoodType.BONUS func update_score(): - score_label.text = "分数: " + str(score) + score_label.text = "🐍 分数: " + str(score) + "\n🏆 最高分: " + str(best_score) + "\n⭐ 等级: " + str(level) + "\n🎮 游戏次数: " + str(games_played) func show_game_over(): game_timer.stop() + game_started = false + if score > best_score: + best_score = score + update_score() + save_player_data() + game_over_label.text = "🎮 游戏结束\n🏆 分数: " + str(score) + "\n⭐ 等级: " + str(level) + "\n\n🔄 按Q键或空格重新开始" game_over_label.visible = true +func eat_food(): + total_food_eaten += 1 + match food_type: + FoodType.NORMAL: + score += 10 + FoodType.GOLDEN: + score += 50 + FoodType.SPEED: + score += 20 + speed_multiplier = 1.5 + speed_effect_timer = 5.0 + game_timer.wait_time = 0.2 / speed_multiplier + FoodType.SLOW: + score += 30 + speed_multiplier = 0.7 + speed_effect_timer = 5.0 + game_timer.wait_time = 0.2 / speed_multiplier + FoodType.BONUS: + score += 100 + update_score() + +func check_level_up(): + var new_level = (total_food_eaten / 10) + 1 + if new_level > level: + level = new_level + generate_obstacles() # 每升级增加障碍物 + +func generate_obstacles(): + # 根据等级生成障碍物 + var obstacle_count = min(level - 1, 10) # 最多10个障碍物 + obstacles.clear() + + for i in range(obstacle_count): + var attempts = 0 + while attempts < 50: + var obstacle_pos = Vector2( + randi() % GRID_WIDTH, + randi() % GRID_HEIGHT + ) + + # 确保障碍物不在蛇身、食物或其他障碍物上 + var blocked = false + for segment in snake_body: + if segment == obstacle_pos: + blocked = true + break + + if not blocked and obstacle_pos == food_position: + blocked = true + + if not blocked: + for existing_obstacle in obstacles: + if existing_obstacle == obstacle_pos: + blocked = true + break + + if not blocked: + obstacles.append(obstacle_pos) + break + + attempts += 1 + +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 restart_btn = virtual_controls.get_node("RestartButton") + + if up_btn: + up_btn.pressed.connect(_on_virtual_button_pressed.bind(Direction.UP)) + if down_btn: + down_btn.pressed.connect(_on_virtual_button_pressed.bind(Direction.DOWN)) + if left_btn: + left_btn.pressed.connect(_on_virtual_button_pressed.bind(Direction.LEFT)) + if right_btn: + right_btn.pressed.connect(_on_virtual_button_pressed.bind(Direction.RIGHT)) + if restart_btn: + restart_btn.pressed.connect(_on_restart_button_pressed) + +func _on_virtual_button_pressed(direction: Direction): + if game_started and not game_over: + change_direction(direction) + +func _on_restart_button_pressed(): + if not game_started or game_over: + init_game() + +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("snake"): + var game_data = player_data["snake"] + best_score = game_data.get("best_score", 0) + games_played = game_data.get("games_played", 0) + total_food_eaten = game_data.get("total_food_eaten", 0) + +func show_start_screen(): + # 重置状态 + game_started = false + game_over = false + + # 初始化蛇身用于显示 + snake_body.clear() + snake_body.append(Vector2(5, 5)) + snake_body.append(Vector2(4, 5)) + snake_body.append(Vector2(3, 5)) + + # 清空障碍物 + obstacles.clear() + + # 生成初始食物 + food_position = Vector2(10, 10) + food_type = FoodType.NORMAL + + # 显示开始提示 + game_over_label.text = "🐍 贪吃蛇游戏 🐍\n\n🏆 最高分数: " + str(best_score) + "\n🎯 游戏次数: " + str(games_played) + "\n\n🎮 按Q键开始游戏\n\n🎯 操作说明:\n方向键/WASD - 控制方向\n\n🍎 食物类型:\n🔴 普通食物 +10分\n🟡 金色食物 +50分\n🔵 加速食物 +20分\n🟣 减速食物 +30分\n🌈 奖励食物 +100分" + game_over_label.visible = true + + # 停止定时器 + game_timer.stop() + + update_score() + queue_redraw() + +func save_player_data(): + if not player_data.has("snake"): + player_data["snake"] = {} + + player_data["snake"]["best_score"] = best_score + player_data["snake"]["current_score"] = score + player_data["snake"]["games_played"] = games_played + player_data["snake"]["total_food_eaten"] = total_food_eaten + player_data["snake"]["max_level_reached"] = 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.05, 0.1, 0.2, 0.95)) + gradient.add_point(0.5, Color(0.1, 0.15, 0.25, 0.9)) + gradient.add_point(1.0, Color(0.15, 0.2, 0.3, 0.95)) + draw_rect(Rect2(Vector2.ZERO, size), gradient.sample(0.5), true) + # 获取游戏区域的位置和大小 var area_pos = game_area.position var area_size = game_area.size + # 绘制游戏区域阴影 + var shadow_offset = Vector2(4, 4) + var area_rect = Rect2(area_pos + shadow_offset, area_size) + draw_rect(area_rect, Color(0, 0, 0, 0.3), true) + + # 绘制游戏区域背景 + area_rect = Rect2(area_pos, area_size) + draw_rect(area_rect, Color(0.08, 0.12, 0.18, 0.9), true) + # 计算网格大小 var cell_width = area_size.x / GRID_WIDTH var cell_height = area_size.y / GRID_HEIGHT + # 绘制网格线(淡色) + for x in range(GRID_WIDTH + 1): + var start_pos = Vector2(area_pos.x + x * cell_width, area_pos.y) + var end_pos = Vector2(area_pos.x + x * cell_width, area_pos.y + area_size.y) + draw_line(start_pos, end_pos, Color(0.3, 0.3, 0.4, 0.3), 1) + + for y in range(GRID_HEIGHT + 1): + var start_pos = Vector2(area_pos.x, area_pos.y + y * cell_height) + var end_pos = Vector2(area_pos.x + area_size.x, area_pos.y + y * cell_height) + draw_line(start_pos, end_pos, Color(0.3, 0.3, 0.4, 0.3), 1) + + # 绘制障碍物 + for obstacle in obstacles: + var rect = Rect2( + area_pos.x + obstacle.x * cell_width, + area_pos.y + obstacle.y * cell_height, + cell_width - 2, + cell_height - 2 + ) + # 绘制立体障碍物效果 + draw_rect(rect, Color(0.4, 0.2, 0.1), true) + # 高光 + var highlight_rect = Rect2(rect.position + Vector2(1, 1), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(highlight_rect, Color(0.6, 0.4, 0.2, 0.8), true) + # 绘制蛇身 for i in range(snake_body.size()): var segment = snake_body[i] var rect = Rect2( area_pos.x + segment.x * cell_width, area_pos.y + segment.y * cell_height, - cell_width - 1, - cell_height - 1 + cell_width - 2, + cell_height - 2 ) - # 头部用不同颜色 - var color = Color.GREEN if i == 0 else Color.LIME_GREEN - draw_rect(rect, color) + if i == 0: # 头部 + # 绘制蛇头(圆形,带渐变) + var center = rect.get_center() + var radius = min(rect.size.x, rect.size.y) * 0.4 + # 阴影 + 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.6, Color(0.4, 1.0, 0.4, 0.7)) + # 眼睛 + var eye_size = radius * 0.2 + draw_circle(center + Vector2(-eye_size, -eye_size), eye_size * 0.5, Color.BLACK) + draw_circle(center + Vector2(eye_size, -eye_size), eye_size * 0.5, Color.BLACK) + else: # 身体 + # 绘制蛇身(渐变色) + var body_color = Color.LIME_GREEN.lerp(Color.DARK_GREEN, float(i) / snake_body.size()) + draw_rect(rect, body_color, true) + # 高光 + var highlight_rect = Rect2(rect.position + Vector2(1, 1), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(highlight_rect, Color(1, 1, 1, 0.3), true) # 绘制食物 var food_rect = Rect2( area_pos.x + food_position.x * cell_width, area_pos.y + food_position.y * cell_height, - cell_width - 1, - cell_height - 1 + cell_width - 2, + cell_height - 2 ) - draw_rect(food_rect, Color.RED) + + # 根据食物类型绘制不同效果 + var food_center = food_rect.get_center() + var food_radius = min(food_rect.size.x, food_rect.size.y) * 0.4 + + match food_type: + FoodType.NORMAL: + # 普通红色食物 + draw_circle(food_center + Vector2(1, 1), food_radius, Color(0, 0, 0, 0.3)) # 阴影 + draw_circle(food_center, food_radius, Color.RED) + draw_circle(food_center - Vector2(1, 1), food_radius * 0.6, Color(1, 0.5, 0.5, 0.8)) # 高光 + FoodType.GOLDEN: + # 金色食物(闪烁效果) + var pulse = sin(Time.get_ticks_msec() * 0.008) * 0.2 + 0.8 + draw_circle(food_center + Vector2(1, 1), food_radius, Color(0, 0, 0, 0.3)) # 阴影 + draw_circle(food_center, food_radius, Color.GOLD * pulse) + draw_circle(food_center - Vector2(1, 1), food_radius * 0.6, Color.YELLOW) # 高光 + FoodType.SPEED: + # 蓝色加速食物 + draw_circle(food_center + Vector2(1, 1), food_radius, Color(0, 0, 0, 0.3)) # 阴影 + draw_circle(food_center, food_radius, Color.CYAN) + draw_circle(food_center - Vector2(1, 1), food_radius * 0.6, Color.LIGHT_BLUE) # 高光 + FoodType.SLOW: + # 紫色减速食物 + draw_circle(food_center + Vector2(1, 1), food_radius, Color(0, 0, 0, 0.3)) # 阴影 + draw_circle(food_center, food_radius, Color.PURPLE) + draw_circle(food_center - Vector2(1, 1), food_radius * 0.6, Color.MAGENTA) # 高光 + FoodType.BONUS: + # 彩虹奖励食物(旋转彩虹效果) + var rainbow_time = Time.get_ticks_msec() * 0.003 + var rainbow_color = Color.from_hsv(fmod(rainbow_time, 1.0), 1.0, 1.0) + draw_circle(food_center + Vector2(1, 1), food_radius, Color(0, 0, 0, 0.3)) # 阴影 + draw_circle(food_center, food_radius, rainbow_color) + draw_circle(food_center - Vector2(1, 1), food_radius * 0.6, Color.WHITE) # 高光 + + +func _on_quit_button_pressed() -> void: + self.hide() + get_parent().remove_child(self) + queue_free() + pass diff --git a/Scene/SmallGame/SnakeGame.tscn b/Scene/SmallGame/SnakeGame.tscn index 65d841b..2dd9965 100644 --- a/Scene/SmallGame/SnakeGame.tscn +++ b/Scene/SmallGame/SnakeGame.tscn @@ -26,11 +26,11 @@ layout_mode = 1 anchors_preset = 2 anchor_top = 1.0 anchor_bottom = 1.0 -offset_left = 20.0 -offset_top = -50.0 -offset_right = 200.0 -offset_bottom = -20.0 +offset_top = -726.0 +offset_right = 180.0 +offset_bottom = -684.0 grow_vertical = 0 +theme_override_font_sizes/font_size = 30 text = "分数: 0" [node name="GameOverLabel" type="Label" parent="."] @@ -55,3 +55,120 @@ vertical_alignment = 1 [node name="GameTimer" type="Timer" parent="."] wait_time = 0.2 autostart = true + +[node name="VirtualControls" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 160.0 +offset_bottom = 160.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="UpButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -529.5 +offset_top = 114.0 +offset_right = -449.5 +offset_bottom = 194.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 35 +text = "W" + +[node name="TestButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -529.5 +offset_top = 194.0 +offset_right = -449.5 +offset_bottom = 274.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 35 +disabled = true + +[node name="DownButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -529.5 +offset_top = 274.0 +offset_right = -449.5 +offset_bottom = 354.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 35 +text = "S" + +[node name="LeftButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 4 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_left = 94.0 +offset_top = -169.0 +offset_right = 174.0 +offset_bottom = -89.0 +grow_vertical = 2 +theme_override_font_sizes/font_size = 35 +text = "A" + +[node name="RightButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 6 +anchor_left = 1.0 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_left = -1151.0 +offset_top = -169.0 +offset_right = -1071.0 +offset_bottom = -89.0 +grow_horizontal = 0 +grow_vertical = 2 +theme_override_font_sizes/font_size = 35 +text = "D" + +[node name="RestartButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -1291.0 +offset_top = -276.0 +offset_right = -1086.0 +offset_bottom = -196.0 +grow_horizontal = 0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 35 +text = "🔄 重新开始" + +[node name="QuitButton" type="Button" parent="."] +self_modulate = Color(1, 0.247059, 0, 1) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -137.0 +offset_top = -1.52588e-05 +offset_right = -54.0 +offset_bottom = 57.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "关闭" + +[connection signal="pressed" from="QuitButton" to="." method="_on_quit_button_pressed"] diff --git a/Scene/SmallGame/Tetris.gd b/Scene/SmallGame/Tetris.gd index 99093ac..f6393ec 100644 --- a/Scene/SmallGame/Tetris.gd +++ b/Scene/SmallGame/Tetris.gd @@ -4,6 +4,15 @@ extends Panel const BOARD_WIDTH = 10 const BOARD_HEIGHT = 20 const CELL_SIZE = 30 +const DATA_FILE_PATH = "user://playergamedata.json" + +# 特殊方块类型 +enum SpecialType { + NORMAL, + BOMB, # 炸弹方块,消除周围方块 + LINE, # 直线方块,消除整行 + RAINBOW # 彩虹方块,消除同色方块 +} # 方块类型 enum PieceType { @@ -68,7 +77,14 @@ var score = 0 var level = 1 var lines_cleared = 0 var game_over = false +var game_started = false # 添加游戏开始状态 var drop_time = 1.0 +var best_score = 0 +var games_played = 0 +var total_lines_cleared = 0 +var combo_count = 0 +var special_pieces = [] # 存储特殊方块位置和类型 +var player_data = {} # 节点引用 @onready var game_area = $GameArea @@ -78,28 +94,39 @@ var drop_time = 1.0 @onready var lines_label = $LinesLabel @onready var game_over_label = $GameOverLabel @onready var drop_timer = $DropTimer +@onready var virtual_controls = $VirtualControls func _ready(): # 连接定时器信号 drop_timer.timeout.connect(_on_drop_timer_timeout) # 设置游戏区域样式 - game_area.modulate = Color(0.1, 0.1, 0.1, 1.0) - next_piece_area.modulate = Color(0.2, 0.2, 0.2, 1.0) + game_area.modulate = Color(0.08, 0.12, 0.18, 0.9) + next_piece_area.modulate = Color(0.1, 0.15, 0.25, 0.9) - # 初始化游戏 - init_game() + # 设置虚拟控制 + setup_virtual_controls() + + # 加载玩家数据 + load_player_data() + + # 显示游戏开始界面 + show_start_screen() func init_game(): # 重置游戏状态 game_over = false + game_started = true score = 0 level = 1 lines_cleared = 0 drop_time = 1.0 + combo_count = 0 + games_played += 1 # 初始化游戏板 board.clear() + special_pieces.clear() for y in range(BOARD_HEIGHT): var row = [] for x in range(BOARD_WIDTH): @@ -120,9 +147,17 @@ func init_game(): func _input(event): if event is InputEventKey and event.pressed: + # 游戏未开始时,按Q键开始游戏 + if not game_started: + if event.keycode == KEY_Q: + init_game() + return + if game_over: if event.keycode == KEY_SPACE: init_game() + elif event.keycode == KEY_Q: + init_game() return if not current_piece: @@ -131,15 +166,34 @@ func _input(event): # 控制方块 match event.keycode: KEY_A: - move_piece(-1, 0) + handle_move_left() KEY_D: - move_piece(1, 0) + handle_move_right() KEY_S: - move_piece(0, 1) + handle_move_down() KEY_W: - rotate_piece() + handle_rotate() KEY_SPACE: - drop_piece() + handle_drop() + +# 控制函数 +func handle_move_left(): + move_piece(-1, 0) + +func handle_move_right(): + move_piece(1, 0) + +func handle_move_down(): + move_piece(0, 1) + +func handle_rotate(): + rotate_piece() + +func handle_drop(): + drop_piece() + +func handle_restart(): + init_game() func spawn_new_piece(): current_piece = { @@ -222,6 +276,9 @@ func clear_lines(): if is_full: lines_to_clear.append(y) + # 处理特殊方块效果 + process_special_pieces(lines_to_clear) + # 清除行并下移 for line_y in lines_to_clear: board.erase(board[line_y]) @@ -232,15 +289,37 @@ func clear_lines(): # 更新分数和等级 if lines_to_clear.size() > 0: - lines_cleared += lines_to_clear.size() - score += lines_to_clear.size() * 100 * level + # 连击系统 + combo_count += 1 + var combo_bonus = combo_count * 50 - # 每10行提升一个等级 + lines_cleared += lines_to_clear.size() + total_lines_cleared += lines_to_clear.size() + + # 计算分数(包含连击奖励) + var base_score = lines_to_clear.size() * 100 * level + var line_bonus = 0 + match lines_to_clear.size(): + 1: line_bonus = 0 + 2: line_bonus = 300 + 3: line_bonus = 500 + 4: line_bonus = 800 # 俄罗斯方块 + + score += base_score + line_bonus + combo_bonus + + # 每10行提升一个等级,速度递增 level = lines_cleared / 10 + 1 - drop_time = max(0.1, 1.0 - (level - 1) * 0.1) + drop_time = max(0.05, 1.0 - (level - 1) * 0.08) drop_timer.wait_time = drop_time - update_ui() + # 随机生成特殊方块 + if randf() < 0.1 + level * 0.02: # 等级越高,特殊方块概率越大 + generate_special_piece() + else: + # 重置连击 + combo_count = 0 + + update_ui() func get_piece_shape(type: PieceType, rotation: int) -> Array: return PIECE_SHAPES[type][rotation] @@ -249,16 +328,27 @@ func get_piece_rotations(type: PieceType) -> int: return PIECE_SHAPES[type].size() func update_ui(): - score_label.text = "分数: " + str(score) - level_label.text = "等级: " + str(level) - lines_label.text = "消除行数: " + str(lines_cleared) + score_label.text = "🏆 分数: " + str(score) + "\n💎 最高: " + str(best_score) + level_label.text = "⚡ 等级: " + str(level) + "\n🎮 游戏: " + str(games_played) + lines_label.text = "📊 消除: " + str(lines_cleared) + "\n🔥 连击: " + str(combo_count) func show_game_over(): drop_timer.stop() + game_started = false + + # 检查并更新最高分 + if score > best_score: + best_score = score + + # 保存玩家数据 + save_player_data() + + # 显示游戏结束信息 + game_over_label.text = "🎮 游戏结束 🎮\n\n🏆 本次分数: " + str(score) + "\n💎 最高分数: " + str(best_score) + "\n📊 消除行数: " + str(lines_cleared) + "\n⚡ 达到等级: " + str(level) + "\n\n🔄 按Q键或空格重新开始" game_over_label.visible = true func _on_drop_timer_timeout(): - if game_over or not current_piece: + if not game_started or game_over or not current_piece: return if can_place_piece(current_piece_pos + Vector2(0, 1), current_piece_rotation): @@ -267,12 +357,177 @@ func _on_drop_timer_timeout(): else: place_piece() +# 虚拟控制设置 +func setup_virtual_controls(): + if virtual_controls: + virtual_controls.get_node("LeftButton").pressed.connect(_on_virtual_button_pressed.bind("left")) + virtual_controls.get_node("RightButton").pressed.connect(_on_virtual_button_pressed.bind("right")) + virtual_controls.get_node("DownButton").pressed.connect(_on_virtual_button_pressed.bind("down")) + virtual_controls.get_node("RotateButton").pressed.connect(_on_virtual_button_pressed.bind("rotate")) + virtual_controls.get_node("DropButton").pressed.connect(_on_virtual_button_pressed.bind("drop")) + virtual_controls.get_node("RestartButton").pressed.connect(_on_virtual_button_pressed.bind("restart")) + +func _on_virtual_button_pressed(action: String): + if not game_started: + if action == "restart": + init_game() + return + + if game_over and action == "restart": + handle_restart() + return + + if game_over or not current_piece: + return + + match action: + "left": + handle_move_left() + "right": + handle_move_right() + "down": + handle_move_down() + "rotate": + handle_rotate() + "drop": + handle_drop() + +# 特殊方块处理 +func process_special_pieces(lines_to_clear: Array): + # 处理特殊方块效果 + for special in special_pieces: + var pos = special.position + var type = special.type + + match type: + SpecialType.BOMB: + # 炸弹方块:清除周围3x3区域 + for dy in range(-1, 2): + for dx in range(-1, 2): + var x = pos.x + dx + var y = pos.y + dy + if x >= 0 and x < BOARD_WIDTH and y >= 0 and y < BOARD_HEIGHT: + board[y][x] = 0 + SpecialType.LINE: + # 直线方块:清除整行 + for x in range(BOARD_WIDTH): + board[pos.y][x] = 0 + SpecialType.RAINBOW: + # 彩虹方块:清除同色方块 + var target_color = board[pos.y][pos.x] + if target_color > 0: + for y in range(BOARD_HEIGHT): + for x in range(BOARD_WIDTH): + if board[y][x] == target_color: + board[y][x] = 0 + + # 清空特殊方块列表 + special_pieces.clear() + +func generate_special_piece(): + # 在随机位置生成特殊方块 + var x = randi() % BOARD_WIDTH + var y = randi() % (BOARD_HEIGHT - 5) + 5 # 在下半部分生成 + + if board[y][x] != 0: # 只在有方块的位置生成特殊效果 + var special_type = randi() % 3 + 1 # 随机选择特殊类型 + special_pieces.append({ + "position": Vector2(x, y), + "type": special_type + }) + +# 数据存储功能 +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("tetris"): + var tetris_data = player_data["tetris"] + best_score = tetris_data.get("best_score", 0) + games_played = tetris_data.get("games_played", 0) + total_lines_cleared = tetris_data.get("total_lines_cleared", 0) + +func show_start_screen(): + # 重置状态 + game_started = false + game_over = false + current_piece = null + + # 显示开始提示 + game_over_label.text = "🎮 俄罗斯方块 🎮\n\n🏆 最高分数: " + str(best_score) + "\n🎯 游戏次数: " + str(games_played) + "\n\n🎮 按Q键开始游戏\n\n🎯 操作说明:\nA/D - 左右移动\nW - 旋转\nS - 快速下落\n空格 - 直接落下" + game_over_label.visible = true + + # 停止定时器 + drop_timer.stop() + + # 清空游戏板 + board.clear() + special_pieces.clear() + for y in range(BOARD_HEIGHT): + var row = [] + for x in range(BOARD_WIDTH): + row.append(0) + board.append(row) + + update_ui() + queue_redraw() + +func save_player_data(): + if not player_data.has("tetris"): + player_data["tetris"] = {} + + player_data["tetris"]["best_score"] = best_score + player_data["tetris"]["games_played"] = games_played + player_data["tetris"]["total_lines_cleared"] = total_lines_cleared + player_data["tetris"]["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.05, 0.1, 0.2, 0.95)) + gradient.add_point(0.5, Color(0.1, 0.15, 0.25, 0.9)) + gradient.add_point(1.0, Color(0.15, 0.2, 0.3, 0.95)) + draw_rect(Rect2(Vector2.ZERO, size), gradient.sample(0.5), true) + # 获取游戏区域的位置 var area_pos = game_area.position + var area_size = Vector2(BOARD_WIDTH * CELL_SIZE, BOARD_HEIGHT * CELL_SIZE) + + # 绘制游戏区域阴影 + var shadow_offset = Vector2(4, 4) + var area_rect = Rect2(area_pos + shadow_offset, area_size) + draw_rect(area_rect, Color(0, 0, 0, 0.4), true) + + # 绘制游戏区域背景 + area_rect = Rect2(area_pos, area_size) + draw_rect(area_rect, Color(0.08, 0.12, 0.18, 0.9), true) + + # 绘制网格线(淡色) + for x in range(BOARD_WIDTH + 1): + var start_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y) + var end_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y + BOARD_HEIGHT * CELL_SIZE) + draw_line(start_pos, end_pos, Color(0.3, 0.3, 0.4, 0.3), 1) + + for y in range(BOARD_HEIGHT + 1): + var start_pos = Vector2(area_pos.x, area_pos.y + y * CELL_SIZE) + var end_pos = Vector2(area_pos.x + BOARD_WIDTH * CELL_SIZE, area_pos.y + y * CELL_SIZE) + draw_line(start_pos, end_pos, Color(0.3, 0.3, 0.4, 0.3), 1) # 绘制游戏板 for y in range(BOARD_HEIGHT): @@ -282,15 +537,75 @@ func _draw(): var rect = Rect2( area_pos.x + x * CELL_SIZE, area_pos.y + y * CELL_SIZE, - CELL_SIZE - 1, - CELL_SIZE - 1 + CELL_SIZE - 2, + CELL_SIZE - 2 ) var color = PIECE_COLORS[cell_value - 1] - draw_rect(rect, color) + + # 检查是否为特殊方块 + var is_special = false + var special_type = SpecialType.NORMAL + for special in special_pieces: + if special.position == Vector2(x, y): + is_special = true + special_type = special.type + break + + if is_special: + # 绘制特殊方块效果 + match special_type: + SpecialType.BOMB: + # 炸弹方块(红色闪烁) + var pulse = sin(Time.get_ticks_msec() * 0.01) * 0.3 + 0.7 + draw_rect(rect, Color.RED * pulse, true) + draw_rect(Rect2(rect.position + Vector2(2, 2), rect.size - Vector2(4, 4)), Color.ORANGE, true) + SpecialType.LINE: + # 直线方块(蓝色闪烁) + var pulse = sin(Time.get_ticks_msec() * 0.008) * 0.3 + 0.7 + draw_rect(rect, Color.CYAN * pulse, true) + draw_rect(Rect2(rect.position + Vector2(2, 2), rect.size - Vector2(4, 4)), Color.LIGHT_BLUE, true) + SpecialType.RAINBOW: + # 彩虹方块(彩虹色) + var rainbow_time = Time.get_ticks_msec() * 0.003 + var rainbow_color = Color.from_hsv(fmod(rainbow_time, 1.0), 1.0, 1.0) + draw_rect(rect, rainbow_color, true) + draw_rect(Rect2(rect.position + Vector2(2, 2), rect.size - Vector2(4, 4)), Color.WHITE, true) + else: + # 普通方块(立体效果) + draw_rect(rect, color, true) + # 高光 + var highlight_rect = Rect2(rect.position + Vector2(1, 1), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(highlight_rect, Color(1, 1, 1, 0.3), true) + # 阴影 + var shadow_rect = Rect2(rect.position + Vector2(1, rect.size.y * 0.7), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(shadow_rect, Color(0, 0, 0, 0.2), true) - # 绘制当前方块 + # 绘制当前方块(带透明度预览) if current_piece: var shape = get_piece_shape(current_piece.type, current_piece_rotation) + + # 绘制投影(显示方块会落在哪里) + var shadow_pos = current_piece_pos + while can_place_piece(shadow_pos + Vector2(0, 1), current_piece_rotation): + shadow_pos.y += 1 + + for y in range(shape.size()): + for x in range(shape[y].size()): + if shape[y][x] == 1: + var board_x = shadow_pos.x + x + var board_y = shadow_pos.y + y + if board_y >= 0 and board_x >= 0 and board_x < BOARD_WIDTH and board_y < BOARD_HEIGHT: + var rect = Rect2( + area_pos.x + board_x * CELL_SIZE, + area_pos.y + board_y * CELL_SIZE, + CELL_SIZE - 2, + CELL_SIZE - 2 + ) + var shadow_color = PIECE_COLORS[current_piece.type] + shadow_color.a = 0.3 + draw_rect(rect, shadow_color, false, 2) + + # 绘制当前方块 for y in range(shape.size()): for x in range(shape[y].size()): if shape[y][x] == 1: @@ -300,25 +615,23 @@ func _draw(): var rect = Rect2( area_pos.x + board_x * CELL_SIZE, area_pos.y + board_y * CELL_SIZE, - CELL_SIZE - 1, - CELL_SIZE - 1 + CELL_SIZE - 2, + CELL_SIZE - 2 ) var color = PIECE_COLORS[current_piece.type] - draw_rect(rect, color) + # 立体效果 + draw_rect(rect, color, true) + # 高光 + var highlight_rect = Rect2(rect.position + Vector2(1, 1), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(highlight_rect, Color(1, 1, 1, 0.4), true) - # 绘制网格线 - for x in range(BOARD_WIDTH + 1): - var start_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y) - var end_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y + BOARD_HEIGHT * CELL_SIZE) - draw_line(start_pos, end_pos, Color.GRAY, 1) - - for y in range(BOARD_HEIGHT + 1): - var start_pos = Vector2(area_pos.x, area_pos.y + y * CELL_SIZE) - var end_pos = Vector2(area_pos.x + BOARD_WIDTH * CELL_SIZE, area_pos.y + y * CELL_SIZE) - draw_line(start_pos, end_pos, Color.GRAY, 1) + # 绘制下一个方块预览区域背景 + var next_area_pos = next_piece_area.position + var next_area_size = next_piece_area.size + draw_rect(Rect2(next_area_pos + Vector2(2, 2), next_area_size), Color(0, 0, 0, 0.3), true) + draw_rect(Rect2(next_area_pos, next_area_size), Color(0.1, 0.15, 0.25, 0.9), true) # 绘制下一个方块预览 - var next_area_pos = next_piece_area.position var next_shape = get_piece_shape(next_piece_type, 0) for y in range(next_shape.size()): for x in range(next_shape[y].size()): @@ -326,8 +639,19 @@ func _draw(): var rect = Rect2( next_area_pos.x + 20 + x * 20, next_area_pos.y + 50 + y * 20, - 19, - 19 + 18, + 18 ) var color = PIECE_COLORS[next_piece_type] - draw_rect(rect, color) + # 立体效果 + draw_rect(rect, color, true) + # 高光 + var highlight_rect = Rect2(rect.position + Vector2(1, 1), Vector2(rect.size.x - 2, rect.size.y * 0.3)) + draw_rect(highlight_rect, Color(1, 1, 1, 0.3), true) + + +func _on_quit_button_pressed() -> void: + self.hide() + get_parent().remove_child(self) + queue_free() + pass diff --git a/Scene/SmallGame/Tetris.tscn b/Scene/SmallGame/Tetris.tscn index c0d449b..3bc4fa5 100644 --- a/Scene/SmallGame/Tetris.tscn +++ b/Scene/SmallGame/Tetris.tscn @@ -12,10 +12,10 @@ layout_mode = 1 anchors_preset = 4 anchor_top = 0.5 anchor_bottom = 0.5 -offset_left = 50.0 -offset_top = -300.0 -offset_right = 350.0 -offset_bottom = 300.0 +offset_left = 542.0 +offset_top = -300.5 +offset_right = 842.0 +offset_bottom = 299.5 grow_vertical = 2 [node name="NextPieceArea" type="Panel" parent="."] @@ -23,9 +23,9 @@ layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -200.0 +offset_left = -469.0 offset_top = 50.0 -offset_right = -50.0 +offset_right = -319.0 offset_bottom = 200.0 grow_horizontal = 0 @@ -35,10 +35,10 @@ anchors_preset = 5 anchor_left = 0.5 anchor_right = 0.5 offset_left = -50.0 -offset_top = 10.0 offset_right = 50.0 -offset_bottom = 40.0 +offset_bottom = 42.0 grow_horizontal = 2 +theme_override_font_sizes/font_size = 30 text = "下一个" horizontal_alignment = 1 @@ -47,11 +47,12 @@ layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -200.0 -offset_top = 220.0 -offset_right = -50.0 -offset_bottom = 250.0 +offset_left = -255.0 +offset_top = 92.0 +offset_right = -105.0 +offset_bottom = 134.0 grow_horizontal = 0 +theme_override_font_sizes/font_size = 30 text = "分数: 0" horizontal_alignment = 1 @@ -60,11 +61,12 @@ layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -200.0 -offset_top = 260.0 -offset_right = -50.0 -offset_bottom = 290.0 +offset_left = -252.0 +offset_top = 201.0 +offset_right = -102.0 +offset_bottom = 243.0 grow_horizontal = 0 +theme_override_font_sizes/font_size = 30 text = "等级: 1" horizontal_alignment = 1 @@ -73,28 +75,29 @@ layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -200.0 -offset_top = 300.0 -offset_right = -50.0 -offset_bottom = 330.0 +offset_left = -255.0 +offset_top = 312.0 +offset_right = -102.0 +offset_bottom = 354.0 grow_horizontal = 0 +theme_override_font_sizes/font_size = 30 text = "消除行数: 0" horizontal_alignment = 1 [node name="GameOverLabel" type="Label" parent="."] -visible = false layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -100.0 -offset_top = -50.0 -offset_right = 100.0 -offset_bottom = 50.0 +offset_left = -116.5 +offset_top = -48.5 +offset_right = 93.5 +offset_bottom = 51.5 grow_horizontal = 2 grow_vertical = 2 +theme_override_font_sizes/font_size = 30 text = "游戏结束 按空格重新开始" horizontal_alignment = 1 @@ -108,13 +111,144 @@ layout_mode = 1 anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 -offset_left = -200.0 -offset_top = 400.0 -offset_right = -50.0 -offset_bottom = 500.0 +offset_left = -214.0 +offset_top = 494.0 +offset_right = -9.0 +offset_bottom = 716.0 grow_horizontal = 0 +theme_override_font_sizes/font_size = 30 text = "操作说明: A/D - 左右移动 S - 快速下降 W - 旋转 空格 - 瞬间下降" + +[node name="VirtualControls" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LeftButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 96.0 +offset_top = -351.0 +offset_right = 176.0 +offset_bottom = -271.0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +text = "A" + +[node name="RightButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 260.0 +offset_top = -350.0 +offset_right = 340.0 +offset_bottom = -270.0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +text = "D" + +[node name="DownButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 180.0 +offset_top = -270.0 +offset_right = 260.0 +offset_bottom = -190.0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +text = "S" + +[node name="TestButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 178.0 +offset_top = -334.0 +offset_right = 238.0 +offset_bottom = -271.0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +disabled = true + +[node name="RotateButton" type="Button" parent="VirtualControls"] +custom_minimum_size = Vector2(80, 80) +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -1225.0 +offset_top = -431.0 +offset_right = -1145.0 +offset_bottom = -351.0 +grow_horizontal = 0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +text = "W" + +[node name="DropButton" type="Button" parent="VirtualControls"] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -1284.0 +offset_top = -583.0 +offset_right = -1061.0 +offset_bottom = -520.0 +grow_horizontal = 0 +grow_vertical = 0 +theme_override_font_sizes/font_size = 40 +text = "🚀瞬间下降" + +[node name="RestartButton" type="Button" parent="VirtualControls"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -582.5 +offset_top = 248.5 +offset_right = -377.5 +offset_bottom = 305.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 35 +text = "🔄 重新开始" + +[node name="QuitButton" type="Button" parent="."] +self_modulate = Color(1, 0.247059, 0, 1) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -137.0 +offset_top = -1.52588e-05 +offset_right = -54.0 +offset_bottom = 57.0 +grow_horizontal = 0 +scale = Vector2(1.54345, 1.50915) +theme_override_font_sizes/font_size = 25 +text = "关闭" + +[connection signal="pressed" from="QuitButton" to="." method="_on_quit_button_pressed"] diff --git a/Script/BigPanel/CropStorePanel.gd b/Script/BigPanel/CropStorePanel.gd index 5e40399..6bd5f92 100644 --- a/Script/BigPanel/CropStorePanel.gd +++ b/Script/BigPanel/CropStorePanel.gd @@ -389,10 +389,23 @@ func _apply_filter_and_sort(): # 自定义排序函数 func _sort_crop_items(a, b): + # 安全地获取排序值,并进行类型转换 + var value_a = a["data"].get(current_sort_key, 0) + var value_b = b["data"].get(current_sort_key, 0) + + # 如果是数值类型的字段,确保转换为数值进行比较 + if current_sort_key in ["花费", "生长时间", "收益", "等级", "经验", "耐候性"]: + # 转换为数值,如果转换失败则使用0 + if typeof(value_a) == TYPE_STRING: + value_a = int(value_a) if value_a.is_valid_int() else 0 + if typeof(value_b) == TYPE_STRING: + value_b = int(value_b) if value_b.is_valid_int() else 0 + + # 执行排序比较 if current_sort_ascending: - return a["data"][current_sort_key] < b["data"][current_sort_key] + return value_a < value_b else: - return a["data"][current_sort_key] > b["data"][current_sort_key] + return value_a > value_b # 更新金钱显示 func _update_money_display(): diff --git a/Script/BigPanel/CropWarehousePanel.gd b/Script/BigPanel/CropWarehousePanel.gd index d80f4fa..9ece46c 100644 --- a/Script/BigPanel/CropWarehousePanel.gd +++ b/Script/BigPanel/CropWarehousePanel.gd @@ -210,11 +210,23 @@ func _sort_crop_items(a, b): print("警告: 排序键 ", current_sort_key, " 在某些成熟物数据中不存在") return false - # 执行排序 + # 安全地获取排序值,并进行类型转换 + var value_a = a["data"].get(current_sort_key, 0) + var value_b = b["data"].get(current_sort_key, 0) + + # 如果是数值类型的字段,确保转换为数值进行比较 + if current_sort_key in ["花费", "生长时间", "收益", "等级", "经验", "耐候性"]: + # 转换为数值,如果转换失败则使用0 + if typeof(value_a) == TYPE_STRING: + value_a = int(value_a) if value_a.is_valid_int() else 0 + if typeof(value_b) == TYPE_STRING: + value_b = int(value_b) if value_b.is_valid_int() else 0 + + # 执行排序比较 if current_sort_ascending: - return a["data"][current_sort_key] < b["data"][current_sort_key] + return value_a < value_b else: - return a["data"][current_sort_key] > b["data"][current_sort_key] + return value_a > value_b # 按品质过滤成熟物 func _filter_by_quality(quality: String): diff --git a/Script/BigPanel/PlayGamePanel.gd b/Script/BigPanel/PlayGamePanel.gd index c1c91d4..5fee78a 100644 --- a/Script/BigPanel/PlayGamePanel.gd +++ b/Script/BigPanel/PlayGamePanel.gd @@ -1 +1,27 @@ extends Panel + + +var _2048_GAME = preload('res://Scene/SmallGame/2048Game.tscn').instantiate() +var PUSH_BOX = preload('res://Scene/SmallGame/PushBox.tscn').instantiate() +var SNAKE_GAME = preload('res://Scene/SmallGame/SnakeGame.tscn').instantiate() +var TETRIS = preload('res://Scene/SmallGame/Tetris.tscn').instantiate() + + +func _on_game_button_pressed() -> void: + self.add_child(_2048_GAME) + pass # Replace with function body. + + +func _on_push_box_button_pressed() -> void: + self.add_child(PUSH_BOX) + pass # Replace with function body. + + +func _on_snake_game_button_pressed() -> void: + self.add_child(SNAKE_GAME) + pass # Replace with function body. + + +func _on_tetris_button_pressed() -> void: + self.add_child(TETRIS) + pass # Replace with function body. diff --git a/Script/BigPanel/PlayerBagPanel.gd b/Script/BigPanel/PlayerBagPanel.gd index 3f1b893..30879ae 100644 --- a/Script/BigPanel/PlayerBagPanel.gd +++ b/Script/BigPanel/PlayerBagPanel.gd @@ -165,11 +165,23 @@ func _sort_seed_items(a, b): print("警告: 排序键 ", current_sort_key, " 在某些种子数据中不存在") return false - # 执行排序 + # 安全地获取排序值,并进行类型转换 + var value_a = a["data"].get(current_sort_key, 0) + var value_b = b["data"].get(current_sort_key, 0) + + # 如果是数值类型的字段,确保转换为数值进行比较 + if current_sort_key in ["花费", "生长时间", "收益", "等级", "经验", "耐候性"]: + # 转换为数值,如果转换失败则使用0 + if typeof(value_a) == TYPE_STRING: + value_a = int(value_a) if value_a.is_valid_int() else 0 + if typeof(value_b) == TYPE_STRING: + value_b = int(value_b) if value_b.is_valid_int() else 0 + + # 执行排序比较 if current_sort_ascending: - return a["data"][current_sort_key] < b["data"][current_sort_key] + return value_a < value_b else: - return a["data"][current_sort_key] > b["data"][current_sort_key] + return value_a > value_b # 按品质过滤种子 func _filter_by_quality(quality: String): diff --git a/Server/TCPGameServer.py b/Server/TCPGameServer.py index fc1f2e9..b5570f8 100644 --- a/Server/TCPGameServer.py +++ b/Server/TCPGameServer.py @@ -2425,11 +2425,7 @@ class TCPGameServer(TCPServer): return self.send_data(client_id, response) crop_name = message.get("crop_name", "") - quantity = message.get("quantity", 1) # 获取购买数量,默认为1 - - # 确保购买数量为正整数 - if not isinstance(quantity, int) or quantity <= 0: - quantity = 1 + quantity = max(1, int(message.get("quantity", 1))) # 确保购买数量为正整数 # 加载作物配置 crop_data = self._load_crop_data() @@ -2447,7 +2443,9 @@ class TCPGameServer(TCPServer): def _process_seed_purchase(self, client_id, player_data, username, crop_name, crop, quantity=1): """处理种子购买逻辑""" # 检查玩家等级 - if player_data["等级"] < crop.get("等级", 1): + player_level = int(player_data.get("等级", 1)) + required_level = int(crop.get("等级", 1)) + if player_level < required_level: return self._send_action_error(client_id, "buy_seed", "等级不足,无法购买此种子") # 计算总花费 @@ -2456,7 +2454,7 @@ class TCPGameServer(TCPServer): # 检查玩家金钱 if player_data["钱币"] < total_cost: - return self._send_action_error(client_id, "buy_seed", f"金钱不足,无法购买此种子。需要{total_cost}元,当前只有{player_data['money']}元") + return self._send_action_error(client_id, "buy_seed", f"金钱不足,无法购买此种子。需要{total_cost}元,当前只有{player_data['钱币']}元") # 扣除金钱 player_data["钱币"] -= total_cost diff --git a/Server/__pycache__/ConsoleCommandsAPI.cpython-313.pyc b/Server/__pycache__/ConsoleCommandsAPI.cpython-313.pyc index b1584f4de3b4c764c11ef93e542d2e9bede4f6ac..ac7a45979a8f13bb7b754b7c7c56569e2cb8848e 100644 GIT binary patch delta 22 ccmZqdV{YtY=Kjpf%f$c$D