godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Provide a smooth move_toward #9803

Open ubitux opened 3 months ago

ubitux commented 3 months ago

Describe the project you are working on

In my case, a pretty standard 3D game at the first person, but it's by no mean specific to this project.

Describe the problem or limitation you are having in your project

There is no helper similar to move_toward to make a smooth transition in Node._process() or Node._process_physics(), so it's common to suggest something like a = lerp(a, B, delta * RATE). Unfortunately, this formula has all sort of limitations. In essence, it is:

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

A better alternative addressing all these shortcomings is a = lerp(a, B, 1-exp(-delta * RATE)). For more details on the topic:

Having such a helper builtin in Godot will:

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

A typical example of a smoothly decaying velocity:

const VELOCITY_DECAY_RATE: float = 3.5

def _process_physics(delta: float) -> void:
    # ...

    velocity = velocity.move_toward_smooth(target, delta, VELOCITY_DECAY_RATE)

    # ...

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

Yes, the function itself can be written but:

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

huwpascoe commented 3 months ago

I've been using a formula from this source

lerp(a, b, 1.0 - f ** dt)

Is this the same function?

ubitux commented 3 months ago

I've been using a formula from this source

lerp(a, b, 1.0 - f ** dt)

Is this the same function?

I think so, because m^n == e^(n log m). The exponential is less expensive though. You'll need a conversion for your f value too: rate = -log(f) I believe in your case.

Pizzaandy commented 3 months ago

My first instinct was to say "this doesn't belong in core." But it's an incredibly common pattern and even mentioned in the docs with position.lerp(mouse_pos, delta * FOLLOW_SPEED) used as an example!

I wouldn't mind if this was added, but at the end of the day it's a specific way of tweening (compared to the more powerful Tween.interpolate_value and trivial to implement via script. So... maybe?

Then again, we already have stuff like Vector2.cubic_interpolate_in_time so maybe this is more broadly applicable than some of the existing API.

AThousandShips commented 3 months ago

Tweens are a very different concept and they have their own use area, this method can't be replaced with a tween, and it isn't a trivial thing to realize to do, and is different from just the plain lerp approach, as it uses the exp to gain finer curves

Pizzaandy commented 3 months ago

Agreed on all counts. I often see lerp used like a lazy tween, but I realized that lerp is used for follower objects just as often. A few suggestions for this feature, inspired by this article:

GuyUnger commented 3 months ago

I really like "move_toward_smooth"

  • Rename the function to interpolate_damped because it's conceptually quite different to move_towards.

I don't think it matters much if they are similar conceptually. move_toward is already named like a helper function (and I'm sure the approachable name has helped more people use it) and in practice for games they are like sister functions, like setting your tween to smoothed or keep it linear.

ubitux commented 3 months ago

Note that move_toward_smooth() prototype has been changed in the PR to make it more consistent with move_toward(): the delta and rate parameters have been merged, so it's used like this: from.move_toward_smooth(to, delta*rate), just like move_toward.