Godot实现Ledge grab抓边功能时RayCast2D反复启停异常问题
Godot 2D抓边功能RayCast检测器反复启停问题修复
问题相关实现代码
if $LedgeDetectorh.is_colliding()&&$LedgeDetectorh.get_collider() is Ledge: if !$LedgeDetectorv.is_colliding(): isLedgeGrabbed = true elif !$LedgeDetectorh2.is_colliding(): isLedgeGrabbed = false if isLedgeGrabbed: Motion = Vector2.ZERO applyGravity = false $LedgeDetectorv.enabled = false $LedgeDetectorh2.enabled = false if immediateInputLogic: onLedgeGrabbedIdle = false $LedgeTimer.start(5) else: onLedgeGrabbedIdle = true if onLedgeGrabbedIdle: Motion.y =0 Motion.x = 0 if $LedgeTimer.time_left ==0: if $LedgeDetectorh2.enabled == true: self.position.direction_to($LedgeDetectorh2.get_collision_point())* 50 elif $LedgeTimer.time_left > 0: print("works") Motion.y = lerp(Motion.y, -400,1) Motion.x = lerp(Motion.x, 100 * direction,0.2) elif !$LedgeDetectorh2.is_colliding(): $LedgeDetectorh.enabled = true $LedgeDetectorv.enabled = true $LedgeDetectorh2.enabled = true applyGravity = true
问题根因
检测器反复启停是逻辑闭环导致的死循环:
- 进入抓边状态时你主动将
$LedgeDetectorh2设为禁用,Godot中禁用状态的RayCast2D调用is_colliding()固定返回false - 下一帧运行到外层
elif !$LedgeDetectorh2.is_colliding()判断时条件成立,直接执行三个检测器全启用、开启重力的逻辑,退出抓边状态 - 检测器重启后立刻检测到Ledge碰撞,再次进入抓边状态、禁用
$LedgeDetectorh2,无限循环就出现了
附带的逻辑bug:
onLedgeGrabbedIdle分支下的位置偏移计算是死代码:计算得到的方向向量没有赋值给Motion或position直接丢弃,且进入该分支前$LedgeDetectorh2已经被禁用,分支内$LedgeDetectorh2.enabled == true的判断永远不成立- 抓边状态进出没有加前置校验,同一帧内可能多次修改
isLedgeGrabbed值,进一步加剧状态抖动
修复方案
核心修改原则:不要在抓边流程中频繁开关RayCast(RayCast启用后至少需要1帧才能返回正确碰撞结果,频繁开关会导致检测结果完全失效),给状态切换加互斥判断,避免循环触发。
修复后的参考代码:
# 仅在非抓边状态下检测抓边进入条件,避免帧内状态反复跳变 if not isLedgeGrabbed: if $LedgeDetectorh.is_colliding() && $LedgeDetectorh.get_collider() is Ledge: if !$LedgeDetectorv.is_colliding(): isLedgeGrabbed = true else: # 抓边状态下用玩家输入作为退出条件,不要依赖被禁用的射线检测 # 可根据自己的输入映射调整,比如按跳跃上爬、按下落松手 if Input.is_action_just_pressed("jump") || Input.is_action_just_pressed("drop"): isLedgeGrabbed = false if isLedgeGrabbed: Motion = Vector2.ZERO applyGravity = false # 移除频繁开关检测器的逻辑,保持检测器常开即可 # $LedgeDetectorv.enabled = false # $LedgeDetectorh2.enabled = false if immediateInputLogic: onLedgeGrabbedIdle = false $LedgeTimer.start(5) else: onLedgeGrabbedIdle = true if onLedgeGrabbedIdle: Motion = Vector2.ZERO if $LedgeTimer.time_left == 0: # 修复无效的位置计算,抓边对齐推荐直接读取Ledge上的预放置标记点,比射线计算稳定 if $LedgeDetectorh.get_collider() is Ledge: global_position = $LedgeDetectorh.get_collider().get_node("GrabPoint").global_position elif $LedgeTimer.time_left > 0: Motion.y = lerp(Motion.y, -400, 1) Motion.x = lerp(Motion.x, 100 * direction, 0.2) else: # 非抓边状态统一重置参数,不要加多余的碰撞判断避免漏重置 applyGravity = true onLedgeGrabbedIdle = false
优化建议
- 所有Ledge碰撞体场景里提前加一个
Marker2D标记抓边后角色应该对齐的位置,抓边时直接对齐到该点,比实时算射线偏移稳定很多,不会出现位置抖动 - 如果一定要在抓边时关闭多余射线,就不要用被关闭的射线做退出判断,单独写退出逻辑
- 抓边状态可以加一个冷却时间,退出抓边后0.2秒内不允许再次触发抓边,避免快速操作导致的状态跳变
内容的提问来源于stack exchange,提问作者a b
相关产品推荐
相关产品推荐

