-
Notifications
You must be signed in to change notification settings - Fork 0
Raycasting, Projectiles, and Hitboxes
Detecting when an object is hit by another in game development is essential to almost every genre--- think platformer games with side-scrolling enemies, stealth games about sneaking around guards, space-shooter games where spaceships launch projectiles, etc. Many of these concepts are connected in the sense that you're trying to detect when objects in your game are positioned in a specific way relative to one-another. There are a few technologies that are most commonly used to accomplish some of these interactions. We'll go over them all briefly, and their use cases in this tutorial, alongside some brief implementations and the like, but as per usual the documentation on the official Godot wiki is the best resource available.
Hitboxes are a basic principle in game development that describe the physical properties of an object in a game. Whereas a mesh (created in external CAD software, such as Blender) defines an object's visual appearance through the usage of polygons, hitboxes define the way objects collide with their environment, interact with raycasts (more on this later), and even how they affect other objects with hitboxes.
It's really slow and resource-inefficient to calculate collisions on detailed models. One can simplify most models to one (or a few more, depending on the use case) simpler shapes (usually boxes or cylinders) and save hugely on performance. For this reason, most game engines will require you to provide a collider.
In Godot, we have the CollisionShape2D/3D Node, which is applied to RigidBody nodes to create physics-affected objects and CharacterBodies.
We can also use the Area nodes to define hitboxes which don't engage in the physics simulation but can still detect for other Area nodes or CollisionBodies.
Raycasting is a method of drawing a straight line to check for collisions with solid bodies, first developed to aid in the rendering of 3D Graphics. Raycasts in Godot (also referred to as hitscans) check for CollisionBodies which lay upon a line in 2D space. They occur instantaneously, are unaffected by physics, and (by default) have no visual or physical appearance--- they are just an algorithmic method of finding intersections on a line with code. They are often used to simulate ballistics or lasers in shooter games, and the same algorithms are used for even modern rendering systems like ray-traced lighting.
Ray casting in Godot can seem daunting at first, but the documentation walks through it in great detail: Link
To summarize, you must first access the physics engine of Godot (the "space") in a script by using the get_world_2d/3d().space
method. This will return a Resource ID (RID) which is just an identifier for Godot's lower-level resources--- don't worry, since Godot is loosely typed, you can just use the var
keyword to define variables containing the "space". You'll have to access the "space" in the _physics_process()
method callback due to some runtime restrictions.
After accessing the current space state, you're then able to perform a raycast query and the results returned will contain the position of the collision and other vital information.
Note about raycast results: raycasts will return an empty dictionary, represented in GDScript with the syntax {}
if the raycast doesn't intersect a collider (essentially, if it misses). Check using an if-statement that the raycast result is not empty to avoid errors.
As mentioned previously, raycasts are often a good way to simulate ballistics or lasers between points (i.e. the cannons on a space-ship to the mouse's position). Something like this would do the trick in 2D.
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
# use global coordinates, not local to node
var query = PhysicsRayQueryParameters2D.create(position, get_viewport().get_mouse_position()) # This will fire a raycast between the position of the Node this script is attached to and the position of the mouse, in 2D space.
var result = space_state.intersect_ray(query)
The only problem here is that you might not want a raycast to collide with certain objects (i.e. obstacles that have colliders but shouldn't interact with the player's lasers). The PhysicsRayQueryParameters class can take a collision_mask argument when it is created, which expects a bitmap. If this issue arises, you should see the Collision layers and masks documentation from Godot, as this is a far more indepth subject.
Other common uses of raycasting include line-of-sight (ensuring one object can "see" another, without obstruction), and pathfinding.