Calling _process or _physics_process myself

Godot Version

v4.3.stable.official.77dcf97d8

Question

I want to spawn a projectile with time precision.

In player _process, i calculate that a projectile should have been spawned x seconds in the past (a time between last and current frame). I instantiate it, add it to the tree, and call myself projectile._physics_process(x) to catch up missed time.
I cannot predict easily where the projectile should be now without calling _physics_process, as the projectile path can vary depending on a lot of factors.

Is there any possible side effect of using this method (Engine automatic calls around _physics_process or other things that I’m unaware of) ?
If this is “bad practice”, what other technique could I use to achieve the same result ?

For completion’s sake, same information about _process would be appreciated to.

It may be just a personal preference, but I consider calling _process() or any other system callback function to be a bad practice. The only things I put into the callback functions are calls to functions that do the actual work. This way, I encourage myself to break my code down into reusable functions or classes.

1 Like

What about if you call a function like move_and_slide(), that “know” by itself the delta and doesn’t take it as param ?

How does that work? Why do you calculate when the projectile should’ve been spawned in the past instead of spawning it at that time? I can’t get my head around your example.

In general, I don’t think there would be any specific side effects if you called _process() or _physics_process() multiple times in your code, in the end these are the same functions as any other. It’s just uncommon to do so and I personally would refrain from doing that. I would get the part that I want to call multiple times out as a separate function and call that function instead of _process() directly.

You can’t get around that problem, there’s an open discussion about changing this behavior here:

2 Likes

Currently, my logic for shooting would look like this:

# in _process(delta)
weapon.cooldown -= delta
if Input.is_action_pressed("shoot"):
    while weapon.cooldown < 0.:
        var proj = proj_scene.instantiate()
        GAME.add_child(proj)
        proj._physics_process(weapon.cooldown * -1)
        weapon.cooldown += weapon.cooling_time

I hence ensure floating point precision in my timing (except the fact that reading player input is tied to _process frequency).

I don’t see how I could spawn the projectile at an exact time otherwise, as even using i.e. the signal system with a Timer would probably not be that precise, I suppose there’s some overhead in using a Timer (I don’t see how Godot would run some code at a specific time, considering a potentially slow hardware)

Do you see any other way ?

I still don’t understand why you need to call the physics process yourself in this example.

And what do you want to achieve with this while loop? All of this code inside the loop will be ran within the same frame, so you’ll spawn multiple projectiles in the same frame with the same action trigger.

What is your concern about the time precision? This code you shared will be triggered in the same frame that your “shoot” action has been pressed, how more precise do you need to be? Why do the projectiles need to catch up to something?

I’m super confused, sorry for all these questions :slight_smile:

No worries, I may not have been precise enough with the game I’m making.

The player will build his own weapon during the game, using “modules” to modify his weapon and projectiles (think Noita if you know this game). So the cooldown of the weapon might go below the physics frame time, at wich point I effectively need to spawn multiple projectiles during the same frame.

If the projectiles are spawned during the same frame and I dont use some kind of logic to make them “catch up”, all the “same frame projectiles” will be superposed, which I really don’t like and give a “discrete space” feel, where projectiles with specific speed can only be at specific points relative to the player, giving the illusion that they are static when continuously shooting.

Also, the shooting is continuous, you are shooting as long as your mouse click is pressed, so number of projectiles is not tied to amount of player’s input.
Pressing your button for 5 frames might result in 25 projectiles, with a fast enough weapon, and I want these 25 projectiles to be equally spaced.

I also connot predict easily where the projectile should be when I finally shoot it, while it should have been spawned 5ms ago, as player can apply path modifications to the projectiles, so it can be far more complex than calculating a point on a linear trajectory using only projectile speed. I really need to calculate what happened to this projectile during these 5ms, thus why I call its _physics_process().

Here, a simple example where I shoot 100 bullets/s.
Without catching up, multiple projectiles superpose and we never see more projectiles than 1/frame:


With catching up, projectiles can be placed at an exact position, because they actually travelled during their own catching time:

Is my case more clear ? Do not hesitate if you need more clarification or if you have other ideas on how to tackle this. Thanks for your time :slight_smile:

1 Like

This explanation made it all so much clearer now!
The Timer node will not work in this case, as it’s also updated every idle or physics frame (you can choose), so in order to go above the physics ticks frequency, you’d need to increase the ticks per physics frame in the settings.

I honestly don’t see any better way to achieve it than you have already mentioned.
3 things for your consideration though:

  1. I would move your projectile spawning code from process() to physics_process(), because currently you’re calling physics related code in the idle frame, which might lead to some physics miscalculations.
  2. I would increase the physics ticks per frame in the settings anyway if you expect that you’ll need these sub-tick shenanigans, so that you need as few adjustments as possible.
  3. If you’re using move_and_slide() in your code, then the inability to adjust its internal delta might lead to some inaccuracies as well. Maybe consider using move_and_collide() instead, which doesn’t use delta internally, so you have a full control.
2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.