308 lines
9.3 KiB
GDScript
308 lines
9.3 KiB
GDScript
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 # 动画完成后重置标志位
|
||
)
|