Day 21: Rope

Cagri BenliCagri Benli
3 min read

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)
0
Subscribe to my newsletter

Read articles from Cagri Benli directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cagri Benli
Cagri Benli