+1 vote

Hey people.

I've been messing with the engine for quite some time, but I noticed I've been ignorant
to frame dependency till now. Most godot's demos have it, so I tried giving a look at it, but I still couldn't get it into my head. I even went through many articles, but it made me even more confused.

On my project, I move the player through a grid. Its a linear movement with a constant speed, so I do it like this :

#assign movement key
if Input.is_key_pressed(KEY_DOWN) && !moving:
    total_movement = Vector3(0,0,2) #total transition
    movement = Vector3(0,0,2) #current transition
    moving = true #start walking loop

#moves player
if moving:
    move(total_movement/32) #move by fraction
    movement -= total_movement/32 #consume fraction from current transition

    #if movement has been fully consumed, closes walking loop
    if movement = Vector3(0,0,0):
        moving = false

Although the code above is really rough, it is good enough to not leave any problems.
The player moves 0.0625 each frame, which is also subtracted from "movement", that will become a perfect 'Vector3(0,0,0)' eventually, sinalizing the end of the cycle.

But this is a bad practice as it seems, meaning players with lower FPS will see the unit
walking at lower speeds and players with higher FPS will get higher speeds.

From what I understood, there two ways of making games framerate independents so FPS don't determine physics actions:

  1. Adapting all physics interactions to multiply by delta as an
    damage control switch

  2. Fixing the Timestep

So following the first step, I went to integrate delta into all my loops, and as I got a good result, it didn't last long. As more extreme FPS variations appeared, they broke the values and skipped conditions. So I had to create more functions and implement more equations to adapt my code to these variations, until I bloated the code, gave up and reverted it back.

Then I went on about the second step, that is almost spammed at every related question I found, but I really didn't get it at all. It's about decoupling the render from the physics and syncing it up in code. Couldn't get the pseudo code neither other users versions. It seems that everybody read the same article and somehow got a different concept from it while explaining.

So I was wondering if someone with a good grasp on the subject could get an example in GD or some advice on what take I should use on the subject.
Any help is appreciated, thx!

References:
When should I use a fixed or variable time step?
motion calculations in games
Fix Your Timestep!

in Engine by (39 points)

In process(delta) or _fixedprocess(delta) call move like this:

move(total_movement*delta)

The delta is the fractional time elapsed between frames, it's like your 1/32 but it will be framerate independent.

This is sorta what I did when I bloated my code.

You see, move(total_movement/32) is frame dependent but it is precise, it will walk 0.0625 p/frame and consume the same value from movement, that is expected to go from Vector3(0,0,2) to Vector(0,0,0) in order to close the walk cycle.

Using the method you told however, it allows values other than 0.0625 p/frame, which leads to the total_movement being divided by odd numbers and movement also subtracted by those same odd numbers, thus never reaching the desired Vector3(0,0,0).

I could simply close the walk cycle using movement.distance_to(Vector(0,0,0)) <= .1 instead of movement == Vector3(0,0,0), however, at even more variable framerates, the delta control could skip this distance and bypass even this condition, and so on.

At this point is either making the game run some rocket sciency methods on the background or fixing the step... I think.

2 Answers

+1 vote
Best answer

I don't know how important is in your game to be at X position, but as you have learned it's not easy task. What I do is get the direction vector substracting target-current position. If length is greater than Epsilon then normalize and multiply by velocity and delta to get movement vector. If length is equal or lower than Epsilon you arrived. You need to adjust your Epsilon to avoid circling around a point, or reduce it to the minimum.

by (992 points)
selected by

I was trying to post it as a comment to undertand your problem before posting it as an answer, but I hope it is what you need.

Yeah, I got more confused because I thought there was a general formula that applied to every game... Now I see it's really specific. Here's what I did:

var speed = 3.75 #0.0625*60 equivalent

#assign movement key
if Input.is_key_pressed(KEY_DOWN) && !moving:
    vector = Vector3(0,0,1) #vector direction
    movement = 2 #total distance to be moved
    moving = true #starts cycle

#moves player
if moving:

    #assign frame independent step correction
    var step_p_frame = speed * delta #0.0625@60FPS, equivalent @XXFPS

    #if next step is whitin range
    if movement - step_p_frame > 0:
        move(vector * step_p_frame) #move step
        movement -= step_p_frame #consume step from movement
    else:
        move(vector * movement) #move what movement's left
        movement = 0 #fully consume movement

        #round possible floats and snap to the grid
        var pos = get_translation()
        var snap = Vector3(round(pos.x),round(pos.y),round(pos.z))
        set_translation(snap)

        moving = false #closes cycle

It's just like what you said. With this, doesn't matter if the FPS drops to 17 or goes up 431, it'll make the best it can without breaking the code.

Now to do something about situations where I have acceleration or a desired specific position at specific iterations...Ugh. At least I'm no longer under the false impression that there is a "one size fits all" solution for these problems. Thanks for the help!

+1 vote

You don't need to implement a fixed timestep game loop of your own, one already exists. The game loop is inherited by the SceneTree and once a node enters it, they can access it.

http://docs.godotengine.org/en/stable/classes/class_mainloop.html

There are two versions, one that can have variable delta and one that has a fixed delta.

You can see these functions in the Node here: http://docs.godotengine.org/en/stable/classes/class_node.html#class-node

You enable them and use them like this:

func _ready():

    set_process(true)
    set_fixed_process(true)

func _process(delta):
    pass # Variable time step loop

func _fixed_process(delta):
    var velocity = heading * accel * delta # Fixed time step loop

The delta provided is based on the frame rate given by the project, so if you have a 60FPS game for example, code inside of _fixed_process(delta) will execute 60 times a second, and give you a delta of 1/60 = 0.01666...

As far as understanding a fixed time step, the code aims to measure the elapsed time and execute code a fixed number of times per second.

When it is setup correctly, whatever you multiply by that delta is how much the value will be after 1 second of time.

So if you're moving say 60 pixels * delta. This is 60 pixels per second. Because in the loop it will execute it 60 times over 1 second, moving it 1 pixel each time. 60 pixels x (1/60fps) = 1 pixel.

The benefit of all of this, is that the loop will check the time in the computer clock and make sure it executes only that many frames. By using this loop and multiplying time sensitive things by delta, you also allow yourself the option to easily change the frame rate without having to update all your code later.

by (5,203 points)

I'm sorry, I was really confused about this when I asked the question.

I already know about _process() and _fixed_process. But I was under the impression that the "Fixed timestep" was some sort of witchcraft that would make the game run the code at a targeted times per sec independent of user FrameRate.

Basically queuing loop iterations at lower FPS and capping iterations at higher FPS, making physics values framerate idenpendent by default, lol. Thx for clarifying anyway.

No problem, if you're just trying to study it, here is what a basic concept in pseudo code would look like. You execute the loop as fast as possible, and throttle when you call update by measuring the accumulated time. When the time hits or exceeds the desired delta (16.6ms for 60fps), you fire off an update call, then subtract the delta from the counter.

I also tossed in a condition for some protection against lag. It can try to catch up when it is behind on frames, but only until a maximum, after that it stops trying, resets the counter, calls a special function (if you need to clean things up), and then carries on.

var previous_time
var accumulated_time

var delta_time = 1/fps / TIME_UNIT


function game_loop() { 

    var current_time  = get_cpu_time()
    accumulated_time += current_time - previous_time
    previous_time     = current_time


    var update_count = 0 // A time out counter.


    while (accumulated_time >= delta_time) {

        do_updates(delta_time)

        accumulated_time -= delta_time;

        if (++update_count >= MAX_UPDATES) {

            accumulated_time = 0.0

            lag_encountered()

            break;
        }
    }
}
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.