Godot 4.x Cheatsheet: _process vs _physics_process

This is part 2 in our Godot Cheatsheet series (part 1 here).

Understanding when to use _process(delta) versus _physics_process(delta) will help you structure your Godot 4.x game for clarity and performance. Use _process for per-frame updates that don’t need strict timing (UI, effects, simple logic)​ dragonflydb.io. Use _physics_process for anything that benefits from a fixed update rate (physics, collisions, controlled movement)​ dragonflydb.io.

Remember to keep your code in these functions efficient and to leverage Godot’s signals and built-in nodes to avoid unnecessary work each frame. By following these best practices – using delta for consistency, dividing responsibilities between idle and physics processing, and toggling updates when appropriate – you’ll ensure your game runs smoothly and behaves reliably under different performance conditions. Happy coding!

Frame vs physics update timing. The red markers (top) show fixed physics ticks (e.g., 60 Hz), and the blue markers (bottom) show rendering frames (variable frame rate). Godot’s _physics_process runs on the fixed ticks, while _process runs on every frame.

Overview

In Godot’s game loop, you have two main callbacks for per-frame code: _process(delta) and _physics_process(delta). Choosing the right one is crucial for smooth gameplay and correct physics. _process runs every rendered frame (idle time), which means its call frequency depends on the frame rate. _physics_process runs at a fixed interval (the physics tick, e.g. 60 times per second by default)​ dragonflydb.io, in sync with the physics engine. Use these functions appropriately to ensure frame-rate independence and stable physics behavior.

Best Practice: Put non-physics logic in _process and physics-related logic in _physics_process. This separation keeps your game logic deterministic and avoids issues where frame rate fluctuations could alter physics outcomes​ stackoverflow.com. Always use the delta parameter to scale time-based movements or actions, so your game runs consistently regardless of a fast or slow frame rate​ forum.godotengine.org.


Understanding _process(delta) (Idle Processing)

_process(delta) is called every frame, as often as the engine renders. Its frequency can vary with performance (more calls on high FPS, fewer on low FPS)​ dragonflydb.io. The delta parameter is the elapsed time (in seconds) since the last frame, which you use to make things frame-rate independent.

When to use _process: Use it for anything that doesn’t need fixed-step physics synchronization​ dragonflydb.io, such as:

  • UI updates and animations (e.g., fading a panel, rotating a coin sprite).
  • General game logic not tied to physics (updating score, checking win/lose conditions, state machines).
  • Playing animations or particle effects.
  • Reading player input for immediate non-physics responses (e.g., menu navigation, button clicks).

For example, you might rotate a sprite smoothly in _process each frame:

gdscript
func _process(delta):
# Rotate 90 degrees per second regardless of frame rate
sprite.rotation_degrees += 90 * delta

Notes: _process is not suitable for physics or precise collision handling because it’s tied to the variable frame rate. If the frame rate drops or spikes, any physics done here would become inconsistent​ stackoverflow.com. Always multiply by delta for movement or timers in _process to account for frame time variations (this ensures consistency if the game runs at 30 FPS or 144 FPS)​ forum.godotengine.org. If nothing needs updating (e.g., the character is idle), you can even disable processing on that node with set_process(false) to save CPU​ docs.godotengine.org.

Understanding _physics_process(delta) (Physics Processing)

_physics_process(delta) is called on every physics frame at a fixed frequency (project setting Physics FPS, default 60 Hz)​ dragonflydb.io. This means delta here is typically a constant (~0.0167s when 60 Hz) for a stable simulation. _physics_process is synchronized with the physics engine, so it’s the right place for any code dealing with physics bodies or requiring deterministic timing.

When to use _physics_process: Use it for physics-related updates and anything that benefits from a fixed tick​ dragonflydb.io, such as:

  • Moving physics bodies (e.g., CharacterBody2D/3D movement, RigidBody forces).
  • Handling collisions or raycasts (the physics state is up-to-date when this runs).
  • Applying forces, impulses, or gravity to objects ​dragonflydb.io.
  • Any gameplay logic that must be in sync with physics (e.g., timing a jump recharge or resetting a player’s double-jump only when they physically land on the ground).

For example, applying an upward force to a rocket while a key is pressed:

gdscript
func _physics_process(delta):
if Input.is_action_pressed("thrust"):
rocket.apply_central_force(Vector3.UP * thrust_power * delta)

Or moving a 2D character with collisions:

gdscript
func _physics_process(delta):
velocity.y += gravity * delta # apply gravity
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_strength
velocity.x = speed * Input.get_axis("left", "right")
move_and_slide() # uses CharacterBody2D.velocity internally

Notes: Keep physics calculations here for consistency – since _physics_process runs at fixed intervals, the outcome won’t change if the rendering slows down stackoverflow.com. You still use delta for physics calculations (e.g., accumulating gravity or timers) to account for custom physics FPS or changed time scale. Godot’s physics interpolation (introduced in 3.5) can smooth out rendering of physics bodies if your rendering FPS is much higher or lower than physics FPS​ forum.godotengine.org. Generally, do minimal work here to maintain performance – heavy computations each physics tick could slow down the game.


Best Practices & Optimizations

  • Separation of Concerns: Keep gameplay code in the appropriate function. Physics interactions (movement, collisions, forces) go into _physics_process, while cosmetic and general logic goes into _processdragonflydb.iodragonflydb.io. This makes the game loop more stable and easier to manage. For example, in a platformer, handle player movement in _physics_process but update the score UI in _process.
  • Frame-Rate Independence: Always multiply time-based values by delta. This applies to both functions (even if physics delta is constant)​forum.godotengine.org. For instance, if a character should move 100 pixels/second, use position.x += 100 * delta in _process. This ensures consistency at different frame rates. (Exception: some Godot methods already incorporate delta – e.g., CharacterBody2D.move_and_slide() uses the node’s velocity and applies delta internally, so you don’t multiply velocity by delta beforehand.)
  • Avoid Heavy Computation: Both _process and _physics_process run every tick, so avoid doing anything expensive inside them. Intensive tasks like pathfinding, complex AI logic, or loading resources should be handled sparingly (e.g., once in a while or in a separate thread). If you need to perform something expensive, consider using a Timer or coroutine (yield) to spread it out, or do it on a background thread, rather than blocking the game loop.
  • Use Signals & Events: Don’t poll for changes every frame if you can use signals or callbacks. For example, instead of checking in _process if the player’s HP went below 0, you could emit a signal when damage occurs. Instead of looping to see if an enemy touched a power-up, use an area’s body_entered signal. This event-driven approach reduces unnecessary checks and makes the code cleaner.
  • Toggle Processing When Inactive: You can disable a node’s processing when you don’t need it. For instance, if an NPC is off-screen or a minigame is not active, turn off its _process or _physics_process using set_process(false) or set_physics_process(false)docs.godotengine.org. This prevents the engine from calling an empty (or unnecessary) update every frame, saving CPU. Just remember to re-enable processing when needed (e.g., when the NPC comes back on screen or the minigame starts). Similarly, pause mode can be used to automatically pause processing on nodes when the game is paused (see a node’s pause_mode property).
  • Input Handling Considerations: For gameplay input, you might choose to read input in _physics_process to tie it with the physics step (common for character movement to avoid missed inputs). Godot’s input polling (e.g., Input.is_action_pressed()) works in both, but _input(event) signals are tied to frame updates. A quick tap could be missed by a physics frame if it happens between ticks​stackoverflow.com. One approach is to combine methods: use _input(event) or _process to catch one-time actions (like a shoot button press), but use _physics_process for continuous movement input to apply it reliably. Example: capture that “jump” was pressed in _input, set a flag, and then consume that flag in _physics_process to actually execute the jump. This hybrid approach ensures responsiveness without losing the benefits of fixed-step logic​reddit.com.
  • Physics Interpolation & Jitter: If you notice jittery motion for physics objects (e.g., a character stuttering at low FPS), consider enabling physics interpolation in the project settings. This feature interpolates physics bodies between ticks for rendering, smoothing out motion when your render FPS is higher than physics FPS​forum.godotengine.org. Alternatively, you can manually interpolate certain visuals (like camera movement) in _process based on physics data from _physics_processforum.godotengine.org. The goal is to get smooth rendering without compromising the fixed-step simulation.
🔎  Python Scripts: Simple Guide for Beginners

When to Use Each (Summary)

  • Use _process(delta) for non-physics frame updates – things that must update every rendered frame or don’t involve physics:
    • UI animations, menu logic, and screen effects.
    • Updating counters, scores, or other game state that isn’t tied to physics.
    • Controlling animations (e.g., playing an AnimationPlayer, or manual sprite frame updates).
    • Camera effects or smoothing that you want to happen every frame.
    • Gathering input for non-physics actions (e.g., pause menu toggles on key press, typing in a dialog).
    Example: In a puzzle game with falling pieces, you might use _process to gradually drop a piece by increasing its Y position with delta until it hits something, then switch to physics for the actual collision resolution if needed. For purely UI-driven games (like a visual novel or card game), _process might handle blinking an indicator or listening for input to flip a card.
  • Use _physics_process(delta) for physics and fixed timing – anything that involves the physics engine or needs deterministic timing each step:
    • Character movement and platformer mechanics (so collisions and floor detection are reliable).Moving kinematic bodies with move_and_slide() or move_and_collide() (2D or 3D characters) – Godot expects these in physics step for correct collision handling ​dragonflydb.io.RigidBody interactions: applying impulses, forces, or reading linear_velocity and adjusting it.Raycasting or shape queries (to detect what’s in front of the player, line of sight checks, etc.) – doing this in physics step ensures the data isn’t from a half-updated state.Timer-based mechanics that require stable intervals (for example, a gameplay timer that ticks down in fixed increments, or an enemy that shoots every 0.5 seconds consistently).
    Example: In a top-down shooter, enemy AI might use _physics_process to move towards the player and shoot bullets. The movement is done with a NavigationAgent (which moves in physics step), and the shooting uses a Timer that triggers a function (also effectively operating on the physics tick because Timer is aligned with the game’s tick). This way, even if the frame rate dips, the enemy doesn’t skip a beat in movement or shoot twice as fast—its logic is bound to the fixed step.
  • Using both together: Many games use both functions in tandem. For instance, in a platformer, you read player input in _physics_process and move the player, but you might update the animation sprite in _process (to make it perfectly smooth) based on the player’s state (running, jumping). In a FPS, you rotate the camera in _process for smooth viewing (as it’s just visual), but move the player and handle collisions in _physics_process. Remember: _process runs after physics by default each frame, so it can be used to interpolate or adjust things calculated in the physics step. Godot ensures _physics_process ticks happen in sync, and then renders the frame (calling _process etc.), so you can reliably use results from physics during the same frame.

Examples by Genre

Below are quick examples of how you might structure code for _process vs _physics_process in different game genres. These illustrate typical use cases and best practices for each scenario:

Platformer (2D) – Player Movement, AI, Physics Interactions

Player Movement: In a 2D platformer, use _physics_process for player movement to get reliable collisions and gravity. For example, a character using CharacterBody2D:

gdscript
extends CharacterBody2D
const SPEED = 200
const GRAVITY = 1200.0
const JUMP_VELOCITY = -400.0

func _physics_process(delta):
# Horizontal movement input (returns -1, 0, or 1)
var direction = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
velocity.x = direction * SPEED

# Apply gravity
if not is_on_floor():
velocity.y += GRAVITY * delta
else:
# Jump if on floor and jump button pressed
if Input.is_action_just_pressed("jump"):
velocity.y = JUMP_VELOCITY
else:
velocity.y = 0 # standing on floor

# Move and collide
velocity = move_and_slide() # CharacterBody2D: uses internal velocity

This code runs at a fixed 60 FPS (physics frames). By checking input and applying movement here, the character’s motion and collision detection remain consistent. The use of delta ensures gravity affects the character equally regardless of frame rate. The jump uses is_action_just_pressed in the physics step – this works because Godot queues input events, but if you ever had an issue with missed input, you could set a flag in _input and use it here.

AI (Enemy) Movement: Suppose you have a Goomba-like enemy that walks back and forth and flips when hitting a wall or edge. You’d also put this in _physics_process for the same reasons:

gdscript
extends CharacterBody2D
var patrol_speed = 100.0
var direction = 1 # 1 = right, -1 = left
const GRAVITY = 1200.0

func _physics_process(delta):
# Basic patrol movement
velocity.x = patrol_speed * direction
velocity.y += GRAVITY * delta
velocity = move_and_slide()

# If hit a wall, reverse direction
if get_slide_collision_count() > 0:
direction *= -1
# If walked off an edge (no floor below), also reverse
elif not is_on_floor():
direction *= -1

Here, collision information (like get_slide_collision_count()) is only reliable in _physics_process. The enemy flips direction either on colliding with a wall or losing the ground beneath, keeping its logic in the physics step. Non-physics aspects of the enemy (like switching an animation when turning) could be done in _process or via signals (e.g., if direction changes, play a flip animation).

🔎  Aprende Python para Ciencias: Tutorial Completo para Principiantes en Argentina

Physics Interactions: If your platformer has moving platforms or physics objects the player can push, those are usually RigidBody2D nodes (which have their own internal physics). You might not need to do per-frame logic for them at all – the engine handles their movement. But if the player can push a box, you’d apply an impulse to the box in _physics_process on collision (for example, when the player’s body enters a pushing state, apply force to the box). Area2D triggers (like a coin pickup or a fall zone) often use signals (area_entered) instead of _process loops. This makes things efficient: the coin doesn’t check each frame for the player; it simply emits a signal when the player enters its area.

Card Game – Turn-Based Updates, Animations, Input Handling

A card game (like a digital card battler or tabletop simulation) is largely turn-based and GUI-driven, so physics isn’t a concern unless you have fancy card flipping physics. You’ll rely on _process for animations and maybe input, and use signals or function calls for turn logic.

Turn Logic: Turn-based gameplay doesn’t require continuous _physics_process updates. Instead of running code every frame, you typically wait for player input, then execute a series of actions (draw card, play card, resolve effects). This can be done with direct function calls or coroutines (using await or signals). For example, you might not have any code in _physics_process for the main game loop. Instead:

gdscript
# Pseudocode for turn progression (could be in a Controller node)
func player_turn():
await InputEventSignal # wait for player to perform an action (like clicking "End Turn")
# ... handle the action
end_player_turn()

func end_player_turn():
start_enemy_turn()

func start_enemy_turn():
# ... AI logic for enemy (which might be just a random card play)
enemy_play_card()
end_enemy_turn()

Each of these steps is triggered by events, not by a frame loop polling a state. This is efficient and simpler to manage than constantly checking, say, if player_turn_active in _process.

Animations: Even in a card game, you might use _process for some smooth animations. For example, animating a card moving from the deck to a player’s hand:

gdscript
var moving = false
var move_speed = 1000.0
func deal_card(card: Node2D, target_position: Vector2):
moving = true
card_target = target_position
# (you could also use Tween or AnimationPlayer for this)

func _process(delta):
if moving:
# Move card towards target position
var direction = (card_target - card.position)
var distance = direction.length()
if distance < 5: # close enough
card.position = card_target
moving = false
else:
card.position += direction.normalized() * move_speed * delta

This uses _process because it’s purely visual interpolation of a card’s position. No physics needed. Alternatively, Godot’s Tween or AnimationPlayer would handle this outside of _process entirely, which is even better. The key is: only use _process here for the smooth motion because it needs per-frame updates; the game logic of whose turn it is doesn’t live in _process.

Input Handling: Card games rely on UI input (mouse clicks, drags). It’s best to use _input(event) or GUI input callbacks for these, as they fire exactly when something happens (e.g., a card Node might have a gui_input function or you connect a button’s signal). If you wanted to highlight a card under the cursor, you could use _process to raycast from the mouse position or simply use mouse enter/exit signals on the card nodes. Overall, _process might be used for minor visual updates (like an arrow following your cursor for targeting an attack), whereas the heavy lifting (deciding the outcome of a turn) happens in discrete events.

First-Person Shooter (FPS) – Weapon Firing, Enemy AI, Physics

A first-person shooter combines fast-paced input with physics (for movement, bullets, collisions). Here you’ll often use both _process and _physics_process strategically.

Player Movement (3D): Typically handled in _physics_process for collision accuracy. For instance, using a CharacterBody3D for the player:

gdscript
extends CharacterBody3D
var speed = 8.0
var jump_velocity = 5.0
const GRAVITY = ProjectSettings.get_setting("physics/3d/default_gravity") # default 9.8 m/s^2
func _physics_process(delta):
var direction = Vector3.ZERO
if Input.is_action_pressed("move_forward"):
direction -= transform.basis.z # forward (assuming -Z forward)
if Input.is_action_pressed("move_back"):
direction += transform.basis.z
if Input.is_action_pressed("move_left"):
direction -= transform.basis.x
if Input.is_action_pressed("move_right"):
direction += transform.basis.x
direction = direction.normalized()

# Set horizontal velocity (CharacterBody3D has a built-in velocity property)
velocity.x = direction.x * speed
velocity.z = direction.z * speed

# Apply gravity and jumping
if not is_on_floor():
velocity.y -= GRAVITY * delta
elif Input.is_action_just_pressed("jump"):
velocity.y = jump_velocity
else:
velocity.y = 0.0

# Move the player with collision
velocity = move_and_slide()

This code reads input and moves the player in _physics_process to ensure consistent movement speed and proper collision detection (using the physics engine for sliding along walls, etc.). The camera rotation (looking around with the mouse) can be done in _process or _input because it’s affecting the camera node (usually a child of the player) and isn’t a physics interaction – this separation means even if the game lags, the camera might still respond smoothly, but the player won’t go through walls because movement is locked to physics ticks.

Weapon Firing: Firing a weapon can be instantaneous (hitscan) or involve projectiles. For hitscan (like a rifle hit): you can handle the input in _process (to detect the click immediately) and perform a raycast. The actual physics query for a ray can be done anytime; doing it in _process is fine as long as you are okay with using the state from the last physics frame (usually negligible difference). Example:

gdscript
func _process(delta):
if Input.is_action_just_pressed("fire"):
var from = camera.global_transform.origin
var to = from + camera.global_transform.basis.z * -1 * 1000.0 # 1000 units forward
var space_state = get_world_3d().direct_space_state
var result = space_state.intersect_ray(from, to)
if result:
var target = result.collider
# apply damage or effects to target

This casts a ray in front of the camera to see what it hits. It’s fine in _process because it’s using the physics state (the direct_space_state) as of the last physics frame. If you wanted to be extra safe or if you’re doing something like a projectile that spawns, you might instead spawn the projectile and let physics handle it.

For projectile weapons (like a rocket launcher or throwing a grenade), you would likely spawn a RigidBody or Area node. Spawning the node can be done in _process when input is detected, but setting its initial trajectory is a physics action. One approach: spawn and add it to the scene in _process, but set an initial velocity or call an impulse in the next _physics_process. A simpler approach is to spawn it directly in _physics_process when the input is detected (the input flag can be set in _process as true, then acted upon in _physics_process). For example:

gdscript
shoot_requested = false

func _process(delta):
if Input.is_action_just_pressed("fire"):
shoot_requested = true

func _physics_process(delta):
if shoot_requested:
shoot_requested = false
var bullet = bullet_scene.instance()
bullet.transform.origin = muzzle.global_transform.origin
# Launch the bullet forward from the muzzle
bullet.linear_velocity = muzzle.global_transform.basis.z * -bullet_speed
get_tree().current_scene.add_child(bullet)

This ensures the bullet (if it’s a RigidBody) is spawned and given a velocity during the physics step. The physics engine will then handle its movement and collisions. If the bullet were an Area3D (detecting hits via area overlaps), you might instead use _process to move it (since Area isn’t physics-simulated by default). In that case, you’d manually move it with translate() or similar each frame in _process until it hits something.

🔎  Investigación Científica con Python: Guía Completa

Enemy AI: Enemies in an FPS often have to navigate the world and possibly use physics (for line of sight, projectiles, etc.). Movement and collision for enemies should use _physics_process (similar to the player). For pathfinding, you wouldn’t compute a new path every frame – instead, maybe when the enemy notices the player or when the player moves a significant distance. The actual following of the path can happen in _physics_process by moving the enemy a bit toward the next waypoint each tick. Example of a simple chase AI without pathfinding:

gdscript
extends CharacterBody3D
var player_ref: CharacterBody3D # assign the player node
var move_speed = 4.0

func _physics_process(delta):
if player_ref:
var to_player = player_ref.global_transform.origin - global_transform.origin
# Only move in X/Z plane (no flying)
to_player.y = 0
if to_player.length() > 1:
# Move towards player
velocity.x = to_player.normalized().x * move_speed
velocity.z = to_player.normalized().z * move_speed
else:
# Close enough to attack or stop
velocity.x = 0
velocity.z = 0
# (Attack logic could be triggered here, e.g., shoot at player)
# gravity
if not is_on_floor():
velocity.y -= GRAVITY * delta
else:
velocity.y = 0
velocity = move_and_slide()

This enemy moves toward the player using _physics_process. If you had a more complex AI, say patrolling until the player is seen, you could use _process to rotate an enemy turret to face the player smoothly (because rotation is just visual until it fires, you might not need it in physics). But when it fires a projectile, that spawn goes through physics. Another example: if an enemy throws a grenade at the player, you’d likely instance a grenade (RigidBody) in _physics_process and apply an impulse to it so it arcs through the air. The decision to throw might come from a timer or _process check (like “if player in sight for 2 seconds, then throw”), but the action is executed in the physics step.

Environment Physics: In an FPS, things like explosions or physical traps use the physics engine. An explosion force applying shockwave to nearby objects would be done in _physics_process: you’d iterate through objects in range (areas or PhysicsDirectSpaceState overlap queries) and call apply_impulse on their bodies. Doing this in the fixed step means all affected objects react in the same physics frame, making the effect more uniform. If you did it in _process at high frame rate, some objects might receive force slightly out-of-sync. Again, small differences, but important as projects scale.

RPG – Turn-Based Mechanics, Real-Time Updates, AI Pathfinding

RPGs can vary widely: some are strictly turn-based (e.g., JRPG battle systems), others are real-time (action RPGs), or a mix (real-time exploration with turn-based battles). Here you’ll often enable or disable processing depending on the mode of the game.

Turn-Based Combat (JRPG style): In a turn-based battle, you might not use _physics_process at all, since characters usually don’t move freely; they perform discrete actions when their turn comes. You’d use signals, timers, or a battle loop to handle turns. For example, you might have something like:

gdscript
func start_player_turn():
ui.show_indicator("Your Turn")
await player_input_signal
perform_player_action(chosen_action)
end_player_turn()

No continuous _process needed; the flow is managed by signals and awaits. During this, you might use _process to animate a timebar filling up or a flashing “Ready!” text, but that’s it. _physics_process might only be used if you have some physics-based skill effects (maybe a projectile spell that uses physics for collision), but otherwise, not needed in a strictly turn-based scenario.

Real-Time Exploration: When the player is exploring the world (outside of combat), if it’s a top-down or platformer-like movement, you use _physics_process exactly as discussed in Platformer/FPS to move the player and detect collisions (e.g., with walls or NPCs). For example, in a 2D top-down RPG:

gdscript
# Player character 2D
func _physics_process(delta):
var input_dir = Vector2(
Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left"),
Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
).normalized()
velocity = input_dir * move_speed
velocity = move_and_slide()

This gives you grid-free movement with collision in a Zelda-like game. While exploring, NPCs could be moving on predefined paths – you’d update those in _physics_process too (so they don’t walk through walls). If NPCs just stand until spoken to, you can even turn off their processing entirely until needed.

Pathfinding AI: In an RPG with pathfinding (like NPCs finding their way around obstacles or enemies chasing the player through a maze), you should avoid computing the path every frame. A common approach: compute a path when the destination or target changes, then follow that path step by step in _physics_process. For instance, using Godot’s NavigationServer or Navigation2D:

# Enemy AI 2D with pathfinding
onready var nav = get_node("/root/Navigation2D")
var path: PoolVector2Array = []
var path_index = 0
var target_node: Node2D = null

func _process(delta):
if target_node:
# Recompute path if needed (e.g., target moved significantly or path is finished)
if path.empty() or target_node.position.distance_to(path[-1]) > 50:
path = nav.get_simple_path(global_position, target_node.global_position)
path_index = 0

func _physics_process(delta):
if path.size() > 0:
var next_point = path[path_index]
# move towards next_point
var direction = (next_point - global_position).normalized()
velocity.x = direction.x * move_speed
velocity.y = direction.y * move_speed
velocity = move_and_slide()
# Check if reached next point
if global_position.distance_to(next_point) < 4:
path_index += 1
if path_index >= path.size():
path = [] # reached end of path
velocity = Vector2.ZERO # stop moving

In this example, _process recalculates the path occasionally (not every frame, maybe when needed), and _physics_process moves the character along the current path. This way, heavy pathfinding calculations (which might use A*) are not in the physics loop, preventing slowdowns in each tick. The movement remains in physics for accuracy. If the game is paused or in a cutscene, you might disable both _process and _physics_process on these characters.

Hybrid Turn-Based: Some RPGs (like a tactical RPG) have a phase of free movement then a phase of turn-based combat. You can dynamically enable or disable processing depending on the phase. For example, when combat starts, you might disable the player’s free-roam _physics_process on their movement script (so they can’t move with WASD) and instead enter a turn loop. When combat ends, re-enable _physics_process for movement. This can be done by toggling set_physics_process(true/false) on the player controller script.


by

Tags: