Esqueleto 2D

Um braço articulado feito de um único Polygon2D com skin em dois ossos. Girar um Bone2D move os vértices com peso nele.

O editor acima está rodando este projeto. Mude uma linha e ele recarrega.

Classes do Godot usadas

Skeleton2DBone2DPolygon2D

O código

scripts/main.gd
extends Node

const UPPER: float = 200.0
const FORE: float = 180.0
const WIDTH: float = 58.0
const COLUMNS: int = 14

var arm_polygon: PackedVector2Array


func _ready() -> void:
	_add_background()
	_add_label('one Polygon2D, two Bone2D, no sprite', Vector2(40, 40))

	var view: Vector2 = get_viewport().get_visible_rect().size

	var rig: Node2D = Node2D.new()
	rig.position = Vector2(view.x * 0.36, view.y * 0.62)
	add_child(rig)

	var skeleton: Skeleton2D = Skeleton2D.new()
	skeleton.name = 'Skeleton'
	rig.add_child(skeleton)

	var upper: Bone2D = Bone2D.new()
	upper.name = 'Upper'
	skeleton.add_child(upper)
	upper.rest = upper.transform

	var fore: Bone2D = Bone2D.new()
	fore.name = 'Fore'
	fore.position = Vector2(UPPER, 0)
	upper.add_child(fore)
	fore.rest = fore.transform

	arm_polygon = _make_strip()

	var arm: Polygon2D = Polygon2D.new()
	arm.polygon = arm_polygon
	arm.polygons = _make_quads()
	arm.color = Color('#8da5f3')
	rig.add_child(arm)
	arm.skeleton = arm.get_path_to(skeleton)
	arm.add_bone(NodePath('Upper'), _weights(false))
	arm.add_bone(NodePath('Upper/Fore'), _weights(true))

	_add_joint(upper, Vector2.ZERO, 20.0, Color('#f3d48d'))
	_add_joint(fore, Vector2.ZERO, 16.0, Color('#f3d48d'))
	_add_joint(fore, Vector2(FORE, 0), 22.0, Color('#8eef97'))

	var swing: Tween = create_tween().set_loops()
	swing.tween_property(upper, 'rotation', deg_to_rad(-55), 0.9).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
	swing.tween_property(upper, 'rotation', deg_to_rad(20), 0.9).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)

	var bend: Tween = create_tween().set_loops()
	bend.tween_property(fore, 'rotation', deg_to_rad(-115), 0.6).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
	bend.tween_property(fore, 'rotation', deg_to_rad(-5), 0.6).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)

	print('the arm is a single polygon, the bones move its vertices')


func _make_strip() -> PackedVector2Array:
	var top: PackedVector2Array = PackedVector2Array()
	var bottom: PackedVector2Array = PackedVector2Array()
	var total: float = UPPER + FORE

	for i in COLUMNS + 1:
		var x: float = total * i / COLUMNS
		top.append(Vector2(x, -WIDTH * 0.5))
		bottom.append(Vector2(x, WIDTH * 0.5))

	bottom.reverse()

	return top + bottom


# ear clipping on a strip of collinear points makes triangles that span the elbow
func _make_quads() -> Array:
	var quads: Array = []

	for i in COLUMNS:
		var bottom_index: int = COLUMNS + 1 + (COLUMNS - i)
		quads.append(PackedInt32Array([i, i + 1, bottom_index - 1, bottom_index]))

	return quads


func _weights(distal: bool) -> PackedFloat32Array:
	var weights: PackedFloat32Array = PackedFloat32Array()

	for point in arm_polygon:
		var blend: float = clampf((point.x - UPPER + 40.0) / 80.0, 0.0, 1.0)
		weights.append(blend if distal else 1.0 - blend)

	return weights


func _add_joint(bone: Bone2D, at: Vector2, radius: float, color: Color) -> void:
	var dot: Polygon2D = Polygon2D.new()
	dot.polygon = _make_circle(radius)
	dot.color = color
	dot.position = at
	dot.z_index = 1
	bone.add_child(dot)


func _make_circle(radius: float) -> PackedVector2Array:
	var points: PackedVector2Array = PackedVector2Array()

	for i in 24:
		var angle: float = TAU * i / 24
		points.append(Vector2(cos(angle), sin(angle)) * radius)

	return points


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)


func _add_label(text: String, at: Vector2) -> void:
	var label: Label = Label.new()
	label.text = text
	label.position = at
	label.add_theme_color_override('font_color', Color('#999999'))
	add_child(label)

Mais exemplos de Animação