D3/Level/Rope/Rope.gd
2025-05-10 23:19:52 +08:00

308 lines
9.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends Node2D
var debug_mode: bool = false
# 添加debug绘制相关变量
var debug_lines: Array[Line2D] = []
var debug_font: Font
# 导出变量便于在编辑器中配置
@export var start_point: Vector2
@export var end_point: Vector2
@export var part_distance: float = 12.0
@export var rope_part_scene: PackedScene
@export var max_deform: float = 30.0 # 最大形变距离
@export var deform_spread: float = 3.0 # 形变扩散范围
@export var initial_sag: float = 8.0 # 初始下垂量
# Line2D相关导出变量
@export var rope_texture: Texture2D # 绳索纹理
@export var rope_width: float = 8 # 绳索宽度
@export var rope_color: Color = Color.WHITE # 绳索颜色
@export var rope_texture_tile_factor: float = 1.0 # 纹理平铺系数
var rope_parts: Array[RigidBody2D] = []
var rest_positions: Array[Vector2] = [] # 存储原始位置
var active_deforms: Dictionary = {} # 存储当前变形 {rope_part: deform_amount}
var _exit_tween: Tween
var _is_exiting := false
@onready var rope_line: Line2D = $rope_line
func _ready() -> void:
if rope_part_scene == null:
push_error("Rope part scene is not set!")
return
# 初始化Line2D
setup_rope_line()
$rope_start.position = start_point
$rope_end.position = end_point
# 初始化debug字体
if debug_mode:
debug_font = ThemeDB.fallback_font
print("[Rope] Initialized with start_point: ", start_point, " end_point: ", end_point)
generate_rope_parts()
# 保存生成后的位置作为静止位置
rest_positions.clear()
for part in rope_parts:
rest_positions.append(part.position)
func setup_rope_line() -> void:
rope_line.width = rope_width
rope_line.default_color = rope_color
if rope_texture:
rope_line.texture = rope_texture
rope_line.texture_mode = Line2D.LINE_TEXTURE_TILE
rope_line.texture_repeat = CanvasItem.TEXTURE_REPEAT_ENABLED
rope_line.joint_mode = Line2D.LINE_JOINT_ROUND
rope_line.begin_cap_mode = Line2D.LINE_CAP_ROUND
rope_line.end_cap_mode = Line2D.LINE_CAP_ROUND
rope_line.antialiased = true
func _process(_delta: float) -> void:
update_rope_line()
func update_rope_line() -> void:
rope_line.clear_points()
for part in rope_parts:
rope_line.add_point(part.position)
func generate_rope_parts() -> void:
# 清理debug线
if debug_mode:
for line in debug_lines:
line.queue_free()
debug_lines.clear()
print("[Rope] Generating rope parts...")
# 清理可能存在的旧rope parts
for child in $rope_parts.get_children():
child.queue_free()
rope_parts.clear()
rest_positions.clear()
# 计算绳索总长度和需要的部件数量
var total_distance: float = start_point.distance_to(end_point)
var parts_count: int = int(total_distance / part_distance)
# 确保至少有一个部件
if parts_count < 1:
parts_count = 1
if debug_mode:
print("[Rope] Total distance: ", total_distance)
print("[Rope] Parts count: ", parts_count)
# 计算实际的部件间距,确保均匀分布
var _actual_distance: float = total_distance / parts_count
# 首先创建所有部件并存储到临时数组
var temp_parts: Array[Dictionary] = []
# 生成每个rope_part
for i in range(parts_count + 1):
var t = float(i) / float(parts_count) # 插值因子
var part_position = start_point.lerp(end_point, t)
# 计算下垂量:使用抛物线函数 y = 4h(x-x²)
var sag = initial_sag * 4.0 * t * (1.0 - t)
part_position.y += sag
var part = rope_part_scene.instantiate() as RigidBody2D
$rope_parts.add_child(part)
part.position = part_position
# 配置第一个和最后一个点
if i == 0 or i == parts_count:
part.freeze = true # 固定两端点
# 将部件及其位置信息存储到临时数组
temp_parts.append({
"part": part,
"position": part_position
})
if debug_mode:
print("[Rope] Created part at position: ", part_position)
# 仅按x坐标排序
temp_parts.sort_custom(func(a, b): return a["position"].x < b["position"].x)
# 清空并重新填充rope_parts和original_positions数组确保顺序正确
rope_parts.clear()
rest_positions.clear()
# 创建物理关节连接相邻的部件
for i in range(temp_parts.size()):
var part = temp_parts[i]["part"]
var pos = temp_parts[i]["position"]
rope_parts.append(part)
rest_positions.append(pos)
# 创建与前一个部件的关节连接(除了第一个部件)
if i > 0:
var pin_joint = PinJoint2D.new()
pin_joint.set_node_a(rope_parts[i-1].get_path())
pin_joint.set_node_b(part.get_path())
pin_joint.softness = 1.0 # 可以调整这个值来改变绳索的柔软度
$rope_parts.add_child(pin_joint)
# 设置关节的位置(在两个部件之间)
var joint_pos = (rope_parts[i-1].position + part.position) / 2
pin_joint.position = joint_pos
if debug_mode:
print("[Rope] Parts sorted and connected. Total parts: ", rope_parts.size())
for i in range(rope_parts.size()):
print("[Rope] Part ", i, ": ", rope_parts[i])
print("[Rope] Part ", i, " position: ", rope_parts[i].position)
# 初始更新Line2D
update_rope_line()
func handle_player_rope_contact(collider: RigidBody2D) -> void:
var contact_index = -1
for i in range(rope_parts.size()):
if rope_parts[i] == collider:
contact_index = i
break
if contact_index == -1:
return
# 如果碰到第一个或最后一个部件,直接返回
if contact_index == 0 or contact_index == rope_parts.size() - 1:
return
var max_offset = 10.0 # 最大下移距离
var tween = create_tween()
# 同时应用所有部件的移动
for i in range(rope_parts.size()):
# 跳过第一个和最后一个部件
if i == 0 or i == rope_parts.size() - 1:
continue
var part = rope_parts[i]
var target_pos = rest_positions[i]
# 如果接触点是第二个或倒数第二个部件,最大偏移量减半
var current_max_offset = max_offset
if contact_index == 1 or contact_index == rope_parts.size() - 2:
current_max_offset = max_offset * 0.5
# 计算移动距离
var move_amount: float
if i <= contact_index:
# 左侧部分,从接触点到起点,线性减少
move_amount = float(i) / float(contact_index) if contact_index > 0 else 0.0
else:
# 右侧部分,从接触点到终点,线性减少
var remaining_parts = rope_parts.size() - 1 - contact_index
move_amount = float(remaining_parts - (i - contact_index)) / float(remaining_parts) if remaining_parts > 0 else 0.0
# 计算目标位置
var target_offset = Vector2(0, current_max_offset * move_amount)
# 将所有移动添加到同一个tween中parallel=true使它们同时开始
tween.parallel().tween_property(part, "position",
target_pos + target_offset, 0.3
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
func handle_player_rope_exit() -> void:
# 如果正在执行退出动画,直接返回
if _is_exiting:
return
_is_exiting = true
# 如果已经有正在进行的退出动画,先停止它
if _exit_tween and _exit_tween.is_valid():
_exit_tween.kill()
const DOWN_OFFSET_FACTOR := 1.3
const UP_OFFSET_FACTOR := 0.5
const DOWN_TIME := 0.15
const UP_TIME := 0.2
const RECOVER_TIME := 0.1
# 找到最大偏移点
var max_offset_index := 0
var max_offset := 0.0
for i in range(rope_parts.size()):
var current_offset = abs(rope_parts[i].position.y - rest_positions[i].y)
if current_offset > max_offset:
max_offset = current_offset
max_offset_index = i
_exit_tween = create_tween()
var tween = _exit_tween
# 第一阶段:下沉
for i in range(rope_parts.size()):
if i == 0 or i == rope_parts.size() - 1:
continue
var part = rope_parts[i]
var rest_pos = rest_positions[i]
var move_amount: float
if i <= max_offset_index:
move_amount = float(i) / float(max_offset_index) if max_offset_index > 0 else 0.0
else:
var remaining_parts = rope_parts.size() - 1 - max_offset_index
move_amount = float(remaining_parts - (i - max_offset_index)) / float(remaining_parts) if remaining_parts > 0 else 0.0
var down_offset = Vector2(0, max_offset * DOWN_OFFSET_FACTOR * move_amount)
tween.parallel().tween_property(part, "position",
rest_pos + down_offset, DOWN_TIME
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
# 第二阶段:反弹
tween.chain().tween_callback(func(): pass)
for i in range(rope_parts.size()):
if i == 0 or i == rope_parts.size() - 1:
continue
var part = rope_parts[i]
var rest_pos = rest_positions[i]
var move_amount: float
if i <= max_offset_index:
move_amount = float(i) / float(max_offset_index) if max_offset_index > 0 else 0.0
else:
var remaining_parts = rope_parts.size() - 1 - max_offset_index
move_amount = float(remaining_parts - (i - max_offset_index)) / float(remaining_parts) if remaining_parts > 0 else 0.0
var up_offset = Vector2(0, -max_offset * UP_OFFSET_FACTOR * move_amount)
tween.parallel().tween_property(part, "position",
rest_pos + up_offset, UP_TIME
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
# 第三阶段:恢复到静止位置
tween.chain().tween_callback(func(): pass)
for i in range(rope_parts.size()):
if i == 0 or i == rope_parts.size() - 1:
continue
var part = rope_parts[i]
tween.parallel().tween_property(part, "position",
rest_positions[i], RECOVER_TIME
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
# 添加完成回调,重置标志位
tween.chain().tween_callback(func():
_is_exiting = false # 动画完成后重置标志位
)