Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easier to tween properties with custom easings #7807

Closed
KoBeWi opened this issue Sep 22, 2023 · 1 comment · Fixed by godotengine/godot#82306
Closed

Make it easier to tween properties with custom easings #7807

KoBeWi opened this issue Sep 22, 2023 · 1 comment · Fixed by godotengine/godot#82306
Milestone

Comments

@KoBeWi
Copy link
Member

KoBeWi commented Sep 22, 2023

Describe the project you are working on

Godot

Describe the problem or limitation you are having in your project

There were a couple proposals about tweening using curves and the topic seems to re-appear from time to time. The reason it wasn't implemented yet is that it's easy to achieve it using MethodTweener:

tween.tween_method(func(v): position.y = curve.sample_baked(v), 0, curve.get_baked_length(), 1.0)

There is however a rather prominent problem with that: the property you want to animate has to be hard-coded in the method (in this case the method is a lambda). Alternative is passing object and property to the method, but then we just end up with wannabe PropertyTweener that doesn't provide the convenient methods like as_relative() or from().

Another thing is that it's verbose. If you want to interpolate curve, you don't care about it's length. In the above example the length can be moved inside method and the method could run from 0 to 1 instead, but you'd still need to provide the 0-1 range to the method, which is a chore.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add a new method to PropertyTweener. It could be called set_custom_method() and would take a Callable. Then instead of doing easing, the tweener would call the method and use its output for the value. For simplicity, the method would be normalized to take 0-1 range as argument and return 0-1. Then the code above can be changed to:

tween.tween_property(self, "position:y", 300.0, 1.0).set_custom_method(interpolate_curve.bind(curve))

The interpolate_curve would be:

func interpolate_curve(v, curve):
	return curve.sample_baked(remap(v, 0, curve.get_baked_length()))

What's better, you can wrap this method to make it more convenient to use:

TweenUtils.tween_curve(tween, self, "position:y", 300, 1.0, curve)

Which makes it easy to distribute as an addon.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Add a Callable custom_method to PropertyTweener, which would be called inside step() instead of running equation. The equation could be run on the method's parameter (otherwise set_trans() and set_custom_method() would cancel out each other).

If this enhancement will not be used often, can it be worked around with a few lines of script?

It can be worked around, but the workaround is less convenient. This is about convenience.
Also tweening with custom curves is a frequently requested feature. The feature suggested here would make it easy to implement curves as an addon.

Is there a reason why this should be core and not an add-on in the asset library?

This makes it easier to create addons.

@WagnerGFX
Copy link

While the PR isn't merged, I've made a small script that makes MethodTweener simulate PropertyTweener with a custom interpolation.
It's still very limited by a lot of factors, but it should be flexible enough for the most basic use cases while reducing verbosity.

tween_utils.gd

# Add this script to AutoLoad as TweenUtils
# Can only set full properties:
#  [OK]    "position"
#  [ERROR] "position:x"

extends Node

# Tween a property using a custom interpolation method
func interpolate_property(tween : Tween, interpolator_method : Callable, object : Object, property : StringName, final_val : Variant, duration : float) -> MethodTweener:
	var inner_method := inner_interpolator.bind(interpolator_method, object, property, object.get(property), final_val)
	return tween.tween_method(inner_method, 0.0, 1.0, duration)

# Shortcut to directly use a curve instead of a method
func interpolate_curve_property(tween : Tween, interpolation_curve : Curve, object : Object, property : StringName, final_val : Variant, duration : float) -> MethodTweener:
	var interpolator_method := func(value, curve) -> float: return curve.sample_baked(value)
	interpolator_method = interpolator_method.bind(interpolation_curve)
	return interpolate_property(tween, interpolator_method, object, property, final_val, duration)

# Inner method, used by Tween.tween_method()
func inner_interpolator(tween_value : float, interpolator_method : Callable, object : Object, property: StringName, from : Variant, to : Variant):
	var interpolated_value = interpolator_method.call(tween_value)
	var interpolated_property
	
	if(interpolated_value is float):
		interpolated_property = (to - from) * interpolated_value + from
	else:
		interpolated_property = (to - from) * tween_value + from
		push_error(str(interpolator_method.get_object()) + "." + interpolator_method.get_method() + "() method does not return a float value")
	
	object.set(property, interpolated_property)

# [Example]
# TweenUtils.interpolate_property(tween, my_interpolator, self, "position", target.position, duration)
#
# func my_interpolator(tween_value : float) -> float:
#	return my_curve.sample_baked(tween_value)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants