Skip to main content

Scripting

Everything a SoftBody2D generates is ordinary Godot nodes, so you can reach in and use them directly. The full list is in the Class Reference.

Reacting to a tear

joint_removed fires for every broken joint, body_detached when a body loses its last one, and get_clusters() returns the connected pieces.

func _ready() -> void:
$SoftBody2D.joint_removed.connect(_on_joint_removed)
$SoftBody2D.body_detached.connect(_on_body_detached)

func _on_joint_removed(a: SoftBody2D.SoftBodyChild, _b: SoftBody2D.SoftBodyChild) -> void:
# Play a rip sound where the joint gave way.
$Rip.global_position = a.rigidbody.global_position
$Rip.play()

func _on_body_detached(child: SoftBody2D.SoftBodyChild) -> void:
# Detached bodies are frozen with their shape disabled. Let this one live as debris.
var body := child.rigidbody as RigidBody2D
body.freeze = false
child.shape.disabled = false

func _process(_delta: float) -> void:
if $SoftBody2D.get_clusters().size() > 1:
print("the softbody is in pieces")

Cutting

cut() takes two global points and removes every joint the segment crosses, returning how many went.

func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
_slice_from = get_global_mouse_position()
elif event is InputEventMouseButton and not event.pressed:
var removed := $SoftBody2D.cut(_slice_from, get_global_mouse_position())
print("cut %d joints" % removed)

Pushing it around

apply_impulse() and apply_force() push every body equally. apply_impulse_at_point() pushes from a point in the world with the correct lever arm, which is what explosions want.

func explode(at: Vector2) -> void:
$SoftBody2D.apply_impulse_at_point(Vector2(0, -400), at, 300.0)

Individual bodies are yours too:

for child in $SoftBody2D.get_rigid_bodies():
var body := child.rigidbody as RigidBody2D
body.apply_central_impulse(Vector2(0, -50))

To drag one around, push the nearest body toward the mouse rather than teleporting it:

func _physics_process(_delta: float) -> void:
if _grabbed:
var to_mouse := get_global_mouse_position() - _grabbed.global_position
_grabbed.apply_central_force((to_mouse * 220.0 - _grabbed.linear_velocity * 12.0) * _grabbed.mass)

Generating at runtime

In the editor a softbody rebuilds itself whenever a property changes. At runtime nothing rebuilds automatically: set the properties, then call create_softbody2d(true) once.

var softbody := SoftBody2D.new()
softbody.texture = load("res://blob.png")
softbody.vertex_interval = 40
softbody.joint_reach = 2
add_child(softbody)
softbody.create_softbody2d(true)

baked fires when generation finishes, with the number of bodies created.

Custom bodies

Set Rigidbody Scene to a scene whose root is a RigidBody2D and every body is instantiated from it, so your own script rides along. Extend SoftBody2DRigidBody to be told which shape you got and whether you are the centre body:

extends SoftBody2DRigidBody

func _ready() -> void:
rigidbody_created.connect(func(shape: CollisionShape2D, is_center: bool):
if is_center:
add_to_group("softbody_centre"))