Godot Version
Godot 4.3 stable
Question
I’m working on a snake-like movement system by dynamically generating bones, using the following structure:
@tool
extends Skeleton3D
const MAX_BONES = 10
func _ready() -> void:
clear_bones()
# Criando os bones dinamicamente
for i in range(MAX_BONES):
var bone_name = "bone{}".format([i], "{}")
add_bone(bone_name)
Node setup:
Skeleton3D
└ PhysicalBoneSimulator3D
└ PhysicalBone3D (1 to 10)
└ PinJoint (1 to 9) connecting the bones
Initially, I used PinJoints to connect the bones, and while the movement was almost what I wanted, it felt too rigid and didn’t behave fluidly as I imagined.
So I decided to switch to distance constraints after watching this video. The movement improved and became more flexible, but when I try to move the character, the bone structure collapses and falls apart.
Here’s a GIF showing the issue:
When using PinJoints, the bones remain stable as expected, especially when I lock the axes (axis_lock_angular_x, axis_lock_angular_z) on the bones I want to stay rigid. However, applying the same axis locking doesn’t seem to work when using distance constraints.
My Question:
Why does the structure break apart when using distance constraints, and why doesn’t the axis locking work in this case? Is there a better way to maintain a stable yet flexible snake-like movement using distance constraints or other alternatives? How can I keep the structure intact while moving?
Here is the complete movement code:
extends PhysicalBoneSimulator3D
@export var head: PhysicalBone3D # The main bone (head) that will lead the movement
@export var distance: float = 1.0
@export var speed: float = 10.0
@onready var bone_list: Array[PhysicalBone3D] = []
func _ready() -> void:
physical_bones_start_simulation()
for child in self.get_children():
if child is PhysicalBone3D:
bone_list.append(child)
func _physics_process(delta: float) -> void:
move_head(delta)
for i in range(bone_list.size() - 1):
apply_bone_constraint(bone_list[i], bone_list[i + 1])
func move_head(delta: float) -> void:
var move_speed = 8.0
var movement = Vector3.ZERO
if Input.is_action_pressed("ui_right"):
movement.x += move_speed
if Input.is_action_pressed("ui_left"):
movement.x -= move_speed
if Input.is_action_pressed("ui_up"):
movement.z -= move_speed
if Input.is_action_pressed("ui_down"):
movement.z += move_speed
var target_position = head.global_position + movement * speed * delta
var new_pos := constraint_distance(head.global_position, target_position, distance)
head.global_position = head.global_position.lerp(new_pos, 0.1)
func apply_bone_constraint(bone_a: PhysicalBone3D, bone_b: PhysicalBone3D) -> void:
var anchor := bone_a.position
var new_pos := constraint_distance(anchor, bone_b.position, distance)
bone_b.position = new_pos
func constraint_distance(anchor: Vector3, point: Vector3, dist: float) -> Vector3:
var direction := point - anchor
var current_distance := direction.length()
if current_distance > dist:
direction = direction.normalized() * dist
else:
direction = direction.normalized() * current_distance
return anchor + direction