Scirra / Construct-bugs

Public bug report submissions for Construct 3 and Construct Animate. Please read the guidelines then click the 'Issues' tab to get started.
https://www.construct.net
107 stars 83 forks source link

Moveto behavior. Abrupt speed changes without regard to acceleration and decceleration. #7489

Closed ruskul closed 1 year ago

ruskul commented 1 year ago

Problem description

MoveTo behavior does not respect decelerate/accelerate values in certain conditions.

Attach a .c3p

https://drive.google.com/file/d/1LYAXukiV2ove8IFnbV_3oDnWO9NoQu9K/view?usp=drive_link

Steps to reproduce

Run the project. Left click to add way points to the "moveto" behavior on the sprite. Begin by allowing the sprite to fully stop before adding another way point. Note the results. In this scenario, everything is fine, and will be considered the control. Now for the issue: Alternate clicking on the far left and far right side of the screen rapidly, such that the sprite will be forced to do a series of abrupt 180 degree course changes. The final click should be near the previous click.

Observed result

In the first scenario, the sprite accelerates along the path, and then begins to decelerate an appropriate distance from the endpoint in order to come to a rest. Acceleration, deceleration, and max speed have all been respected.

In the issue scenario, The Sprite respects acceleration along the path. Slowly gaining speed. however, when the sprite reaches a way-point, it abruptly changes direction with no regard to acceleration and deceleration. Rather it magically rotates its heading and instantly changes direction. Finally, as the sprite nears the end of its path, if the distance between the second to last waypoint and the final waypoint is too short to decelerate to in time, upon beginning to travel to the final waypoint, the sprite instantly drops to a speed such that it can decelerate in time. If you add two waypoints to nearly the same spot, the result will be that the sprite basically instanly stops, rather than decelerating.

Expected result

2 things:

The moveto behavior should calculate the total distance to to the final waypoint in order to determine when to decelerate. As it stands, it only tries to decelerate while traveling to the final waypoint only. In any situation where the sprite needs more distance to properly decelerate that exists between the second to last waypoint and the final point, the object abruptly changes speed. This is undesirable as it totally invalidates the concept of deceleration.

In regards to the object changing directions instantly, with no regard to either acceleration or deceleration, this is also undesirable as it invalidates any concept of inertia or predictability. Many times, objects are given acceleration and deceleration values in order to be more predictable, natural, etc... Being able to instantly change directions is probably wanted in some cases. If you want the object to apply acceleration and deceleration vectors in order to arrive at the target naturally, then the sprite needs to consider where it is going and also where it goes after that.

More details

This isn't an issue in any particular browser and has been an issue with the moveto behavior as long as I can recall. It is an effect of the implementation of the behavior itself.

The simplest way to create an object that accelerates and decelerates optimally to change direction, without having max speed clamping issues, is to lerp the velocity vector (xspeed, yspeed) to a velocity target that will point the sprite in the right direction to hit the waypoint. The lerp is applied over time such that acceleration and deceleration are respected.

An important point is that an object reversing directions should optionally be applying both deceleration and acceleration until all negative velocity has been removed in the direction of the target. Once the object is moving towards the target, only acceleration is applied. Essentially, you can simply view the sprites velocity as a circle on a grid, with the center being 0,0 and the circle being the max speed. You then take the current velocity as a point on that grid and simply move it about, either directly towards the target speed or using rotation if needing to model steering behavior. Imo Cartesian coordinates are easier to adjust and modify than trying to use polar coordinates with this method.

Another way to model this is to apply deceleration to any component of the velocity that isn't heading towards the target while accelerating towards it, then clamp the result. Honestly though, without clamping it, I find this method to work better when modeling objects with physics and applying forces to mimic lateral friction or fluid resistance as these forces all conspire to create a "max-Speed" without arbitrarily constraining it - which can be useful if you want other objects to act on it.

System details

System details aren't relevant.

View details PASTE HERE
AshleyScirra commented 1 year ago

The project link says "access denied". Please provide a public link.

ruskul commented 1 year ago

Sorry, should be fixed now:

https://drive.google.com/file/d/1LYAXukiV2ove8IFnbV_3oDnWO9NoQu9K/view?usp=drive_link

On Wed, Oct 25, 2023 at 12:55 AM Ashley (Scirra) @.***> wrote:

The project link says "access denied". Please provide a public link.

— Reply to this email directly, view it on GitHub https://github.com/Scirra/Construct-bugs/issues/7489#issuecomment-1779007868, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACUVSMVWWMXTWUHISRHFFDDYBDV2TAVCNFSM6AAAAAA6OOXUISVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTONZZGAYDOOBWHA . You are receiving this because you authored the thread.Message ID: @.***>

AshleyScirra commented 1 year ago

You've set the move to 'Rotate speed' to 0 to disable turning. So it's working as expected when it changes between waypoints. Set the rotate speed to 360 and you can see it then keeps up its speed but turns between waypoints; setting the rotate speed to 0 means it then can do instant turns, so it keeps up its speed but instantly turns between waypoints.

It may suddenly decelerate on the last waypoint because calculating where to decelerate is actually a difficult mathematical problem. Firstly with a very long deceleration time there could be several waypoints in the braking distance. Secondly with a rotate speed set, it might not be able to actually reach the next waypoint if it's inside its turn circle; the solution is to brake further in advance, but you have to calculate how far; even then the following waypoint after that might have a yet tighter circle, or be the final waypoint, so you have to brake even further in advance and calculate that distance; further still, the turn circle while braking is actually spiral rather than circular, which further complicates the maths.

I have studied this problem in detail for my Command & Construct game project and blogged about it here: https://www.construct.net/en/blogs/ashleys-blog-2/rts-devlog-10-following-paths-1611 That details a number of the edge cases, and to avoid the possibility of getting stuck it resorts to a "stop and spin on the spot" movement. That's suitable for that game, but might not be suitable for all other games depending on the movement, and IIRC it also still resorts to sudden changes in speed in some cases because there are still difficult edge cases it doesn't cover.

Perhaps the Move To behavior could be improved, but currently it's little more than a variant of the Tween behavior that works by speed instead of time. It does naive path following and only looks at the next waypoint, and in edge cases it just sets its speed so it doesn't ever get stuck or overshoot. That's currently how it works by design. If you think it should be better then file a suggestion, but it would be helpful if you can figure out the (presumably very complicated) algorithm for proper path planning through arbitrary waypoints with a given acceleration, deceleration, max speed and rotate speed, as despite spending a lot of time on this already I've never figured it out! Or you can custom code the movement with event sheets, JavaScript, or even write your own behavior to do it how you want. As I often say and as I wrote on the blog...

However like many things in programming, it turns out to be harder than it looks