Visualising RayCasts in Godot
I began creating a new game this week and today needed to create an on screen visualisation of where the user is aiming. To do this, I wanted to be able to draw a line that would start at the origin of a RayCast
and end where it collides with another body (to stop a line being indefinitely drawn through the level’s geometry).
I could find several examples of how to do this in 2D space, but as I am building a 3D game, the methods shown would not work. Eventually, I came across the ImmediateGeometry class, which allowed me to achieve exactly what I wanted to.
An example of how the final product looks can be found below. The line extruding from the rotating arrow is a visual representation of the RayCast
and ends at each object that it collides with.
The node tree for this example looks like this:
There’s not much special going on here, but for completeness sake, the purpose of each node is as follows:
- The
Arrow
node is aSpatial
which contains the arrow mesh and also contains theRayCast
that will be visualised. - The
ImmediateGeometry
node is what will be used to “draw” the visualisation - The
Shapes
spatial contains the various meshes that are setup with collision enabled that are placed around the scene
The script attached to the root node is:
extends Spatial
onready var arrow = $Arrow
onready var raycast = $Arrow/RayCast
onready var line = $ImmediateGeometry
func _process(delta):
arrow.rotate_y(delta * 1.5)
line.clear()
if raycast.is_colliding():
line.begin(Mesh.PRIMITIVE_LINE_STRIP)
line.add_vertex(to_local(raycast.global_transform.origin))
line.add_vertex(to_local(raycast.get_collision_point()))
line.end()
First, ImmediateGeometry->clear is called, which will remove everything that was previously drawn.
Next, if RayCast
has collided with another body, ImmediateGeometry->begin is called with the PRIMITIVE_LINE_STRIP
constant, which will ensure that the specified vertexes will be rendered as a line strip (see Mesh::PrimitiveType for more options).
Now that the ImmediateGeometry
node is ready to begin creating the mesh, two calls are made to ImmediateGeometry->add_vertex, the first being the starting point (i.e. the origin of RayCast
) and the second being the point where RayCast
detected a collision.
An important thing to note about plotting the positions, is that they need to be in local space. All classes that extend Spatial
have a to_local
method available to them, which will take a global coordinate and translate it to a local one. For this reason, rather than accessing raycast.transform.origin
, I use the global transform instead. Likewise, the Vector3
returned from raycast.get_collision_point
is in the global coordinate system, and needs to be converted to local.
Finally, calling ImmediateGeometry->clear will finish the rendering and draw the line on the screen.
You can grab a copy of the sample project at RobTheFiveNine/godot-raytrace-visualisation-example.