Problem with rayCast2D's refresh rate

Godot Version

4.2.stable

Question

Hello, I recently migrated from Unity to Godot, so I was trying to get used to the platform by reproducing a project in 2D I already had in Unity. In it, I use a raycast for character movement, similar to the mechanics of the game Dandara: Trials of Fear. However, the raycast update speed is limited by the _physics_process since it is called only a certain number of times equivalent to the frame rate (from what I could research).
A large part of the mechanics and code will depend on the accuracy of the raycast, but I don’t know how to increase this update rate or find a similar solution (I tried to solve it with a force_raycast_update(), but nothing changed). The project and code are quite raw, but if anyone has any ideas or guidance on what to do, I would appreciate it.

extends Node2D
Components 
@onready var Player = $"."
@onready var Mira = $Raycast
@onready var Alvo = $Raycast/Alvo
@onready var Ray_A = $Raycast/OffSet_A 
@onready var Ray_B = $Raycast/OffSet_B 
@onready var Line = $Raycast/Line2D var 
target = null

func _ready():
target = self.global_position

func _physics_process(delta): 
update_raycast()
move()

func update_raycast():
var direction = (get_global_mouse_position() - Mira.global_position) * 100 Mira.target_position = direction Mira.force_raycast_update()
Alvo.global_position = Mira.get_collision_point()
#var offset = 15
#var collision_point = Mira.to_local(Mira.get_collision_point())
#var collision_normal = Mira.get_collision_normal().normalized()
#var parallel_left = collision_point + (collision_normal.rotated(-PI / 2) * offset)
#var parallel_right = collision_point + (collision_normal.rotated(PI / 2) * offset)
#Ray_A.target_position = parallel_left * 10 #Ray_B.target_position = parallel_right * 10

func move(delta):
if target != Player.global_position:
global_position = target # Here I planned to use Alvo.global_position instead of target

if Input.is_action_just_pressed("left_click") && target == Player.global_position:
target = Mira.get_collision_point()
var collision_normal = Mira.get_collision_normal()
var angle_radians = atan2(collision_normal.y, collision_normal.x)
Player.rotation = angle_radians + deg_to_rad(90)
Mira.global_rotation = deg_to_rad(0)
#Ray_B.global_rotation = deg_to_rad(0)
#Ray_A.global_rotation = deg_to_rad(0)`
![Capturar1|690x389](upload://vUaa89PRd7gyqjqRshPoRnMAtyd.png)
![Capturar|690x393](upload://eC3dLlRUlwdazmptuuOH99no6ZP.png)


Where you put the force_raycast_update() last time?

I tried a while loop that repeated 10 times, in each loop I executed “Mira.target_position = direction” and “Mira.force_raycast_update()”. It’s a workaround, I know, but one way or another it didn’t work.

force_raycast_update should work, if wasn’t only looking into the project to try to find what’s going on.

I don’t know if I got it right, ok force_update forces raycast to update but I don’t know how to call it at the speed and frequency I need or if it would really work, I’m probably using it wrong but that’s it. I think I’ll just try to get around the problem by not needing as much precision as I’d like, like a timer or something. And thanks in advance for your help.

You’ll call it every time you update the raycast position/target_direction in the same physics frame, example:

Your raycast is pointing to point A when your project starts, in the first physics frame you’ll already have the result of the raycast collision without need to call force update, but if in this same frame you also wants to check point B, you need to adjust your raycast position/target distance to the new point and call the force update, if you need also the result of point C in this frame, the same thing until you get all the needed points.

Do you mean that I should calculate the direction more than once per frame, while using force_update? Like in the while I mentioned before? In this case, I had already tried something similar and I did it like this:
func update_raycast(): # RayCast principal e linha var i = 10 while i>0: var direction = (get_global_mouse_position() - Mira.global_position) * 100 Mira.target_position = direction Mira.force_raycast_update() Alvo.global_position = Mira.get_collision_point() i -= 1
That didn’t work either. But no problem, I’ll try something else

Yeah, if that didn’t work that can means is something wrong with your collision configurations or you problem is not related to the physics queuing.

There’s no way I can send a compressed file of the project here, right? If you did, I’d like you to take a look, it’s something I’d like to understand.

You can upload somewhere like google drive and send the link here or in the pm. I can’t promise anything, but at least i’ll try to help.

Okay, so here’s the link if you want to take a look:
[https://drive.google.com/file/d/1m2lAvVC8lhyepOqACrm-DWQaT4mSNE2m/view?usp=drive_link]
The project is very basic and the code isn’t that great, but it’s easy to reproduce the bug. When you start the project, just hover the mouse a bit quickly and the raycast will follow, but the 2d area will end up lagging behind. If you find any other bugs or want to comment on something, just let me know. In fact, I’ve just updated the project to Godot 4.3 stable

You need to unlock the access for the link.

Sorry, I just got the notification. I’ve already updated the configuration, see if that works.

I did a check and i ddn’t noticed any lagging between the raycast and the area (the video is slowed down):

You have to move your mouse quickly towards a point that has no wall. I’ll record a video later to show it better. But thanks

Sorry, i had to leave to work, but now i had time to proper look for a solution for you, the problem is the mouse moviment can be too fast for the engine physics ticks follow, but in this case you can make the raycast keep moving until find an edge, like this (needs a extra raycast as player child, dont need the offset raycasts):

image

extends Node2D

# Componentes
@onready var Player = $"."
@onready var Mira = $Mira
@onready var Alvo = $Mira/Area2D
@onready var timer = $Mira/Timer
@onready var desired_pos: RayCast2D = $desired_pos
@onready var Line = $Mira/Line2D
var target = self.global_position
var speed = 2500
var Colldown = false

var desired_angle = 0
var last_valid_vector: Vector2
var current_deg_step := 45

func _ready():
	target = self.global_position
	global_rotation = 0


func _physics_process(delta: float) -> void:
	desired_angle = (get_global_mouse_position() - global_position).normalized()
	desired_pos.target_position = desired_angle * 800
	
	desired_pos.force_raycast_update()
	if desired_pos.is_colliding():
		Mira.target_position = desired_pos.target_position
		Line.set_point_position(1, Mira.to_local(desired_pos.get_collision_point()))
		Alvo.global_position = desired_pos.get_collision_point()
		last_valid_vector = Mira.target_position
		current_deg_step = 45
	
	else:
		var multiplier = 1
		if rad_to_deg(desired_pos.target_position.angle()) > rad_to_deg(Mira.target_position.angle()):
			multiplier = 1
		
		else:
			multiplier = -1
		
		Mira.target_position = Mira.target_position.rotated(deg_to_rad(current_deg_step * multiplier))
		Mira.force_raycast_update()
		
		if current_deg_step > 0.0:
			if Mira.is_colliding():
				last_valid_vector = Mira.target_position
				Line.set_point_position(1, Mira.to_local(Mira.get_collision_point()))
				Alvo.global_position = Mira.get_collision_point()
			
			else:
				current_deg_step = lerp(current_deg_step, 0, 0.5)
				Mira.target_position = last_valid_vector

Thank you so much! I was already giving up on doing it this way, but it turned out great. Just one more doubt, there are some things in your code that I’m not sure why it’s like that, but I think it’s what they call “Good programming practices”. I don’t know much about gdscript so I wanted to try to make my code a little better and learn a little from what you’ve written here.

First, when you used something like :Vector2 when declaring a var you only specify the type of information it holds, right? Or does it change anything else?
Second, what’s the difference between using _physics_process(delta: float) → void: and _physics_process(delta)?
And lastly, in the first code I was dividing the execution by functions that were called in the process, because for me it’s easier to understand, and putting everything in a single function would be too big and I’ve always heard that the smaller the function the better. Is there a problem with doing it this way? Does it affect performance?

Thanks again for the solution, and sorry for taking so long to reply. No need to reply if you’re busy

1° - This is called static typing and yes, that means i’m specifing the variable type to only holds values of that type. That has lot of benefits:

  • Better autocompletion: Because knowing the type the editor can give you better info.
  • Less mistakes: A classic example is using Label nodes when you’ll change their text but you forget the .text after the node reference, the editor will imediatly throw an error about attempt to attribute a String for a variable of type Label.
  • More perfomance: In this case is more for mathematical operations because the code can do shortcurts without the need to check for the variable type.

2° - Is basically the same as the 1°, the delta: float is typing the parameter delta as float variable and the -> void means this function don’t return anything as value. But if returned a int as example, that should be -> int and will be the same advantages of the 1°.


3° - Preference, in the end that will be preference, calling a function has a slight more cost but that would be a problem only if you did 500 functions calls in the same frame, in your case the performance impact is irrelevant.

1 Like

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