关节链

十二个互相铰接的链节,末端挂着一个重物。点击推动链条,看它慢慢稳定下来。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

PinJoint2DRigidBody2DStaticBody2D

代码

scripts/main.gd
extends Node

const LINKS: int = 12
const LINK_SIZE: Vector2 = Vector2(18, 44)
const WEIGHT_RADIUS: float = 34.0

var weight: RigidBody2D


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	var top: Vector2 = Vector2(view.x * 0.5, view.y * 0.18)

	var anchor: StaticBody2D = StaticBody2D.new()
	anchor.position = top
	anchor.add_child(_make_rect(Vector2(60, 18), Color('#2a2f3d')))
	add_child(anchor)

	var previous: PhysicsBody2D = anchor

	for i in LINKS:
		var at: Vector2 = top + Vector2(0, LINK_SIZE.y * (i + 1))
		var link: RigidBody2D = _add_link(at, i == LINKS - 1)

		_pin(previous, link, at - Vector2(0, LINK_SIZE.y * 0.5))
		previous = link

	weight = previous as RigidBody2D
	weight.apply_impulse(Vector2(900, 0))

	print('the last link is heavier, click to push the chain again')


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.pressed and is_instance_valid(weight):
		weight.apply_impulse(Vector2(randf_range(-900, 900), -200))


func _add_link(at: Vector2, is_weight: bool) -> RigidBody2D:
	var body: RigidBody2D = RigidBody2D.new()
	body.position = at

	if is_weight:
		body.mass = 6.0

		var circle: CircleShape2D = CircleShape2D.new()
		circle.radius = WEIGHT_RADIUS

		var collision: CollisionShape2D = CollisionShape2D.new()
		collision.shape = circle

		body.add_child(_make_circle(WEIGHT_RADIUS, Color('#fc7f7f')))
		body.add_child(collision)
	else:
		var shape: RectangleShape2D = RectangleShape2D.new()
		shape.size = LINK_SIZE

		var collision: CollisionShape2D = CollisionShape2D.new()
		collision.shape = shape

		body.add_child(_make_rect(LINK_SIZE, Color('#8da5f3')))
		body.add_child(collision)

	add_child(body)

	return body


func _pin(a: PhysicsBody2D, b: PhysicsBody2D, at: Vector2) -> void:
	var joint: PinJoint2D = PinJoint2D.new()
	joint.position = at
	add_child(joint)

	joint.node_a = a.get_path()
	joint.node_b = b.get_path()


func _make_rect(size: Vector2, color: Color) -> Polygon2D:
	var rect: Polygon2D = Polygon2D.new()
	rect.polygon = PackedVector2Array([-size * 0.5, Vector2(size.x, -size.y) * 0.5, size * 0.5, Vector2(-size.x, size.y) * 0.5])
	rect.color = color

	return rect


func _make_circle(radius: float, color: Color) -> Polygon2D:
	var points: PackedVector2Array = PackedVector2Array()

	for i in 24:
		points.append(Vector2(cos(TAU * i / 24.0), sin(TAU * i / 24.0)) * radius)

	var circle: Polygon2D = Polygon2D.new()
	circle.polygon = points
	circle.color = color

	return circle


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	# ColorRect defaults to MOUSE_FILTER_STOP and would eat the click before _unhandled_input
	background.mouse_filter = Control.MOUSE_FILTER_IGNORE
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

更多物理示例