How can I stop Sprite2D objects from redrawing in Godot?

28 views Asked by At

I'm making a 2D turn-based game. Various Node2D objects move when a player action happens.

But when they move, they make physics-based checks. Since I want that move that the previous node made to affect the moves that are made by the nodes that go after, I have something like this:

 for individual in individuals:
  # We need to wait for the move to take effect so that the next
  # collision calculations take it into account.
  await get_tree().physics_frame
  individual.do_thing_using_area2d()

This makes sure that physics updates happen so that the next physics check is valid. (e.g. if an individual just moved in front of the one that's about to move, a bump will happen.)

The problem with this is that Sprite2Ds are attached to the nodes being moved. So they get redrawn in separate frames and cause flickering.

To make all the sprites to update in one frame, I did this:

    # Stop _process and draw calls, but keep physics going.
    get_tree().paused = true
    PhysicsServer2D.set_active(true)

Then after all of the individuals have moved and updated the physics objects, I unpause:

    await get_tree().create_timer(0.1).timeout
    # Unpause to get _process and _draw going again.
    get_tree().paused = false

This works, but it kind of feels like Too Much and like there may be Consequences. Is there a cleaner and more conventional way to batch up Sprite2D updates?

1

There are 1 answers

0
Theraot On

The closest I can think to disabling redraw for a Node is disabling automatic redraw for a Viewport.

So I'm not answering the question as stated...

Instead, this is what I'm thinking: you can set top_level to true on the Node2D, so it does not move with its parent (while still being in its position in the scene tree) and then you have to move it...

For example, you could give it a script something like this:

func _physics_process(_delta: float) -> void:
    var parent := get_parent() as Node2D
    if is_instance_valid(parent):
        global_transform = parent.global_transform

Or save a few cycles like this:

extends Node2D


var parent_node_2d:Node2D


func _ready() -> void:
    top_level = true
    parent_node_2d = get_parent() as Node2D
    set_physics_process(is_instance_valid(parent_node_2d))


func _exit_tree() -> void:
    parent_node_2d = null
    request_ready()


func _physics_process(_delta: float) -> void:
    global_transform = parent_node_2d.global_transform

Here I'm using set_physics_process to enable or disable _physics_process depending on the parent. Let us step it up with a configuration warning:

@tool
extends Node2D


var parent_node_2d:Node2D


func _ready() -> void:
    top_level = true
    parent_node_2d = get_parent() as Node2D
    set_physics_process(is_instance_valid(parent_node_2d))


func _exit_tree() -> void:
    parent_node_2d = null
    request_ready()


func _physics_process(_delta: float) -> void:
    global_transform = parent_node_2d.global_transform


func _get_configuration_warnings() -> PackedStringArray:
    if not is_instance_valid(parent_node_2d):
        return ["Must be a child of a Node2D"]

    return []

Or you could use _process which is how you can start writing your own physics interpolation.

@tool
extends Node2D


var parent_node_2d:Node2D


func _ready() -> void:
    top_level = true
    parent_node_2d = get_parent() as Node2D
    set_process(is_instance_valid(parent_node_2d))


func _exit_tree() -> void:
    parent_node_2d = null
    request_ready()


func _process(delta: float) -> void:
    var final_transform := parent_node_2d.global_transform
    var factor := get_approach_factor(1.0 / Engine.physics_ticks_per_second, delta)
    global_transform = global_transform.interpolate_with(final_transform, factor)


func _get_configuration_warnings() -> PackedStringArray:
    if not is_instance_valid(parent_node_2d):
        return ["Must be a child of a Node2D"]

    return []


func get_approach_factor(total_time:float, delta_time:float) -> float:
     # Constants
    const EPSILON := 0.00001
    const SCALE := 1000.0

    # When the total_time is not finite, the approach factor is zero
    if not is_finite(total_time):
        return 0.0

    # When the total_time is approximately zero, the approach factor is one
    if total_time < EPSILON / SCALE:
        return 1.0

    # Compute the approach factor
    var base := pow(EPSILON, 1.0 / (total_time * SCALE))
    var approach_factor := 1.0 - pow(base, delta_time * SCALE)
    return approach_factor

Addendum: The above approach for interpolation is a good frame rate independent damping. But it is not the best option for physics interpolation. You might be able to do better if you know the speed at which the Node should be moving.