What is the best/proper way to separate physics/AI and rendering?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By AngryTomato
:warning: Old Version Published before Godot 3 was released.

I have a bunch of minions on the board that I need to move around and their AI is based on the state and position of the other minions on the board. I came up with 3 ways of doing it, but I’m not sure which is correct or what the pro and cons are of each.

Method 1

In each minion node’s script:

func _fixed_process(delta):
     # Get surrounding minions details and calculate new
     # velocity vector for this minion, but don't change the position yet.
     # This is important because I need to
     # calculate new velocities for all minions before anything moves.
func _process(delta):
     # Actually move the minions based on velocities
     # calculated in _fixed_process().
     self.set_pos(self.get_pos() + self.velocity * delta)

Is this correct to use both _fixed_process and _process? Also, can I be guaranteed that _fixed_process() will be called on all my minions before _process() is?

Or can I think of _fixed_process() and _process() like 2 separate threads? One for physics and one for rendering?

Method 2

In the minions’ parent node:

func _fixed_process(delta)
 # Basically do the same thing as method 1 except loop
 # through all minions on the board and do it to each minion.

func _process(delta)
 # Same as method 1 except again looping through
 # each minion to update its new position.

for minion in minions:
    minion.set_pos(minion.get_pos() + minion.velocity * delta)

So instead of relying on the Godot engine to loop through minions, I am doing it myself. Would this be too much processing in one function?
Not strictly related, but is there a guaranteed order in which _fixed_process() and _process() are called down the tree? Is it breadth first? Depth first?

Method 3

Create an Area2D circle on each minion.

Use collision triggers to set AI update flags:

    flagNeedsAIUpdate = true

    if length(get_overlapping_areas()) <= 0:
         flagNeedsAIUpdate = false

func _on_fixed_process(delta):
   if flagNeedsAIUpdate:
       # Do same thing as method 1

func _on_process(delta):
   # Do same thing as method 1

I guess this is just on optimization of method 1 (I think?)

I can get my minions to do what I want them to do, I’m just wondering what’s the best way to structure this.

Sorry about the formatting, it does not match the preview!!! And I don’t know how to escape those things…

AngryTomato | 2017-07-26 01:20

select code blocks and press this button.

volzhs | 2017-07-26 02:28

Thank you! Updated formatting.

AngryTomato | 2017-07-26 05:37

:bust_in_silhouette: Reply From: RandomShaper

I think there is not an universal best way. You can structure your code as it best matches your game logic/mechanics.

Things to consider:

  • _process() is called per rendered frame. So you may be interested in changing visual stuff here, if that’s what you mean with ‘rendering’. But if changes are not complex and you are fixed-frame-based, that would be overkill.
  • _fixed_process() is called uniformly at your target FPS (so it may be called multiple times per visual frame). This is the best place for almost anything if you are using Godot’s physics. Also note that collision callbacks, etc. belong to this same “fixed frame realm”.

You can have a look at my talk where I dissected the engine loop so you can have a clearer awareness of when things happen exactly: Node Nuggets, from minute 14.

That was a great talk, I watched the whole thing! I think the information about the game loop would be useful to add to the docs.

I ended up doing something like this:

func _fixed_process(delta):

and I had nothing in _process

AngryTomato | 2017-08-08 01:03

I’m happy you liked it! I was never sure about its potential.

RandomShaper | 2017-08-08 10:34