Is it possible to keyframe the points of a Curve2D or Path2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By avencherus
:warning: Old Version Published before Godot 3 was released.

Before I post a feature request, I wanted to check to see if I was overlooking something.

It doesn’t seem like it’s possible to manipulate a curve in the editor and keyframe it. The keyframing deals with swapping curves, so you can’t get interpolation across the the points and the in/out handles.

Support my old request :smiley:
https://github.com/godotengine/godot/issues/6914

Exposing that resource data for editing and copy-paste too is needed.


To make it by hand, I have tried animating a exported Vector2:
https://twitter.com/_eons/status/782926658739245056

That was using setget to modify a curve point and works, but I have no idea how to use setget with a Vector2Array.

Animating Paths need some extra details too (apart from bezier in-out corrections) because PathFollow offset may change position in some cases.

eons | 2016-11-27 13:39

That is interesting. I’ll probably look more into that in the near future.

avencherus | 2016-11-27 17:10

With the keyframe issue mentioned on GH, I see it a bit more difficult to make than I expected.

It may need a different approach, maybe using tweens…

eons | 2016-11-27 17:33

I can’t imagine a use case for using AnimationPlayer to animate individual points of a Curve2D. And even if it was possible, it would be complicated IMO :s

I tried this anyways:

extends Path2D

func _get(key):
	if key.begins_with("point"):
		# So we can access point X with 'point/X'
		# Notice it's very inefficient on a larger scale because the key is
		# not a StringName and the index must be parsed
		var i = key.to_int()
		return get_curve().get_point_pos(i)

func _set(key):
	if key.begins_with("point"):
		var i = key.to_int()
		get_curve().set_point(i)
		update()

So now it’s possible to call curve.get("point/42") or set on any point, but AnimationPlayer failed at animating it, so I must be missing something:

ERROR: AnimationPlayer::_animation_update_transforms: Failed setting key at time 0.299121 in Animation 'New Anim', Track 'Path2D:point/0'. Check if property exists or the type of key is right for the property
At: scene\animation\animation_player.cpp:671

Edit: oh. Yeah, I probably forgot to override _get_property_list() too. That would mean writing a for loop to “simulate” as many properties as point count.

I still prefer animating the curve in another way, really :'D

Zylann | 2016-11-28 14:00

My thought was that it could assist in animating things like chains and such if you could put segments on a path and animate the curve itself.

And it would be useful if the AnimationPlayer had the functionality to keyframe and interpolate between all the curve’s point from just changing the curve and keyframing it’s new position.

What is your preference for curve animation?

avencherus | 2016-11-28 16:19

If I really were to animate specifically a curve with AnimationPlayer (so, completely deterministic), I would create Position2D nodes over it, link them to specific curve points, then animate the nodes position. Not ideal, but way less hacky than making curve indexes behave like properties at the moment.
But again, I don’t see a concrete use case for that (chains?).

Zylann | 2016-11-28 20:28

A potential use case could be like that deformed platform in my link and some simple effects.


The general use for keyframed array elements may be good for polygon shapes and other stuff like that.

Of course everything can be done by code but a visual way could be interesting.

eons | 2016-11-28 21:27

I would not animate the curve for such deformations, but preferably 2D skinning/rigging (like in 3D, possibly coming in Godot 3.0), which is way more efficient. If it’s a platform then good luck with physics too^^

Zylann | 2016-11-29 00:43

Worked better than expected for a staticbody with bad polygons :stuck_out_tongue: , pure shape based custom living platform physics could be possible.

But yeah, is not the best example ^_^;

eons | 2016-11-29 01:37

That would be interesting, I hope something like that comes along.

avencherus | 2016-11-29 05:06

I tried to implement _get_property_list(), for science:

func _get_property_list():
	var props = []
	props.resize(get_curve().get_point_count())
	for i in range(0, props.size()):
		props[i] = {
			"name": "point/" + str(i),
			"type": TYPE_VECTOR2
		}
	return props

It didn’t worked and it was not even called :C

So I assume you did this eons?
(I am sorry, so sorry (why is there no scrollbar?))

extends Path2D


export(Vector2) var point0 setget set_point0, get_point0
export(Vector2) var point1 setget set_point1, get_point1
export(Vector2) var point2 setget set_point2, get_point2
export(Vector2) var point3 setget set_point3, get_point3
export(Vector2) var point4 setget set_point4, get_point4
export(Vector2) var point5 setget set_point5, get_point5
export(Vector2) var point6 setget set_point6, get_point6
export(Vector2) var point7 setget set_point7, get_point7
export(Vector2) var point8 setget set_point8, get_point8
export(Vector2) var point9 setget set_point9, get_point9
export(Vector2) var point10 setget set_point10, get_point10
export(Vector2) var point11 setget set_point11, get_point11
export(Vector2) var point12 setget set_point12, get_point12
export(Vector2) var point13 setget set_point13, get_point13
export(Vector2) var point14 setget set_point14, get_point14
export(Vector2) var point15 setget set_point15, get_point15
export(Vector2) var point16 setget set_point16, get_point16
export(Vector2) var point17 setget set_point17, get_point17
export(Vector2) var point18 setget set_point18, get_point18
export(Vector2) var point19 setget set_point19, get_point19
export(Vector2) var point20 setget set_point20, get_point20
export(Vector2) var point21 setget set_point21, get_point21
export(Vector2) var point22 setget set_point22, get_point22
export(Vector2) var point23 setget set_point23, get_point23



func set_point0(pos):
	var curve = get_curve()
	if 0 < curve.get_point_count():
		get_curve().set_point_pos(0, pos)

func get_point0():
	var curve = get_curve()
	if 0 < curve.get_point_count():
		return curve.get_point_pos(0)


func set_point1(pos):
	var curve = get_curve()
	if 1 < curve.get_point_count():
		get_curve().set_point_pos(1, pos)

func get_point1():
	var curve = get_curve()
	if 1 < curve.get_point_count():
		return curve.get_point_pos(1)


func set_point2(pos):
	var curve = get_curve()
	if 2 < curve.get_point_count():
		get_curve().set_point_pos(2, pos)

func get_point2():
	var curve = get_curve()
	if 2 < curve.get_point_count():
		return curve.get_point_pos(2)


func set_point3(pos):
	var curve = get_curve()
	if 3 < curve.get_point_count():
		get_curve().set_point_pos(3, pos)

func get_point3():
	var curve = get_curve()
	if 3 < curve.get_point_count():
		return curve.get_point_pos(3)


func set_point4(pos):
	var curve = get_curve()
	if 4 < curve.get_point_count():
		get_curve().set_point_pos(4, pos)

func get_point4():
	var curve = get_curve()
	if 4 < curve.get_point_count():
		return curve.get_point_pos(4)


func set_point5(pos):
	var curve = get_curve()
	if 5 < curve.get_point_count():
		get_curve().set_point_pos(5, pos)

func get_point5():
	var curve = get_curve()
	if 5 < curve.get_point_count():
		return curve.get_point_pos(5)


func set_point6(pos):
	var curve = get_curve()
	if 6 < curve.get_point_count():
		get_curve().set_point_pos(6, pos)

func get_point6():
	var curve = get_curve()
	if 6 < curve.get_point_count():
		return curve.get_point_pos(6)


func set_point7(pos):
	var curve = get_curve()
	if 7 < curve.get_point_count():
		get_curve().set_point_pos(7, pos)

func get_point7():
	var curve = get_curve()
	if 7 < curve.get_point_count():
		return curve.get_point_pos(7)


func set_point8(pos):
	var curve = get_curve()
	if 8 < curve.get_point_count():
		get_curve().set_point_pos(8, pos)

func get_point8():
	var curve = get_curve()
	if 8 < curve.get_point_count():
		return curve.get_point_pos(8)


func set_point9(pos):
	var curve = get_curve()
	if 9 < curve.get_point_count():
		get_curve().set_point_pos(9, pos)

func get_point9():
	var curve = get_curve()
	if 9 < curve.get_point_count():
		return curve.get_point_pos(9)


func set_point10(pos):
	var curve = get_curve()
	if 10 < curve.get_point_count():
		get_curve().set_point_pos(10, pos)

func get_point10():
	var curve = get_curve()
	if 10 < curve.get_point_count():
		return curve.get_point_pos(10)


func set_point11(pos):
	var curve = get_curve()
	if 11 < curve.get_point_count():
		get_curve().set_point_pos(11, pos)

func get_point11():
	var curve = get_curve()
	if 11 < curve.get_point_count():
		return curve.get_point_pos(11)


func set_point12(pos):
	var curve = get_curve()
	if 12 < curve.get_point_count():
		get_curve().set_point_pos(12, pos)

func get_point12():
	var curve = get_curve()
	if 12 < curve.get_point_count():
		return curve.get_point_pos(12)


func set_point13(pos):
	var curve = get_curve()
	if 13 < curve.get_point_count():
		get_curve().set_point_pos(13, pos)

func get_point13():
	var curve = get_curve()
	if 13 < curve.get_point_count():
		return curve.get_point_pos(13)


func set_point14(pos):
	var curve = get_curve()
	if 14 < curve.get_point_count():
		get_curve().set_point_pos(14, pos)

func get_point14():
	var curve = get_curve()
	if 14 < curve.get_point_count():
		return curve.get_point_pos(14)


func set_point15(pos):
	var curve = get_curve()
	if 15 < curve.get_point_count():
		get_curve().set_point_pos(15, pos)

func get_point15():
	var curve = get_curve()
	if 15 < curve.get_point_count():
		return curve.get_point_pos(15)


func set_point16(pos):
	var curve = get_curve()
	if 16 < curve.get_point_count():
		get_curve().set_point_pos(16, pos)

func get_point16():
	var curve = get_curve()
	if 16 < curve.get_point_count():
		return curve.get_point_pos(16)


func set_point17(pos):
	var curve = get_curve()
	if 17 < curve.get_point_count():
		get_curve().set_point_pos(17, pos)

func get_point17():
	var curve = get_curve()
	if 17 < curve.get_point_count():
		return curve.get_point_pos(17)


func set_point18(pos):
	var curve = get_curve()
	if 18 < curve.get_point_count():
		get_curve().set_point_pos(18, pos)

func get_point18():
	var curve = get_curve()
	if 18 < curve.get_point_count():
		return curve.get_point_pos(18)


func set_point19(pos):
	var curve = get_curve()
	if 19 < curve.get_point_count():
		get_curve().set_point_pos(19, pos)

func get_point19():
	var curve = get_curve()
	if 19 < curve.get_point_count():
		return curve.get_point_pos(19)


func set_point20(pos):
	var curve = get_curve()
	if 20 < curve.get_point_count():
		get_curve().set_point_pos(20, pos)

func get_point20():
	var curve = get_curve()
	if 20 < curve.get_point_count():
		return curve.get_point_pos(20)


func set_point21(pos):
	var curve = get_curve()
	if 21 < curve.get_point_count():
		get_curve().set_point_pos(21, pos)

func get_point21():
	var curve = get_curve()
	if 21 < curve.get_point_count():
		return curve.get_point_pos(21)


func set_point22(pos):
	var curve = get_curve()
	if 22 < curve.get_point_count():
		get_curve().set_point_pos(22, pos)

func get_point22():
	var curve = get_curve()
	if 22 < curve.get_point_count():
		return curve.get_point_pos(22)


func set_point23(pos):
	var curve = get_curve()
	if 23 < curve.get_point_count():
		get_curve().set_point_pos(23, pos)

func get_point23():
	var curve = get_curve()
	if 23 < curve.get_point_count():
		return curve.get_point_pos(23)

Zylann | 2016-11-29 13:45

@zylann hahaha, something like that but only with 1-2 points, i’m not a crazy scientist xD

eons | 2016-11-29 17:18

I do like that work around, it’s clever. Though it’s a shame it can’t easily be compacted.

avencherus | 2016-11-29 18:31

Even then I still prefer having actual nodes to automate.
If you modify the curve after animating it, there is a very high chance you will break the animation and have to remake it (because indexes can change), while a Node is named and can easily detect which point is the nearest and affect it when it moves.
Also, you see them without having to run the game :slight_smile:

Zylann | 2016-11-29 18:44

Thanks Zylann, as usual, you’ve been very helpful. :slight_smile:

Those are two good ideas to experiment with.

avencherus | 2016-11-29 18:50

Well, to see it playing with exported vars (on editor and path debug), making it a tool and update after setter is enough (won’t update editor in-outs though).

eons | 2016-11-29 19:15

And that thing that some nodes do, like VButtonArray, it adds something like an array to the inspector (with button/count) and everything there is accesible from animationplayer.

I wonder if something like that could be done with curves from plugin/tool, without making a module.
Maybe not exposing the real curve but an array (or dictionary?).

eons | 2016-11-29 22:13

The button array thing probably relies on the property list (that works at a low level with _get(), _set() and _get_property_list() as far as I understand). I was able to hide existing properties by overriding that method. That’s why I tried that first but I have no idea why it doesn’t work for adding “fictive” properties.

Zylann | 2016-11-29 23:19