钩锁逻辑更新

This commit is contained in:
RedisTKey 2026-01-11 00:57:27 +08:00
parent 8d3a3da5d5
commit b57104481a
9 changed files with 135 additions and 36 deletions

View File

@ -139,6 +139,7 @@ grap_hook_shooting_time = 0.2
[node name="Grapping" type="LimboState" parent="PlayerHSM/Normal/GrapHook"] [node name="Grapping" type="LimboState" parent="PlayerHSM/Normal/GrapHook"]
unique_name_in_owner = true unique_name_in_owner = true
script = ExtResource("19_u7cua") script = ExtResource("19_u7cua")
release_distance = 20.0
[node name="Dash" type="LimboState" parent="PlayerHSM/Normal"] [node name="Dash" type="LimboState" parent="PlayerHSM/Normal"]
unique_name_in_owner = true unique_name_in_owner = true
@ -184,11 +185,13 @@ metadata/_custom_type_script = "uid://bkkkyugppu7sl"
[node name="ReedSFXMananger" type="Node" parent="."] [node name="ReedSFXMananger" type="Node" parent="."]
script = ExtResource("24_x5g5e") script = ExtResource("24_x5g5e")
bindings = Array[ExtResource("25_rjd7i")]([ExtResource("26_256x0")]) bindings = Array[ExtResource("25_rjd7i")]([ExtResource("26_256x0")])
enable_debug = false
metadata/_custom_type_script = "uid://lp1ge1qbyfr3" metadata/_custom_type_script = "uid://lp1ge1qbyfr3"
[node name="ReedVFXManager" type="Node" parent="."] [node name="ReedVFXManager" type="Node" parent="."]
script = ExtResource("24_mwlgv") script = ExtResource("24_mwlgv")
bindings = Array[ExtResource("25_s5egm")]([ExtResource("26_s5egm"), ExtResource("30_hquoe")]) bindings = Array[ExtResource("25_s5egm")]([ExtResource("26_s5egm"), ExtResource("30_hquoe")])
enable_debug = false
metadata/_custom_type_script = "uid://ta2r2bc1nrwe" metadata/_custom_type_script = "uid://ta2r2bc1nrwe"
[node name="HitBox" type="Area2D" parent="."] [node name="HitBox" type="Area2D" parent="."]

View File

@ -11,6 +11,11 @@ var/had_jump/type = 1
var/had_jump/value = false var/had_jump/value = false
var/had_jump/hint = 0 var/had_jump/hint = 0
var/had_jump/hint_string = "" var/had_jump/hint_string = ""
var/force_reset_jump/name = &"force_reset_jump"
var/force_reset_jump/type = 1
var/force_reset_jump/value = false
var/force_reset_jump/hint = 0
var/force_reset_jump/hint_string = ""
var/is_dashing/name = &"is_dashing" var/is_dashing/name = &"is_dashing"
var/is_dashing/type = 1 var/is_dashing/type = 1
var/is_dashing/value = false var/is_dashing/value = false

View File

@ -3,9 +3,14 @@ extends LimboState
@onready var root: Normal = %Normal @onready var root: Normal = %Normal
@export_category("Hook Pull Release") @export_category("Hook Pull Release")
##如果和目标点小于此距离,则会自动松开
@export var release_distance: float = 12.0 @export var release_distance: float = 12.0
##如果钩爪方向和角色速度方向小于此,则中断
@export var release_dot_threshold: float = 0.0 @export var release_dot_threshold: float = 0.0
##最短pull时间即使距离非常近也会判断至少此时间
@export var min_pull_timer: float = 0.08 @export var min_pull_timer: float = 0.08
##如果钩爪跳时速度低于这个值,则会强制重置跳跃
@export var force_jump_reset_maxinum_speed: float = 230
var _pull_timer := 0.0 var _pull_timer := 0.0
var _anchor var _anchor
@ -75,9 +80,7 @@ func _update(delta: float) -> void:
return return
func _exit() -> void: func _exit() -> void:
agent.locomotion_comp.stop_custom_move(false) agent.locomotion_comp._release_hook_keep_momentum()
_anchor = null
agent.spawn_hook_comp.release_cached_hook() agent.spawn_hook_comp.release_cached_hook()
func _handle_trigger_jump() -> bool: func _handle_trigger_jump() -> bool:
@ -85,6 +88,9 @@ func _handle_trigger_jump() -> bool:
return false return false
func _hook_to_jump() -> void: func _hook_to_jump() -> void:
var v : float = agent.velocity.length()
if v <= force_jump_reset_maxinum_speed:
get_root().blackboard.set_var(&"force_reset_jump",true)
get_root().blackboard.set_var(&"want_to_jump",true) get_root().blackboard.set_var(&"want_to_jump",true)
self.dispatch(&"hook_to_jump") self.dispatch(&"hook_to_jump")

View File

@ -10,8 +10,12 @@ func _setup() -> void:
func _enter() -> void: func _enter() -> void:
_jump_from_land_check() _jump_from_land_check()
var _j = agent.locomotion_comp.jump() as bool ##是否要强制重置水平速度。
var f = blackboard.get_var(&"force_reset_jump",false) as bool
var _j = agent.locomotion_comp.jump(f) as bool
get_root().blackboard.set_var(&"is_jumping",true) #标记jump get_root().blackboard.set_var(&"is_jumping",true) #标记jump
get_root().blackboard.set_var(&"force_reset_jump",false)#重置强制重置位
func _exit() -> void: func _exit() -> void:
#这里强制重置一下 #这里强制重置一下

View File

@ -8,9 +8,14 @@ extends Node2D
@export var max_length := 200.0 @export var max_length := 200.0
@export var stretching_speed: float = 1400.0 @export var stretching_speed: float = 1400.0
@export_category("Hook Retract")
@export var retract_speed: float = 1800.0
@onready var line_2d: Line2D = %Line2D @onready var line_2d: Line2D = %Line2D
@onready var ray: RayCast2D = %RayCast2D @onready var ray: RayCast2D = %RayCast2D
var _tween: Tween
const GRAPABLE_GROUP = &"GRAPABLE" const GRAPABLE_GROUP = &"GRAPABLE"
signal stretching_finished(reach_limit: bool, anchor_node: Node2D) signal stretching_finished(reach_limit: bool, anchor_node: Node2D)
@ -127,7 +132,7 @@ func _handle_hit(target: Node2D, hit_pos: Vector2) -> void:
stretching_finished.emit(reach_max, anchor) stretching_finished.emit(reach_max, anchor)
## 釋放鉤爪(清理 Anchor 與狀態) ## 釋放鉤爪(清理 Anchor 與狀態)
func release_hook() -> void: func _release_hook() -> void:
# 1. 停止拉伸(保險) # 1. 停止拉伸(保險)
_is_stretching = false _is_stretching = false
_stretching_dir = Vector2.ZERO _stretching_dir = Vector2.ZERO
@ -145,8 +150,59 @@ func release_hook() -> void:
ray.target_position = Vector2.ZERO ray.target_position = Vector2.ZERO
_update_line() _update_line()
func release_hook_with_transition(has_trans: bool) -> void:
# 先停止一切拉伸逻辑
_is_stretching = false
_stretching_dir = Vector2.ZERO
_cached_cancel = false
# 如果不需要动画,直接释放
if not has_trans:
_release_hook()
queue_free()
return
# ===== 需要回收动画 =====
# 先清 Anchor但不影响末端视觉
if _anchor and is_instance_valid(_anchor):
_anchor.get_parent().remove_child(_anchor)
_anchor.queue_free()
_anchor = null
# 当前末端位置(本地坐标)
var start_pos: Vector2 = ray.target_position
var distance := start_pos.length()
if distance <= 0.001:
queue_free()
return
# 根据速度算 tween 时间
var duration := distance / retract_speed
if _tween and _tween.is_valid():
_tween.kill()
_tween = get_tree().create_tween()
_tween.set_trans(Tween.TRANS_SINE)
_tween.set_ease(Tween.EASE_IN)
_tween.tween_property(
ray,
"target_position",
Vector2.ZERO,
duration
)
_tween.finished.connect(func():
queue_free()
)
func _update_line() -> void: func _update_line() -> void:
line_2d.set_point_position(0, Vector2.ZERO) if _anchor and is_instance_valid(_anchor):
# 关键:锚点是世界坐标固定的,把它换算到 Hook 的本地坐标
ray.target_position = to_local(_anchor.global_position)
line_2d.set_point_position(1, ray.target_position) line_2d.set_point_position(1, ray.target_position)
func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D: func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D:

View File

@ -15,8 +15,8 @@ signal hook_reach_finished(reach_limit: float, anchor: Node2D)
func spawn_grap_hook_inst(dir: Vector2) -> Hook: func spawn_grap_hook_inst(dir: Vector2) -> Hook:
var i = GRAPPING_HOOK.instantiate() as Hook var i = GRAPPING_HOOK.instantiate() as Hook
owner.add_child(i)
i.init(self,true) i.init(self,true)
get_tree().current_scene.add_child(i)
i.start_stretching(dir) i.start_stretching(dir)
_current_grap_hook_inst = i _current_grap_hook_inst = i
@ -25,7 +25,7 @@ func spawn_grap_hook_inst(dir: Vector2) -> Hook:
func release_cached_hook() -> void: func release_cached_hook() -> void:
if _current_grap_hook_inst: if _current_grap_hook_inst:
_current_grap_hook_inst.release_hook() _current_grap_hook_inst.release_hook_with_transition(true)
func get_current_hook_inst() -> Hook: func get_current_hook_inst() -> Hook:
return _current_grap_hook_inst return _current_grap_hook_inst

View File

@ -45,10 +45,11 @@ var _h_boost_timer: float
##跳跃 ##跳跃
func jump() -> bool: func jump(force_reset_x: bool = false) -> bool:
if _is_jumping : return false if _is_jumping : return false
var input_dir = sign(_movement_input) as float var input_dir = sign(_movement_input) as float
if not force_reset_x:
if input_dir != 0 : if input_dir != 0 :
var vel_dir := sign(characterbody.velocity.x) var vel_dir := sign(characterbody.velocity.x)
var accel_dir := sign(_current_acceleration.x) var accel_dir := sign(_current_acceleration.x)
@ -67,6 +68,10 @@ func jump() -> bool:
characterbody.velocity.x = input_dir * jump_horizontal_Boost characterbody.velocity.x = input_dir * jump_horizontal_Boost
apply_jump_horizontal_boost(input_dir) apply_jump_horizontal_boost(input_dir)
else:
##如果强制重置,我们会让水平速度直接等于输入加水平补正
characterbody.velocity.x = input_dir * jump_horizontal_Boost
print("强制重置")
_jump_timer = jump_hold_maxium_time _jump_timer = jump_hold_maxium_time
_h_boost_timer = jump_horizontal_Boost_last_time _h_boost_timer = jump_horizontal_Boost_last_time

View File

@ -12,33 +12,43 @@ func _ready() -> void:
_bind_all() _bind_all()
func _bind_all() -> void: func _bind_all() -> void:
print("[SFX] binding count =", bindings.size(), " manager path=", get_path()) _dbg_fmt("binding count=%d manager=%s", [bindings.size(), get_path()])
for binding in bindings: for binding in bindings:
print("[SFX] try bind: target_node=", binding.target_node, " signal=", binding.signal_name) _dbg_fmt(
"try bind: target_node=%s signal=%s",
[binding.target_node, binding.signal_name]
)
if not has_node(binding.target_node): if not has_node(binding.target_node):
push_warning("[SFX] target node not found: %s (manager=%s)" % [binding.target_node, get_path()]) push_warning(
"[SFX] target node not found: %s (manager=%s)"
% [binding.target_node, get_path()]
)
continue continue
var target := get_node(binding.target_node) var target := get_node(binding.target_node)
print("[SFX] target resolved:", target, " path=", target.get_path()) _dbg_fmt("target resolved: %s path=%s", [target, target.get_path()])
if not target.has_signal(binding.signal_name): if not target.has_signal(binding.signal_name):
push_warning("[SFX] signal not found: %s on %s" % [binding.signal_name, target.get_path()]) push_warning(
"[SFX] signal not found: %s on %s"
% [binding.signal_name, target.get_path()]
)
continue continue
# 🔥 关键lambda 包一层 + 打印确认
var cb := func(ctx = null): var cb := func(ctx = null):
print("[SFX] callback fired! binding=", binding, " ctx=", ctx) _dbg_fmt(
"callback fired: binding=%s ctx=%s",
[binding, ctx]
)
_on_sfx_signal(binding, ctx) _on_sfx_signal(binding, ctx)
# 防止重复连接
if target.is_connected(binding.signal_name, cb): if target.is_connected(binding.signal_name, cb):
print("[SFX] already connected, skip:", binding.signal_name) _dbg_fmt("already connected: %s", [binding.signal_name])
else: else:
var err := target.connect(binding.signal_name, cb) var err := target.connect(binding.signal_name, cb)
print("[SFX] connect result =", err, " (OK=0)") _dbg_fmt("connect result=%d (OK=0)", [err])
func _on_sfx_signal(binding: SFXSignalBinding, args: Variant) -> void: func _on_sfx_signal(binding: SFXSignalBinding, args: Variant) -> void:
if enable_debug and binding.debug_print: if enable_debug and binding.debug_print:
@ -52,6 +62,16 @@ func _on_sfx_signal(binding: SFXSignalBinding, args: Variant) -> void:
if args is Dictionary and args.has("world_pos"): if args is Dictionary and args.has("world_pos"):
_play_at_position(binding, args["world_pos"]) _play_at_position(binding, args["world_pos"])
func _dbg(msg: String) -> void:
if not enable_debug:
return
print("[SFX]", msg)
func _dbg_fmt(fmt: String, args: Array = []) -> void:
if not enable_debug:
return
print("[SFX]", fmt % args)
# ----------------------------- # -----------------------------
# 播放逻辑 # 播放逻辑
# ----------------------------- # -----------------------------