Just to ensure that’s not a precision problem. How far away are you from the world origin when the issue appears?
Interesting.
Alright. Let us know if you figure something out.
Ok, I found what is causing the problem.
My game places the 3D scene inside a 2D scene with SubViewports, which handle the minimap and the GUI and pixelate the visuals.
After a little debugging, the problem comes from the script in this 2D scene, specifically the function minimap_handler().
If anyone can help me find the problem within this function, that would be great!
extends Control
@onready var game_node: Node3D = $Game/SubViewport/Node3D
@onready var minimap_camera: Camera3D = $Minimap/SubViewport/Camera3D
@onready var minimap: SubViewportContainer = $Minimap
@onready var money_fill: Label = $GameGui/VBoxContainer/MoneyFill
@onready var money_outline: Label = $GameGui/VBoxContainer/MoneyFill/MoneyOutline
@onready var quota_fill: Label = $GameGui/VBoxContainer/QuotaFill
@onready var quota_outline: Label = $GameGui/VBoxContainer/QuotaFill/QuotaOutline
@onready var time_fill: Label = $GameGui/TimeFill
@onready var time_outline: Label = $GameGui/TimeFill/TimeOutline
func _process(_delta: float) -> void:
minimap_handler()
gui_text_handler()
func gui_text_handler():
# Update the money label
if Global.money:
money_fill.text = "Money: $" + str("%0.2f" % Global.money)
money_outline.text = "Money: $" + str("%0.2f" % Global.money)
# Update quota label
if Global.quota and Global.round_quota:
quota_fill.text = "Quota: $" + str(int(snappedf(Global.round_quota, 1.0))) + "/" + str(int(snappedf(Global.quota, 1.0)))
quota_outline.text = "Quota: $" + str(int(snappedf(Global.round_quota, 1.0))) + "/" + str(int(snappedf(Global.quota, 1.0)))
elif Global.quota:
quota_fill.text = "Quota: $0/" + str(int(snappedf(Global.quota, 1.0)))
quota_outline.text = "Quota: $0/" + str(int(snappedf(Global.quota, 1.0)))
# Changes the time label
if Global.time:
var minutes: int = int(Global.time / 60.0)
var seconds: float = fmod(Global.time, 60.0)
time_fill.text = "%02d:%02d" % [minutes, seconds]
time_outline.text = "%02d:%02d" % [minutes, seconds]
func minimap_handler():
# Change the minimap position
if Global.car:
minimap_camera.position.x = Global.car.position.x
minimap_camera.position.z = Global.car.position.z
# Rotate map based on car
minimap_camera.rotation.y = Global.car.rotation.y
# Rotates civilian icons and destination icons
for child in game_node.get_children():
# Civilian waypoint
if child.name == "Road":
for civilian in child.get_children():
if civilian.name.contains("Civilian"):
# Get references
var waypoint = null
for civilian_child in civilian.get_children():
if civilian_child.name == "Waypoint":
waypoint = civilian_child
var minimap_icon = waypoint.get_child(4)
var minimap_move_icon = waypoint.get_child(7)
var civilian_minimap_visibility_notifier = waypoint.get_child(5)
var icon_visibility_notifier = waypoint.get_child(6)
# Rotate the icon
minimap_icon.rotation.y = minimap_camera.rotation.y - civilian.rotation.y
# If the civilian is on the minimap screen
civilian_minimap_visibility_notifier.connect("screen_entered", Callable(func():
if minimap_icon.get_meta("icon_on_screen") == true: return
minimap_icon.set_meta("icon_on_screen", true)
minimap_icon.visible = true
minimap_move_icon.visible = false
minimap_icon.position = Vector3(0, 30, 0)
))
# If the civilian is off the minimap screen
civilian_minimap_visibility_notifier.connect("screen_exited", Callable(func():
if minimap_icon.get_meta("icon_on_screen") == false: return
minimap_icon.set_meta("icon_on_screen", false)
minimap_move_icon.visible = true
minimap_icon.visible = false
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
# See if the icon has moved off the screen when adusting when not on the civilian
icon_visibility_notifier.connect("screen_entered", Callable(func():
if minimap_icon.get_meta("should_move") == false: return
minimap_icon.set_meta("should_move", false)
))
icon_visibility_notifier.connect("screen_exited", Callable(func():
if minimap_icon.get_meta("should_move") == true: return
minimap_icon.set_meta("should_move", true)
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
# When civilian is ready to be picked up, make sure that we can see the markers
Global.new_destination.connect(Callable(func(type):
if minimap_move_icon.visible == true or type != "civilian": return
minimap_icon.set_meta("should_move", true)
minimap_icon.set_meta("icon_on_screen", false)
minimap_move_icon.visible = true
minimap_icon.visible = false
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
# Destination waypoint
if child.name == "Waypoint":
# References
var minimap_icon = child.get_child(4)
var minimap_move_icon = child.get_child(7)
var destination_minimap_visibility_notifier = child.get_child(5)
var icon_visibility_notifier = child.get_child(6)
# Set texutre and rotate icon
minimap_icon.texture = preload("res://assets/Textures/dropoff.png")
minimap_move_icon.get_child(0).texture = preload("res://assets/Textures/dropoff.png")
minimap_icon.rotation.y = minimap_camera.rotation.y
# If the destination is on the minimap screen
destination_minimap_visibility_notifier.connect("screen_entered", Callable(func():
if minimap_icon.get_meta("icon_on_screen") == true: return
minimap_icon.set_meta("icon_on_screen", true)
minimap_move_icon.visible = false
minimap_icon.visible = true
minimap_icon.position = Vector3(0, 30, 0)
))
# If the destination is off the minimap screen
destination_minimap_visibility_notifier.connect("screen_exited", Callable(func():
if minimap_icon.get_meta("icon_on_screen") == false: return
minimap_icon.set_meta("icon_on_screen", false)
minimap_move_icon.visible = true
minimap_icon.visible = false
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
# See if the minimap has moved off the screen when adusting when not on the destination
icon_visibility_notifier.connect("screen_entered", Callable(func():
if minimap_icon.get_meta("should_move") == false: return
minimap_icon.set_meta("should_move", false)
))
icon_visibility_notifier.connect("screen_exited", Callable(func():
if minimap_icon.get_meta("should_move") == true: return
minimap_icon.set_meta("should_move", true)
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
# When destination is set, make sure that we can see the markers
Global.new_destination.connect(Callable(func(type):
if minimap_move_icon.visible == true or type != "destination": return
minimap_icon.set_meta("should_move", true)
minimap_icon.set_meta("icon_on_screen", false)
minimap_move_icon.visible = true
minimap_icon.visible = false
print("here destination")
move_minimap_icon(minimap_icon, minimap_move_icon, icon_visibility_notifier)
))
func move_minimap_icon(icon, minimap_move_icon, icon_visibility_notifier):
if icon.get_meta("is_moving") == true: return
if icon.get_meta("icon_on_screen") == false:
icon.set_meta("is_moving", true)
# Reset the position and move linear towards the center of the minimap camera
icon_visibility_notifier.position = Vector3(0, 30, 0)
var tween = create_tween()
tween.tween_property(icon_visibility_notifier, "global_position", Vector3(minimap_camera.global_position.x, 30, minimap_camera.global_position.z), 0.05)
tween.play()
# Wait until the visibility checker comes back on screen
while true:
await get_tree().create_timer(0.00001).timeout
# Checks if the destination is on screen
if icon.get_meta("should_move") == false or icon.get_meta("icon_on_screen") == true:
icon.set_meta("is_moving", false)
tween.stop()
# Move the icon
tween = create_tween()
tween.tween_property(minimap_move_icon, "global_position", icon_visibility_notifier.global_position, 0.5)
minimap_move_icon.get_child(0).rotation.y = icon.rotation.y - minimap_move_icon.rotation.y
if not minimap_move_icon.position.is_equal_approx(icon.position):
minimap_move_icon.look_at(icon.global_position)
tween.play()
# Recall to check if we need to move agian
move_minimap_icon(icon, minimap_move_icon, icon_visibility_notifier)
break
As for @conz3d idea, after a little bit of testing, that seemed not to affect the issue.
How did you figure out that minimap_handler() is to blame for the issue? Did you just test it by one-by-one removing a function to see if it fixed the issue? I’m asking because there is not a single line in that function that does any operation on the car, so it shouldn’t affect the car at all.
Could you post the node tree structure for your scene? It’s possible that an ancestral node, whose position is repeatedly modified, will affect the car’s physics.
As referenced by @athousandships:
I tried to switch the car to a CharacterBody3D, but that did not solve the issue.
It is something in the function that is stated above
Alright, I isolated the problem to this while loop.
while true:
await get_tree().create_timer(0.00001).timeout
# Checks if the destination is on screen
if icon.get_meta("should_move") == false or icon.get_meta("icon_on_screen") == true:
icon.set_meta("is_moving", false)
tween.stop()
# Move the icon
tween = create_tween()
tween.tween_property(minimap_move_icon, "global_position", icon_visibility_notifier.global_position, 0.5)
minimap_move_icon.get_child(0).rotation.y = icon.rotation.y - minimap_move_icon.rotation.y
if not minimap_move_icon.position.is_equal_approx(icon.position):
minimap_move_icon.look_at(icon.global_position)
tween.play()
# Recall to check if we need to move agian
move_minimap_icon(icon, minimap_move_icon, icon_visibility_notifier)
break
If anyone sees anything wrong with this, let me know!
await get_tree().create_timer(0.00001).timeout
I’d wager its the 100 microsecond timer that’s called every physics frame that’s causing issues.
I’m not seeing anything that’s weird about that snippet of code. Within that snippet, have you tried to find the specific line that causes the issue?
Similar to @puffcrushin, I would also say that the 0.00001-timer is a little weird. I’m not sure if it’s causing your problem, but it’s definitely weird. An alternative would be to wait for one process-frame:
await get_tree().process_frame
The outcome is probably the same, but it’s worth changing regardless.
If you narrow it down further, let us know.
IMHO the code contains 3 elements that cause problems:
- the
minimap_handler()(which is called from the_process()function which in turn is called from the engine several times per seccond) connects callables to signals (likecivilian_minimap_visibility_notifier.connect("screen_entered", Callable(...))which means that after some time there are hundreds of callables connected to those signals.
This can cause performance problems later on. - The
move_minimap_icon()function that contains awhile trueloop that (with the timer set at 10 microseconds could run up to 100000 times per second, even if the states it checks update at far lower rates - The
move_minimap_icon()function calls itself recursively which can lead to a stack overflow.
@tomyy 's first point ended up being the solution.
I changed all of the connect functions to use CONNECT_ONE_SHOT, looking something like this.
civilian_minimap_visibility_notifier.connect(“screen_entered”, func():
if minimap_icon.get_meta("icon_on_screen") == true: return
minimap_icon.set_meta("icon_on_screen", true)
minimap_icon.visible = true
minimap_move_icon.visible = false
minimap_icon.position = Vector3(0, 30, 0)
,CONNECT_ONE_SHOT
)
If I encounter any more problems, I will let you guys know.
Thank you, everyone, for your insights!

