Welcome to the forums @Odion!
I'll admit that I'm not totally sure I am understanding the issue correctly, but I think I can help a bit.
delta
is the time it takes to render a single frame, in real-world time. In Godot, delta
is in seconds, where 1
is a second and 0.01
is one hundredth of a second. If you multiply a value by delta, you are, essentially, making it bound to real-world seconds. For example, if you have the following code:
var tmp = 0
const VALUE = 20
func _process(delta):
tmp += VALUE * delta
Then tmp
will increase by 20
every second. This is regardless of the frame-rate when normalized over a period of time (more about that later). So, if you are trying to make a game frame-rate independent, you multiply by delta
because then it becomes linked to the actual time that passes between each frame, it goes from "X value per frame" to "X value per second".
Where the precision problem comes in is because delta
is the time (in seconds) it takes between frames (for_process
). If you are running at a constant 60 FPS, then delta
will be 1/60 or 0.0167 each time _process
is called. If you are running at a constant 30 FPS, then delta will be 1/30 or 0.033.
Side note: Keep in mind that FPS is not always constant, so the actual numbers would vary a bit. However, over a long enough period of time and then averaged, it should equal a number very close to 1.
When multiplied by a value, it may seem like 30 FPS is going to add more to the value, and that is technically correct. On each individual _process
call, more "stuff" will be added then a 60 FPS _process
call. But if you take all 30 _process
calls on a 30 FPS and compare it to all 60 _process
calls on a 60 FPS, you should find that the values are exactly the same, as a single second of real-world time has passed.
0.0167 * 60 = 1.0 # roughly
0.033 * 30 = 1.0 # roughly
# in reality, its a bit more like this:
# (13 - 30 FPS frames + 7 - 28 FPS frames + 10 - 32 FPS frames)
(0.033 * 13) + (0.036 * 7) + (0.031 * 10) = 1.0 # roughly, over time
For time dependent actions, like player input, the difference in time can cause precision issues, which is what I think you are seeing and trying to work around. While over the course of a single second its all the same, if you have a sub-second action that the player needs to react to, the FPS can make a difference.
To fix this, you likely will need to add "coyote time" for jumps and similar actions. "coyote time" is where the character can jump even in mid air, as long as they left the ground just a few seconds (or rather, fractions of a second) ago. Something like this, for example:
var timer = 0
var can_jump = false
# they can jump 0.1 seconds after leaving the ground.
# this should allow for jumping with relatively good
# precision even at 10 FPS (1/10 = 0.1)
# You can adjust this value to be relative to your minimum
# target frame rate.
const MAX_TIME = 0.1
func _process(delta):
# We'll assume is_grounded is a boolean
# that is true when the player is on the ground
if (is_grounded == true):
timer = 0
can_jump = true
else:
# instead of just setting can_jump to false,
# we use a small timer, giving "coyote time"
if (timer <= MAX_TIME):
timer += delta
if (timer >= MAX_TIME):
can_jump = false
if (Input.is_action_just_pressed("jump")):
if (can_jump == true):
# Jump code here!
# Then, set can_jump to false and the timer to MAX_TIME
# to avoid double jumps
can_jump = false
timer = MAX_TIME
The timer compensates for the possible differences in delta
, allowing certain time sensitive inputs (like jumping) to happen even if the FPS is dramatically different. Its a bit of a cheat, because you are technically jumping on air, but it helps give the exact same game feel regardless of the FPS of the computer it is running on. Someone playing at 10 FPS should be able to make all the same jumps that someone playing at 60 FPS makes (at least in theory)
For things that are not as time sensitive you can probably just get away with taking the input as it happens and not using any sort of timer. This is why movement, for example, is often not using a "coyote time" timer system and instead is just processed as soon as it comes, because (for the most part) the difference in movement over 1/10 of a second compared to 1/60 of a second is minor enough that the player will not suffer consequences in-game if they get the timing wrong.
Hopefully what I wrote above helps explain a bit! I wrote it all in one pass, so there may be mistakes and the like. (Also, apologies for writing a book :lol: )