Skip to main content

Command Palette

Search for a command to run...

Day 21: Rope

Published
•3 min read•View as Markdown

This took around 3.5 hours of reading posts, checking out shaders, watching youtube tutorials, asking questions to chatgpt, reading godot documentations, going through github repos. So I hope this saves you some headache.

First off, here is the final result. In-game controlled end-points with “sag” parameter.

Creating the Mesh

And how to create step-by-step:

  • Create a mesh in blender

    • Add circle with 6 segments (Ctrl + A)

    • Press E(Extrude)

    • Move on Z axis by 0.2m (8inches)

    • Repeat extrude and move steps as many times you want (10-15 is good)

    • I also added “caps” to the end by selecting the edges on ends and pressing F (add face)

    • Then Shift + N to recalculate normals, so they are looking outside

You’ll have something like below at the end.

  • Return to object mode

  • Press Ctrl + A and apply all transformations

Adding the Armature (bones)

We’ll then add armature.

  • Add an armature (Shift + A, then type “armature”)

  • You’ll have a single “bone”

  • Go into edit mode select and move (press G then Z) the top part of the bone to the first line

  • Press E then Z to extrude and move on Z axis to the next line

  • Repeat until you reach to the end of the mesh

  • Then go to object mode

  • Select the mesh

  • Press and hold shift → select the armature (bones)

  • Press Ctrl + P → With Automatic Weights

The Weird Part

Now the part that none of the tutorials mentioned, maybe it isn’t required maybe something was wrong with my setup idk.

  • For each bone

    • Press Alt + P

    • Select Disconnect Bone

Export

One different thing while exporting is:

  • Under Data → Mesh → Check Apply Modifiers

  • Google said don’t apply the modifier yourself manually, so I didn’t :D

Godot Setup

Here’s the hierarchy for the scene:

And the script:

@tool
class_name Rope3D extends Node3D

@export var skeleton: Skeleton3D
@export var top_target: Node3D
@export var bottom_target: Node3D
@export var slack: float = 0
@export var count: int = 15

func _ready():
    _set_positions(bottom_target.global_transform, top_target.global_transform)

func _process(_delta):
    if not skeleton or not top_target or not bottom_target:
        return 
    if Engine.is_editor_hint():
        _set_positions(bottom_target.global_transform, top_target.global_transform)

func _set_positions(A_global: Transform3D, B_global: Transform3D) -> void:
    var A := skeleton.global_transform.affine_inverse() * A_global
    var B := skeleton.global_transform.affine_inverse() * B_global

    for i in range(count):
        var t := i / (count - 1.0)

        var final_xform: Transform3D

        if i == 0:
            final_xform = A
        elif i == count - 1:
            final_xform = B
        else:
            var pos := A.origin.lerp(B.origin, t)
            pos.y -= sin(t * PI) * slack

            var basis := A.basis.slerp(B.basis, t)
            final_xform = Transform3D(basis, pos)

        skeleton.set_bone_global_pose_override(i, final_xform, 1.0, false)