Search code examples
2dgame-developmentgdscript

Having problems with implementing Ledge grab



   
   
   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

problem is as far as I can tell raycast2ds keep on getting enabled and false all the time made some conditions to only enable them if is not colliding with wall still no luck


Solution

  • I'd say keep your multiple RayCast2D enabled. If your goal is to optimize the code, this is not the way (for that I'd suggest intersect_ray). However, first let us make sure it works correctly.

    By the way, you can use is_stopped() instead of time_left.

    I'm confused about what approach you are going for. I'm aware of a few approaches to do this:

    1. Two horizontal RayCast2D, both horizontal, and parallel to each other, checking for the wall. One on the bottom and one on the top. If the bottom one detects a wall, but the top one does not, it means it there is a ledge. This is easy to implement, but does not give much information for animation/Inverse kinematics. You know there is a ledge, but not exactly where.

    2. Again two RayCast2D. This time one is horizontal and detects a wall, and the other one is vertical, casting downwards from the top, and it is trying to find the floor where the player would stand if it climbed up the ledge. With the catch that you should position the vertical RayCast2D according to the position where the horizontal collided. Which reminds me, use force_update_transform and force_raycast_update if you moved the RayCast2D from code.

    3. With collaboration from ledge objects. You can add small Area2Ds for each ledge on the designer, and if the player overlaps any of them, then it is on a ledge.

    4. Again with collaboration from ledge objects, but this time they are floor extensions that the player can check with a RayCast2D directly upwards. So an horizontal ReyCast2D finds the wall, and a vertical RayCast2D finds the floor extensions that identifies the ledge. Taking care of collision layers and masks so these floor extensions do not interfere with anything else.

    Having these ledge objects is great for animation (in particular in 3D), since you can get very precise positions for that inverse kinematics. But it means that a designer has to make sure they are placed in the world, and in any area without them the player can't climb. Don't tell me, you have seen that in games.

    You already have ledge objects, don't you? I think it would be easy to add an Area2D to them and use the third approach I listed above, which does not require RayCast2D at all.

    So the Ledge would be or have an Area2D that connects "body_entered" to the Area2D itself. When the signal triggers, the Area2D will get a reference to the object that entered. Make sure it is the player, and call a method on it, letting it know there is a ledge it can grab (it can pass its own position as parameter so the player script can snap to it).


    Addendum

    Let us go over the third approach as described. There will be an Area2D for each ledge. This Area2D will have a script, and the class name is Ledge:

    class_name Ledge extends Area2D
    

    So the Ledge would be or have an Area2D that connects "body_entered" to the Area2D itself. When the signal triggers, the Area2D will get a reference to the object that entered.

    I'll do the signal connections from code. As to avoid any mistake in connecting it via the editor.

    class_name Ledge extends Area2D
    
    func _ready() -> void:
        connect("body_entered", self, "_on_body_entered")
    
    func _on_body_entered(body:Node) -> void:
        pass
    

    Here body is the reference of the body that entered.

    Make sure it is the player

    Hopefully you can use collision masks and layers to narrow the checks. I don't know how you would identify the player, but I'll venture to guess it has a class name Player, so I can check like this:

    class_name Ledge extends Area2D
    
    func _ready() -> void:
        connect("body_entered", self, "_on_body_entered")
    
    func _on_body_entered(body:Node) -> void:
        if body is Player:
            pass
    

    and call a method on it, letting it know there is a ledge it can grab (it can pass its own position as parameter so the player script can snap to it).

    So you can either have the Area2D pass itself, or pass its global position. In the code below go for the global position. I'll call the method it calls on player on_ledge, which also means we can check for the method with has_method (I'll replace the check for that).

    class_name Ledge extends Area2D
    
    func _ready() -> void:
        connect("body_entered", self, "_on_body_entered")
    
    func _on_body_entered(body:Node) -> void:
        if body.has_method("on_legde"):
            body.on_ledge(global_position)
    

    And you would, of course, add that method to the player script:

    func on_ledge(ledge_global_position:Vector2) -> void:
        print(ledge_global_position)
    

    There you can snap the player position, play an animation or whatever is appropriate.


    Perhaps passing the Area2D itself is better, we could do this:

    class_name Ledge extends Area2D
    
    func _ready() -> void:
        connect("body_entered", self, "_on_body_entered")
    
    func _on_body_entered(body:Node) -> void:
        if body.has_method("on_legde"):
            body.on_ledge(self)
    

    And then on the player:

    var current_ledge:Area2D = null
    
    func on_ledge(ledge:Area2D) -> void:
        current_ledge = ledge
    
    func _physics_process(_delta:float) -> void:
        if not is_instance_valid(current_ledge) or not current_ledge.overlaps_body(self):
            current_ledge = null
    
        if current_ledge != null:
            var current_ledge_position = current_ledge.global_position
            # do something with the ledge position