添加小游戏面板

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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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

View File

@@ -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"]

View File

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

View File

@@ -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"]

View File

@@ -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

View File

@@ -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"]

View File

@@ -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

View File

@@ -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"]

View File

@@ -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():

View File

@@ -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):

View File

@@ -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.

View File

@@ -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):

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
Test/playergamedata.json Normal file
View File

@@ -0,0 +1 @@
{"2048":{"best_score":2056,"current_score":2056,"games_played":4.0,"highest_tile":128.0,"total_moves":515.0},"global":{"last_played":"2025-08-26T19:02:30"},"pushbox":{"best_moves_per_level":{"1":4.0,"2.0":13.0},"current_level":1.0,"levels_completed":3.0,"max_level_reached":1.0,"total_moves":56.0}}

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://j8bi7kpe2qbo"
path="res://.godot/imported/未成熟.webp-82494694de3fd628511fc7826d35227c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/作物/咖啡豆/未成熟.webp"
dest_files=["res://.godot/imported/未成熟.webp-82494694de3fd628511fc7826d35227c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.01
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

32
playergamedata.json Normal file
View File

@@ -0,0 +1,32 @@
{
"2048": {
"best_score": 0,
"current_score": 0,
"games_played": 0,
"highest_tile": 0,
"total_moves": 0,
"achievements": [],
"settings": {
"sound_enabled": true,
"animations_enabled": true
}
},
"pushbox": {
"current_level": 1,
"max_level_reached": 1,
"total_moves": 0,
"levels_completed": 0,
"best_moves_per_level": {},
"achievements": [],
"settings": {
"sound_enabled": true,
"show_hints": true
}
},
"global": {
"total_play_time": 0,
"last_played": "",
"player_name": "Player",
"version": "1.0"
}
}