The first thing about Godot raycast that might not make any sense to anyone who base their ideas/concept/application from Unity is the difference in how both manage the order and access to the physics data.
Unity works with stacks which allows data to be considered in the calculation of the physics engine even when outside the Update() phase during which physics are applied. As such, if there’s a change coded outside of the Update() function that affects the physics of an object, that change will be considered by raycast and other kind of physics related calculation if done after said changes, but also before the next physics loop.
Godot doesn’t work with stacks on object/nodes, but with a snapshot of the scene state which is only updated during the _PhysicsProcess(). (I’ll immediately point out that I’m not considering/writing about how multi-threading the physics onto a separate thread and how it would change things with Godot.) Due to that, whenever you cast a raycast, you’re working with the latest updated state which might, depending on where you call it, be obsolete already.
What you might not know about Unity’s camera.ScreenPointToRay(); function is that it’s does something a really similar thing to what you do in Godot with only the caveat of having to update the 3D space state separately. If you look at the source code of what ScreenPointToRay() does behind, you would get that it’s not just as simple as it looks. As such, you could do an extremely similar function/tool in Godot by creating your own Ray class.
For example, this is what I would write in the script (in C#) that would require the raycast:
[Export] public Node3D DebugAimSphere;
private Vector3 AimLocation;
private Ray ray;
private Dictionary CamRaycastResult;
[Export] private float CamRayDistance = 1000f;
public override void _Ready()
{
base._Ready();
ray = new Ray();
}
public override void _Input(InputEvent _ie)
{
base._Input(_ie);
CamRaycastResult = ray.RaycastScreenPointToRay(GetViewport().GetMousePosition(), GetViewport().GetCamera3D(), CamRayDistance);
if (CamRaycastResult.Count > 0)
{
if (DebugAimSphere != null)
{
AimLocation = (Vector3)CamRaycastResult["position"];
DebugAimSphere.GlobalPosition = AimLocation;
}
}
}
and I would have this as a Ray.cs file in my project:
using Godot;
using Godot.Collections;
public partial class Ray
{
private Vector3 Raycast_Start = new Vector3();
private Vector3 Raycast_End = new Vector3();
private PhysicsRayQueryParameters3D CamRaycastQuery;
public Dictionary RaycastScreenPointToRay(Vector2 mousePosition, Camera3D curCam, float rayDistance)
{
if (WorldManager.Instance == null || WorldManager.Instance.space3D == null)
{
GD.Print("Space 3D is not set yet.");
return new Dictionary();
}
Raycast_Start = curCam.ProjectRayOrigin(mousePosition);
Raycast_End = Raycast_Start + (curCam.ProjectRayNormal(mousePosition) * rayDistance);
return Raycast();
}
private Dictionary Raycast()
{
CamRaycastQuery = PhysicsRayQueryParameters3D.Create(Raycast_Start, Raycast_End, Constants.Ref_Mask_PlayerInteraction);
CamRaycastQuery.CollideWithBodies = true;
return WorldManager.Instance.space3D.IntersectRay(CamRaycastQuery);
}
}
The reason why I separate the PhysicsRayQueryParameters3D into a separate function in the Ray class is that I could add more functions later that wouldn’t be based on the camera, but maybe just 2 different locations/nodes/objects positions.
As I previously mentioned, only the PhysicsDirectSpaceState3D is kinda “unique” to Godot. In the code above, I set it up so that the world state is generated only once on a parent Node3D that exists at all time and this has the following script:
using Godot;
public partial class WorldManager : Node3D
{
private static WorldManager _instance;
public static WorldManager Instance => _instance;
public PhysicsDirectSpaceState3D space3D { get; private set; }
public override void _EnterTree()
{
if (_instance != null && _instance != this)
{
_instance.QueueFree(); // A singleton of the World Manager already exist. Delete it.
}
_instance = this;
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
space3D = GetWorld3D().DirectSpaceState;
}
}
As I previously mentioned, Godot uses a snapshot of the current scene’s content (state) for its physics. that space3D of type PhysicsDirectSpaceState3D is basically a copy of that snapshot. Whenever you do raycast, you basically ask the physic engine to look at that stored state and check if there’s a contact or not.