Esqueleto 2D
Un brazo articulado hecho de un solo Polygon2D con skin en dos huesos. Girar un Bone2D mueve los vértices que tienen peso en él.
El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.
Clases de Godot usadas
Skeleton2DBone2DPolygon2D
El 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)