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)
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.
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
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.
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
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):
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.