_physics_process(delta) speed * delta with 60fps and speed = 200 gives inconsistent motion effect 2D

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Sylvain22

Hello,

I was moving my 2D code motion to grid movement, in order to ensure a fixed range position. I took some Example I got from Godot 3.2: Godot-Grid-Movement

Based, the code changed brings me some inconsistent 2D motion effect.
The code use _physics_process(delta): and a default speed of 200.
at 60fps default the delta = 0,016666666666667 * 200 = 3,33333333333333

Here is the screen recording:
screencast inconsistent 2D motion effect

The code

func _physics_process(delta):
	if not initialized:
		return
	
	# MOVEMENT
	# increase, the player travels from grid step
	var vel =  speed * delta
	if not float_velocity:
		vel = ceil(vel)
	var p = position
	velocity = want_dir * vel
	p += velocity
	# if we've moved further than one space
	if p.distance_to(last_position) >= grid_size - vel:
		p = target_position # snap the player to the intended position
	if p != position:
		position = p
		#print("p: %10s vel %10s targer %10s" % [p, vel, target_position])
	
	if position == target_position:
		# IDLE on grid position
		get_input()
		# get new target (also shift to moving state)
		var new_target = target_position + want_dir * grid_size
		if new_target != position and is_inside_grid(new_target):
			target_position = new_target
			# record the player's current idle position
			last_position = position
			speed = our_max_speed

This seems to be due to the specific value computed here: 3,33333333333333
As the increment velocity. If I switch to integer velocity, I don’t get any motion inconsistency, nor for more float value not 3.3333. Speed 201 or 199 don’t generate any motion inconsistency in float.

I was first seeking about the overlapping element on the screen as the “slow” part of the motion seemed be over the silhouette, but no. This is definitely related to float computation.

Did you ever noticed that?
How did you fixed it?

Regards,
Sylvain.

:bust_in_silhouette: Reply From: Sylvain22

I found.

because of the grid_size of 10 and the sapping to a grid offset:

if p.distance_to(last_position) >= grid_size - vel:
    p = target_position # snap the player to the intended position

So I suppose that a velocity of 3.3333335 (float32) as exactly at one third of 10 and the floating point accumulated errors, at some part of the addition (with some specific floating number or int may be) then the offset we get has some nasty effect of being snapped to the grid more often that other.

So the motion inconsistency at this specific value, which disappears when we change the velocity to not have a specific relation with he grid size.

I made a python script to simulate this:

import numpy as np

speed = 200
grid_size = 10
delta = np.float32(1.0 / 60)
vel = np.float32(speed * delta)
print(vel)

x = np.float32(0)
last_position = 0
target_position = 0
want_move = False
while x < 350:
    if x != target_position:
        x += vel
        if (x - last_position) >= grid_size - vel:                                                                                                   
            print("%6s %s -> %s" % ("SNAP", x, target_position))
            # snap the player to the intended position
            x = np.float32(target_position)
        else:
            print("%6s %s" % ("move", x))
    else:
        new_target = target_position + grid_size
        if new_target != x:
            target_position = new_target
            # record the player's current idle position
            last_position = x
            print("%s %s %s" % ("TARGET", last_position, target_position))

Which displays some interesting rounding effect on the float32 precision and the boundary.

Also a better snapping grid condition when we are near the grid point from both side:

if (x - last_position) >= grid_size or (target_position - x) < np.float32(0.02) 

This avoid the nasty visual effect of slow down when multiple loop of _physics_process are needed to reach a point or to skip a point of the grid.

Hope that helps.
Regards,
Sylvain.