Unity Raycast 2D what is it and how to use it

GDT cover blog

Today there are two friends here to talk about the Unity Raycast 2D.

Let me introduce Rogue Mike and Ray P. Gee to you. They really know how to explain things!

C’mon guys! We depend on you now!

Well! Hey Mike would you like a Unity Raycast?

No thanks Ray I’m good.

Wait a second! Guys, do you know what Unity Raycast is?

Ray suggests it’s something to drink.

Ok well bye guys. Thank you for your help.

I think as usual it will be me and you to solve another case. Let’s answer the two important questions about it.

What is Unity Raycast 2D? How do you use it in our game?

We will discover together, follow me.

What is Unity Raycast 2D?

What is it

Let’s start with the basics. A Raycast is like a laser beam fired from a point along a particular direction as reported in the official documentation

The big advantage is that every object which makes contact with the laser beam can be reported!

Then why could a laser beam be useful for our game?

Let’s see some common cases of usage:

  • Detect if a player or an enemy or an object in general sees something.
  • Define where an object like a missile or a bullet for instance will hit another object.
  • Use as support for physics collision detection (check if an object is grounded for instance or to change the behavior of a platform if the player is underneath or above it).

We will cover some of these interesting cases together.

So what are we waiting for? Let’s create our first laser beam to detect objects in our game!

Physics 2D Raycast method

Raycast method

The method we need to do all the magic is this one Physics2D.Raycast. I knew you would have noticed, using the Physics 2D library means we have to use it in the FixedUpdate for a good practice.

We can see different versions of it, but let’s focus on the first one, we will talk about the others later in the article.

public static RaycastHit2D Raycast(Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);

We can immediately see that the method returns one object called RaycastHit2D and eventually it’s the object hit by the laser.

To be hit by the ray an object should have a collider 2D component, if you want more information about this great and extremely useful component please check the article Unity Collision Detection 2D Everything You Need To Know + Examples

First clue, if this method returns a RaycastHit2D object it means the laser hit something, if this method returns null, then the laser has hit nothing.

Parameters

Then we can see different parameters, we need to find out their scope.

  • Origin: The starting point of the ray.
  • Direction: The direction of the ray. 
  • Distance: The coverage of the ray, you can think of it like the length of the ray.
  • LayerMask: In Unity you can assign a layer to a gameobject, with this mask we can decide which layers we want to report the collision with the ray, ignoring the others. (By default if you don’t set this parameter Unity ignores the collisions with the layer “Ignore Raycast” as suggested by the name).
  • MinDepth: Detect collisions only with objects with a z transform position greater or equal of this value.
  • MaxDepth: Detect collisions only with objects with a z transform position lesser or equal of this value.

It’s very important to notice that this version of the method will report only the first object hit by the laser according to the configuration. If we have 2 consecutive objects only the first object hit by the laser will be returned. The second could be returned only if the first is ignored due to the configuration we applied.

Examples

Now let’s see a simple code to generate and debug a ray in our game.

void FixedUpdate()
{
	//Length of the ray
	float laserLength = 50f;

	//Get the first object hit by the ray
	RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.right, laserLength);

	//If the collider of the object hit is not NUll
	if (hit.collider != null)
	{
		//Hit something, print the tag of the object
		Debug.Log("Hitting: " + hit.collider.tag);
	}

	//Method to draw the ray in scene for debug purpose
	Debug.DrawRay(transform.position, Vector2.right * laserLength, Color.red);
}

The ray is invisible, if we want to see it in the scene for debug purposes, then we need to use the method Debug.ray. In this way, we can easily see in our scene the ray created, here is an example.

Debug Ray 2D example

Please notice one important thing, if the ray starts inside the collider of the object than the object will be reported as the object hit by the ray, if you want to avoid this you can either change the start position of the ray outside the object collider or assign the object to a specific layer and ignore that layer with a mask. We will see later how the layer masks work.

Physics 2D Raycast configuration

Now let’s see some visual examples of different situations generated by different parameter configurations.

We need a context, let’s pretend we have 3 consecutive objects and a player with a gun. From the gun starts our laser beam. If one object is hit by the laser beam it should change its colour from red to green.

Configuration example

Let me show you with different configurations what will happen in this context.

  • Origin: Gun point.
  • Direction: Right. 
  • Distance: Infinite.
  • LayerMask: Not configured.
  • MinDepth: Not configured.
  • MaxDepth: Not configured.

No configuration

The first object is hit because it’s the first hit by the laser beam.

  • Origin: Gun point.
  • Direction: Right. 
  • Distance: Infinite.
  • LayerMask: LayerMask.GetMask(“Default”) //Only the objects with layer Default.
  • MinDepth: Not configured.
  • MaxDepth: Not configured.

layerMask

The first object is not  hit because its layer property is set to UI and we ignore any layer except Default. Then the second is hit.

  • Origin: Gun point.
  • Direction: Right. 
  • Distance: Infinite.
  • LayerMask: LayerMask.GetMask(“Default”) //Only the objects with layer Default.
  • MinDepth: 0 //Minimum position z accepted
  • MaxDepth: Not configured.

minDepth

The first object is not hit because its layer property is set to UI and we ignore any layer except Default. The second is ignored too because its position z is -1. We set minDepth starting from 0. So the third will be hit.

Here the snippet with the configurations.

 void FixedUpdate()
{
	//Length of the ray
	float laserLength = 50f;
	Vector2 startPosition = (Vector2)transform.position + new Vector2(0.5f, 0.2f);
	int layerMask = LayerMask.GetMask("Default");
	//Get the first object hit by the ray
	RaycastHit2D hit = Physics2D.Raycast(startPosition, Vector2.right, laserLength, layerMask, 0);

	//If the collider of the object hit is not NUll
	if (hit.collider != null)
	{
		//Hit something, print the tag of the object
		Debug.Log("Hitting: " + hit.collider.tag);
		//Get the sprite renderer component of the object
		SpriteRenderer sprite = hit.collider.gameObject.GetComponent<SpriteRenderer>();
		//Change the sprite color
		sprite.color = Color.green;
	}

	//Method to draw the ray in scene for debug purpose
	Debug.DrawRay(startPosition, Vector2.right * laserLength, Color.red);

}

Physics 2D Raycast different usage examples

Different ways

It’s time to see some real scenarios to understand the power of Unity Raycast 2D.

I would like to share with you three important cases we can solve with the support of Raycast.

Sentinel Enemy

This scenario is when we have some enemies and we have to sneak around to avoid being caught. The laser beam could be the visual range of a sentinel. So if the player is hit by the laser beam the sentinel notices him.

Grounded

One of the big problems in game development is to avoid the player jumping when he is in the air infinite times, so we need to know when the player is touching the ground and only then allow the player to jump.

This is an important problem, knowing if the player is “grounded” can help us define when he will be able to jump for example.

We can solve this problem with the Raycast 2D (not only), let’s see how in the snippet.

//Public configuration
public float speed = 10f;
public float jumpForce = 5f;
//Components
private Rigidbody2D rb2d;
private SpriteRenderer sprite;
private BoxCollider2D bCol2d;
//Private attributes
private bool jump = false;

// Start is called before the first frame update
void Start()
{
	rb2d = GetComponent<Rigidbody2D>();
	sprite = GetComponent<SpriteRenderer>();
	bCol2d = GetComponent<BoxCollider2D>();
}

private void Update()
{
	//Pressing the key space
	if (Input.GetKeyDown(KeyCode.Space))
	{
		//Let the player jump
		jump = true;
	}
}

void FixedUpdate()
{

	//Store the current horizontal input in the float moveHorizontal.
	float moveHorizontal = Input.GetAxis("Horizontal");

	//Flip the sprite according to the direction
	if (moveHorizontal < 0)
	{
		sprite.flipX = false;
	}
	else if (moveHorizontal > 0)
	{
		sprite.flipX = true;
	}

	//Move the player through its body
	rb2d.velocity = new Vector2(moveHorizontal * speed, rb2d.velocity.y > -4.0f ? rb2d.velocity.y : -4.0f);

	//Check if the player ray is touching the ground and jump is enable
	if (Grounded() && jump)
	{
		//Jump
		rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce);
	}
	//Disable the jump
	jump = false;

}

bool Grounded()
{
	//Laser length
	float laserLength = 0.025f;
	//Start point of the laser
	Vector2 startPosition = (Vector2) transform.position - new Vector2(0,(bCol2d.bounds.extents.y + 0.05f));
	//Hit only the objects of Floor layer
	int layerMask = LayerMask.GetMask("Floor");
	//Check if the laser hit something
	RaycastHit2D hit = Physics2D.Raycast(startPosition, Vector2.down, laserLength, layerMask);
	//The color of the ray for debug purpose
	Color rayColor = Color.red;
	//If the object is not null
	if (hit.collider != null)
	{
		//Change the color of the ray for debug purpose
		rayColor = Color.green;
	}
	else
	{
		//Change the color of the ray for debug purpose
		rayColor = Color.red;
	}
	//Draw the ray for debug purpose
	Debug.DrawRay(startPosition, Vector2.down * laserLength, rayColor);
	//If the ray hits the floor return true, false otherwise
	return hit.collider != null;
}

Generating a small laser beam from the bottom of the player it could be used to check whether the player is touching the ground.

Ground check

Below you can see a little example of a play pressing multiple times the jump button, only if the player is touching the ground the action will affect the player.

Cloud Platforms

I call cloud platforms the platforms the player can pass through jumping when he is underneath but collides when he is falling down from above. Let me show you an example to understand better.

Our goal is to use Unity 2D Raycast to let our player jump through the platform, but land above.

Let’s see how

bool CloudPlatformCheck()
{
	//Laser length
	float laserLength = 1.0f;
	//Left ray start X
	float left = transform.position.x - (bCol2d.size.x * transform.localScale.x / 2.0f) + (bCol2d.offset.x * transform.localScale.x) + 0.1f;
	//Right ray start X
	float right = transform.position.x + (bCol2d.size.x * transform.localScale.x / 2.0f) + (bCol2d.offset.x * transform.localScale.x) - 0.1f;
	//Hit only the objects of Platform layer
	int layerMask = LayerMask.GetMask("Platform");
	//Left ray start point
	Vector2 startPositionLeft = new Vector2(left, transform.position.y - (bCol2d.bounds.extents.y + 0.05f));
	//Check if the left laser hit something
	RaycastHit2D hitLeft = Physics2D.Raycast(startPositionLeft, Vector2.down, laserLength, layerMask);
	//Right ray start point
	Vector2 startPositionRight = new Vector2(right, transform.position.y - (bCol2d.bounds.extents.y + 0.05f));
	//Check if the right laser hit something
	RaycastHit2D hitRight = Physics2D.Raycast(startPositionRight, Vector2.down, laserLength, layerMask);
	//The color of the ray for debug purpose
	Color rayColor = Color.red;

	Collider2D col2DHit = null;
	//If one of the lasers hit a cloud platform
	if (hitLeft.collider != null || hitRight.collider != null)
	{
		//Get the object hit collider
		col2DHit = hitLeft.collider != null ? hitLeft.collider : hitRight.collider;
		//Change the color of the ray for debug purpose
		rayColor = Color.green;
		//If the cloud platform collider is trigger
		if (col2DHit.isTrigger)
		{
			//Store the platform to reset later
			currentPlatform = col2DHit;
			//Disable trigger behaviour of collider
			currentPlatform.isTrigger = false;
			//Color the sprite of the cloud platform for debug purpose
			SpriteRenderer sprite = currentPlatform.gameObject.GetComponent<SpriteRenderer>();
			sprite.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
		}
	}
	else
	{
		//Change the color of the ray for debug purpose
		rayColor = Color.red;
		//If we stored previously a platform
		if (currentPlatform != null)
		{
			//Reset the platform properties
			currentPlatform.isTrigger = true;
			SpriteRenderer sprite = currentPlatform.gameObject.GetComponent<SpriteRenderer>();
			sprite.color = new Color(1.0f, 1.0f, 1.0f, 0.5f);
			currentPlatform = null;
		}
	}

	//Draw the ray for debug purpose
	Debug.DrawRay(startPositionLeft, Vector2.down * laserLength, rayColor);
	Debug.DrawRay(startPositionRight, Vector2.down * laserLength, rayColor);
	//If the ray hits a platform returns true, false otherwise
	return col2DHit != null;
}

Remember to call this method in the FixedUpdate!

What if we want to let the player fall down from a platform pressing the down arrow and the space button for instance?
pssst…. At the end of the article, you will find a link to an interesting project.

Multiple objects detection

Multiple objects

Raycast with ContactFilter2D

Before we decided to ignore the different versions of the methods Physics2D.Raycast. We did it because we were just novices in the case, but now we know everything so let’s figure out the secret behind it.

What if we want to detect with a singular ray more collisions at the same time?

We can do it in multiple ways.

Array

public static int Raycast(Vector2 origin, Vector2 direction, ContactFilter2D contactFilter, RaycastHit2D[] results, float distance = Mathf.Infinity);

List

public static int Raycast(Vector2 origin, Vector2 direction, ContactFilter2D contactFilter, List<RaycastHit2D> results, float distance = Mathf.Infinity);

There are 3 differences with the previous version:

  • The methods return an integer
  • They have a new parameter ContactFilter2D
  • They have special parameter results, one is an array and one is a List.

As you can imagine these new versions of the method allows us to get more objects hit by the ray simultaneously.

  • Both return an integer that will be the number of the objects hit by the ray.
  • In both the results can be filtered by the parameter ContactFilter2D, this is a class to define our configuration like maskLayer, depth, if a collider is a trigger etc… In this way only the objects that respect these configurations will be reported.
  • The first version works with arrays, the second with List, the big difference is that while the arrays once allocated will occupy the memory defined, the lists will be resized if it doesn’t contain enough elements to report all the results. This prevents memory from being allocated for results when the results list does not need to be resized, and improves garbage collection performance when the query is performed frequently.
// Allocate the memory to store the objects hit by the laser
RaycastHit2D[] hits = new RaycastHit2D[10];

// Start is called before the first frame update
void Start()
{
	//Get components
	rb2d = GetComponent<Rigidbody2D>();
	sprite = GetComponent<SpriteRenderer>();
	bCol2d = GetComponent<BoxCollider2D>();
	//Create a contactFilter configuration for the rays to check if the player is grounded
	filter2D = new ContactFilter2D
	{
	//Ignore trigger colliders
	useTriggers = false,
	//Use a layer mask
	useLayerMask = true
	};
	//Set the layer mask to hit only Floor and Platform layer
	filter2D.SetLayerMask(LayerMask.GetMask("Bonus"));

}

void FixedUpdate()
{
	//Laser length
	float laserLength = 0.025f;
	//Right ray start X
	float startPositionX = transform.position.x + (bCol2d.size.x * transform.localScale.x / 2.0f) + (bCol2d.offset.x * transform.localScale.x) - 0.1f;
	//Left ray start point
	Vector2 startPosition = new Vector2(startPositionX, transform.position.y - (bCol2d.bounds.extents.y + 0.05f));
	//The color of the ray for debug purpose
	Color rayColor = Color.red;
	//Check if the left laser hits something
	int totalObjectsHit = Physics2D.Raycast(startPosition, Vector2.down, filter2D, hits, laserLength);

	//Iterate the objects hit by the laser
	for (int i = 0; i < totalObjectsHit; i++)
	{
		RaycastHit2D hit = hits[i];
		//Do something
		if (hit.collider != null)
		{
			SpriteRenderer sprite = hit.collider.GetComponent();
			sprite.color = Color.green;
		}
	}
}

As you can see I defined an array with a prefixed number of elements to store on it. With the previous snippet, we can get at the same time all the bonuses hit by the laser.

RaycastAll

Well now we know quite well all the Raycast stuff, we just need to talk about one more important feature.

Let me introduce the simpler one to use, Physics2D.RacastAll this method will return all the objects hit by ray ignoring the ones we defined in the configuration.

public static RaycastHit2D[] RaycastAll(Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);

This method is very similar to the one we just analyzed except for the return value. As you can see it will return an array of RaycastHit2D  instead of a single one.

Iterating the array we could check and do what we want to the objects we hit.

The colliders in the array are sorted by the distance from the origin point of the laser.

This method could return a big amount of objects, so use it carefully.

Raycast Non Alloc

A super safer way to detect multiple objects with a ray is using the dark side version 😛 of the RaycastAll, called Physics2D.RaycastNonAlloc.

Let’s immediately see the method.

public static int RaycastNonAlloc(Vector2 origin, Vector2 direction, RaycastHit2D[] results, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);

This time we have the result array as a parameter of the method, while the return is an integer. While the integer returned indicates the number of objects hit by the ray, while the array contains the objects hit.

Let me explain why it works this way and what is the convenience in terms of performance for us:

Now let me describe what will happen if I hit 3 elements or 10 elements for instance.

  • If we hit 3 elements the method returns 3 and the results array will contain 3 elements out of 10, now if we want to iterate over the elements without causing any crashes due to editing NULL values we can use the returned value to know how many elements are effective in the array.
  • If we hit 10 elements the method returns only the first 5 elements according to the max number decided by the array

It’s extremely important this method, no memory is allocated for the results and so garbage collection performance is improved when raycasts are performed frequently.

The final example challenge + code

Hey! Another record from the secret files of the blog.

Only a Malesian Dragon could be able to create something like this. 🐲

Do you think you will be able to figure it out?

If not, don’t worry, you can find the complete project on GitHub, but promise to look at it only as the last resort! 😉

Conclusions

Wow! No more confusion between Raycast and drinks I’m sure!

As usual let’s do a quick recap.

Unity Raycast 2D, firing a laser beam from a point in a certain direction and detecting the colliders 2D through the way, helps us in different ways.

It’s useful to:

  • Check if the player is grounded
  • Check if an object sees another object in the distance
  • Physics purposes like detecting that 2 objects will get in contact in the laser direction.

There are different way to use the Unity Raycast 2D:

  • Use Physics2D.Raycast to get the first object hit by the laser beam.
  • You need Physics2D.Raycast with results array or list as parameters to get multiple objects hit by the laser beam filtered by contactFilter2D configuration.
  • Manage carefully Physics2D.RaycastAll to get all the objects hit by the laser beam.
  • Join the dark side using Physics2D.RaycastNonAlloc to get multiple objects hit by the laser beam avoiding allocating memory.

I would never have made it without you.

Thank you partner.

See you for the next case!

More from Romeo Violini

Unity collision detection 2D everything you need to know + examples

Here we are, I can feel another mystery, Unity collision detection 2D....
Read More

Leave a Reply

Your email address will not be published. Required fields are marked *