In an RTS game, we are often required to point & click different units, buildings and objects. We identify what the user is clicking on so we can do actions like moving the unit to destination (point where user has clicked). We also have to identify what type of thing the user is clicking on (it could be a resource that can be collected, such as stone).
For this, I used raycast-based approach. Ray casting is a process in which we fire a ray (like an arrow) in a particular direction, from the camera, to check for intersections with the physics bodies in the game. If it intersects with some physics body, then it means raycast has hit it. The result of raycast usually gives information such as the exact position where the collision occurred, the reference of other object, and the normals of the colliding object at that point and so on.
Implementing Raycast System
Our ray casting system will provide some functions that are used in many places across the project. So I made it as a singleton (globally accessible). First of all, create a folder named “RaycastSystem”. Make a script named RaycastSystem.gd inside the folder.
- To make it singleton in Godot, navigate to the Project -> Project Settings -> Globals -> and add
RaycastSystem
script there.
In the code, we make a function to do raycast collision/intersection test, using Godot’s built-in RayCast3D
node. Then we will make more specific functions, one to check the exact point of collision in the world (useful when we want to get the position in map where user has clicked the mouse, so unit moves to that position); and other to get the body to which raycast has hit (which is the object which user wants to select).
Full Godot Raycast Code
# GLOBAL SCRIPT. ACCESS via RaycastSystem.foo()
# Raycast queries are defined here. All other modules can use it.
extends Node3D
const RAY_LENGTH := 1000
"""
Uses default collision_mask. But can be overrided for custom collision
mask/layers.
Output format:
output_dict = {
"collider": None, # The colliding object (if any)
"collider_id": None, # The colliding object's ID (if any)
"normal": [0, 0, 0], # The surface normal at the intersection point
"position": [0, 0, 0], # The intersection point
"face_index": -1, # The face index at the intersection point
"rid": None, # The intersecting object's RID
"shape": None # The shape index of the colliding shape
}
"""
# Returns raycast result after it hits an object in the world.
# @return Dictionary or null
func _do_raycast_on_mouse_position(collision_mask: int = 0b00000000_00000000_00000000_00000001):
# Raycast related code
var space_state = get_world_3d().direct_space_state
var cam = get_viewport().get_camera_3d()
var mousepos = get_viewport().get_mouse_position()
var origin = cam.project_ray_origin(mousepos)
var end = origin + cam.project_ray_normal(mousepos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin, end)
query.collide_with_areas = true
query.collision_mask = collision_mask
var result = space_state.intersect_ray(query) # raycast result
return result
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# API below:
# Gets ray-cast hit position from camera to world.
# @return Vector3 or null
func get_mouse_world_position(collision_mask: int = 0b00000000_00000000_00000000_00000001):
var raycast_result = _do_raycast_on_mouse_position(collision_mask)
if raycast_result:
return raycast_result.position
return null
# Gets ray-cast hit object from camera to world.
# @return Object or null
func get_raycast_hit_object(collision_mask: int = 0b00000000_00000000_00000000_00000001):
var raycast_result = _do_raycast_on_mouse_position(collision_mask)
if raycast_result:
return raycast_result.collider
return null
Function such as get_mouse_world_position
& get_raycast_hit_object
are frequently used in successive tutorials. They are necessary for selection of units, selection of buildings, point-and-click movement of units and so on.
Notice how I passed collision_mask
as a parameter & set its default value to be Godot’s default collision mask. This is to allow the programmer to test raycast in different physics collision layers if they desire. By default, all things in Godot are in default collision layer.