添加小游戏面板

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

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