Day 21: Rope

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)
Subscribe to my newsletter
Read articles from Cagri Benli directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
