Curve 资源

逐点构建并用 sample(t) 读取的 Curve。图表由驱动方块的同一个资源绘制,旁边是一个只按时间移动的方块。

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

用到的 Godot 类

CurveLine2DPolygon2D

代码

scripts/main.gd
extends Node

const DURATION: float = 2.0
const GRAPH_ORIGIN: Vector2 = Vector2(150, 520)
const GRAPH_SIZE: Vector2 = Vector2(400, 300)
const TRACK_LEFT: float = 690.0
const TRACK_RIGHT: float = 1150.0
const TRACK_Y: float = 340.0
const LINEAR_Y: float = 470.0
const SAMPLES: int = 96

const POINTS: Array = [
	{at = Vector2(0.0, 0.0), left = 0.0, right = 2.6},
	{at = Vector2(0.32, 1.15), left = 0.9, right = -0.4},
	{at = Vector2(0.58, 0.86), left = -0.2, right = 0.4},
	{at = Vector2(0.8, 1.05), left = 0.3, right = -0.1},
	{at = Vector2(1.0, 1.0), left = -0.2, right = 0.0},
]

var curve: Curve
var marker: Polygon2D
var playhead: Line2D
var box: Polygon2D
var linear_box: Polygon2D
var elapsed: float = 0.0


func _ready() -> void:
	_add_background()
	_add_label('a Curve built by code, read with sample(t)', Vector2(40, 40), Color('#999999'))
	_add_label('driven by curve.sample(t)', Vector2(690, 262), Color('#f3d48d'))
	_add_label('driven by t alone', Vector2(690, 392), Color('#4a5162'))

	curve = Curve.new()
	curve.min_value = -0.2
	curve.max_value = 1.3

	for point in POINTS:
		curve.add_point(point.at, point.left, point.right)

	_add_frame()

	var plot: Line2D = Line2D.new()
	plot.width = 4.0
	plot.default_color = Color('#8da5f3')

	for i in SAMPLES + 1:
		var t: float = float(i) / SAMPLES
		plot.add_point(_graph_point(t, curve.sample(t)))

	add_child(plot)

	playhead = Line2D.new()
	playhead.width = 1.0
	playhead.default_color = Color('#4a5162')
	playhead.add_point(_graph_point(0.0, curve.min_value))
	playhead.add_point(_graph_point(0.0, curve.max_value))
	add_child(playhead)

	marker = _make_circle(11.0, Color('#8eef97'))
	add_child(marker)

	_add_rail(TRACK_Y)
	_add_rail(LINEAR_Y)

	box = _make_square(58.0, Color('#f3d48d'))
	add_child(box)

	linear_box = _make_square(44.0, Color('#2a2f3d'))
	add_child(linear_box)

	print('the curve overshoots past 1, so the top square passes the rail end and comes back')


func _process(delta: float) -> void:
	elapsed = fmod(elapsed + delta, DURATION)

	var t: float = elapsed / DURATION
	var value: float = curve.sample(t)

	marker.position = _graph_point(t, value)
	box.position = Vector2(lerpf(TRACK_LEFT, TRACK_RIGHT, value), TRACK_Y)
	linear_box.position = Vector2(lerpf(TRACK_LEFT, TRACK_RIGHT, t), LINEAR_Y)
	playhead.set_point_position(0, _graph_point(t, curve.min_value))
	playhead.set_point_position(1, _graph_point(t, curve.max_value))


func _graph_point(t: float, value: float) -> Vector2:
	var normalized: float = (value - curve.min_value) / (curve.max_value - curve.min_value)

	return GRAPH_ORIGIN + Vector2(GRAPH_SIZE.x * t, -GRAPH_SIZE.y * normalized)


func _add_rail(at_y: float) -> void:
	var rail: Line2D = Line2D.new()
	rail.width = 3.0
	rail.default_color = Color('#2a2f3d')
	rail.add_point(Vector2(TRACK_LEFT, at_y))
	rail.add_point(Vector2(TRACK_RIGHT, at_y))
	add_child(rail)


func _add_frame() -> void:
	var border: Line2D = Line2D.new()
	border.width = 2.0
	border.default_color = Color('#2a2f3d')
	border.closed = true
	border.add_point(GRAPH_ORIGIN + Vector2(0, -GRAPH_SIZE.y))
	border.add_point(GRAPH_ORIGIN + Vector2(GRAPH_SIZE.x, -GRAPH_SIZE.y))
	border.add_point(GRAPH_ORIGIN + Vector2(GRAPH_SIZE.x, 0))
	border.add_point(GRAPH_ORIGIN)
	add_child(border)

	for value in [0.0, 1.0]:
		var guide: Line2D = Line2D.new()
		guide.width = 1.0
		guide.default_color = Color('#333a4a')
		guide.add_point(_graph_point(0.0, value))
		guide.add_point(_graph_point(1.0, value))
		add_child(guide)

		_add_label('%.1f' % value, _graph_point(0.0, value) - Vector2(42, 14), Color('#4a5162'))


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

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

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

	return circle


func _make_square(size: float, color: Color) -> Polygon2D:
	var square: Polygon2D = Polygon2D.new()
	square.polygon = PackedVector2Array(
		[
			Vector2(-size, -size) * 0.5,
			Vector2(size, -size) * 0.5,
			Vector2(size, size) * 0.5,
			Vector2(-size, size) * 0.5,
		]
	)
	square.color = color

	return square


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, color: Color) -> void:
	var label: Label = Label.new()
	label.text = text
	label.position = at
	label.add_theme_color_override('font_color', color)
	add_child(label)

更多动画示例