Squelette 2D
Un bras articulé fait d'un seul Polygon2D skinné sur deux os. Tourner un Bone2D déplace les sommets pondérés dessus.
L'éditeur ci-dessus fait tourner ce projet. Changez une ligne et il se recharge.
Classes Godot utilisées
Skeleton2DBone2DPolygon2D
Le code
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)