Ruby can now fix broken robots, but it’s a lonely world where she is the only inhabitant. To fix that, let’s add another character in the Scene. And to make the new character useful, let’s allow Ruby to talk to it and give her a mighty quest: to fix all the robots!
Recommended Unity Versions
World Interactions - Dialogue Raycast
Creating the character
The first task is to create a character, which will be a frog called Jambi. Now that you are getting used to Unity, this should quick and straightforward. To make it even easier, the character uses a single looping animation.
The Sprite Atlas to use is JambiSheet in the Art/Sprites/Characters folder.
Slice your sprites using the Sprite Atlas format of 4 by 4 sprites.
Import the Sprites into the Scene to create the character. Tip: if you select the 3 sprites in your Project folder and drag them all at once into the Hierarchy, Unity creates the animation of those 3 frames automatically for you and assigns it to the newly created GameObject, so it will play automatically and no other action is needed:
Go back to your Jambi sprite atlas in your Project window asset and change the Pixel per Unit setting to something you think will look nice. Change the value, press Apply and check in the scene view if Jambi is the right size for you. We used 150.
Add a Box Collider 2D and scale it to cover the bottom of the Sprite like we did for our main character or our enemy.
Create a layer called “NPC” (for Non Player Character) and set your character GameObject to that.
Finally, rename the GameObject Jambi and create a Prefab out of that GameObject.
You can now enter play mode and test your character is properly animated and collides correctly.
If the animation that was created automatically looks like it’s running too fast, you can always change the sample rate in the Animation window (menu Window > Animation > Animation), just like we did for our handmade animation clip.
Now we need to add code so that Ruby can talk to our frog character. First, we need to know if Ruby is standing in front of the character.
We could place a trigger in front of the frog character and if Ruby walks on that trigger, then the dialog starts. But this means that Ruby could be turning her back to the frog and still be able to talk to it.
Instead, we will use a physics system feature that is really useful in interactive applications: raycasting. Raycasting is the action of casting a ray in the Scene and checking to see if that ray intersects with a collider. A ray has a start point, a direction and a length, and we say we “cast” the ray because the test is made from the start point, all along the ray until its end.
Here we will cast a ray from our main character’s position, in the direction she is looking, for a small distance like 1 or 1.5 units. To do this, add the following code in your Update function of your RubyController:
RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));
if (hit.collider != null)
Debug.Log("Raycast has hit the object " + hit.collider.gameObject);
Let’s take a look at that code in detail:
In the first line, we check to see if the X key is pressed, just like we did in the projectile lesson. This will be our “talk” button. If you want this to work across multiple devices instead, you can use input axes (for a reminder, see the character controller movement lesson).
If that key is pressed, we enter the if block and start our raycast.
First, we declare a new variable of type RaycastHit2D. This variable stores the result of a raycast, which is given to us by Physics2D.Raycast. There are multiple versions of Physics2D.Raycast (for all variants, take a look at the Scripting API), but the version we use here has 4 arguments:
The start point, which in our example is an upward offset from Ruby’s position because we want to test from the centre of Ruby’s sprite, not from her feet.
The direction, which is direction that Ruby is looking.
The maximum distance of our ray, which we’ve set to 1.5 so the raycast doesn’t test intersections that are 1.5 units away from the start point.
A layer mask, which allows us to test only certain layers. Any layers that are not part of the mask will be ignored during the intersection test. Here, we select the NPC layer, because that one contains our frog.
Finally, we test to see if our raycast has hit a collider. If the raycast didn’t intersect anything, this will be null so we do nothing. Otherwise, RaycastHit2D will contain the collider the raycast intersected, so we go inside our final if block to log the object we just found with the raycast.
To see this logging in the Console, enter play mode, position Ruby close to and facing the frog, and then press X.
To keep it simple, we’ll make our dialog appear in a small box above Jambi, our frog friend. We will use a Canvas like we did in the UI lesson, but this time we’ll make the Canvas appear in world space. This means the Canvas will exist in the world, just above Jambi’s head, instead of always being overlaid on the screen.
To add the Canvas, right click Jambi in the Hierarchy (or select Jambi and then click the Create button on top of the Hierarchy), and select UI > Canvas. This creates a child Canvas GameObject for Jambi.
Currently your Canvas is overlaid on the screen. With the Canvas selected in the Hierarchy, go to the Inspector and change the Render Mode to World Space:
You can ignore the Event Camera setting because it is only useful for UI interactions like button presses, and we only want to display text on our Canvas.
Right now our Canvas is way too big. The Canvas size is in pixels, and in the Rect Transform of the Inspector we can see that the Width and Height are in the hundreds (the value will be different for you as it is based on the size of your Gameview when you switch the Canvas mode to World Space):
We could change those values to give our Canvas a proper size in our Scene (for example, a width of 3 units and a height of 2 units), but that would make it harder for us to build our UI. All UI elements (such as image and text) work in pixels, so a Canvas size of width 3 and height 2 would create a box of 3 by 2 pixels.
Instead, we will scale our Canvas, so the size in the Scene is reduced, but its width and height are kept to proper pixel values.
Set Pos X and Pos Y to 0, Width to 300 and Height to 200, and then set the Scale to 0.01 in X, Y and Z:
Now that our Canvas is the right size in our Scene, let’s move it to be above our frog character. Now let’s add an image - right-click the Canvas in the Hierarchy, select UI > Image and drag the Art/Sprites/UI/UIDialogBox sprite into the Source Image setting. Don’t forget to expand the image to fill the Canvas by selecting the bottom right handle while keeping Alt pressed in the Rect transform:
You may notice that our Canvas image appears behind some elements in our Scene. That’s because our Canvas exists in the Scene, so it’s like any other GameObject and can be rendered behind those GameObjects. To make sure nothing renders on top of our Canvas, select the Canvas in the Hierarchy and, in the Inspector, set Order in Layer to a high value (for example, 10):
To add the text to our Canvas, right-click on that Image GameObject in the Hierarchy and choose UI > Text Mesh Pro - Text. This will display the following dialog box:
Click on Import TMP Essentials. When the import is done (the Import TMP Essential button will become greyed out), you can close that window. Your text is now created. Just like you did for the image earlier, click on the expand anchor handle with Alt pressed to spread the text to the full size of the image, then use the small white handle to move the yellow box (the text bounds), to give the text box a margin:
Now, in the Inspector, we can write our text and change the text’s style. Play with all the parameters and enter the text you want Jambi to say!
Finally, we need to display the dialog when Ruby talks to Jambi. For this, let’s hide the Canvas by disabling it. Select the Canvas in the Hierarchy and, in the Inspector, uncheck the checkbox at the top of the Inspector.
Then create a new C# script called NonPlayerCharacter and add it on Jambi.
In that script, create two public variables:
One of type float called displayTime, initialised to 4, which will store how long in seconds our dialog box is displayed.
One of type GameOject called dialogBox that will store the Canvas GameObject, so we can enable/disable it in the script.
Then a private variable:
One of type float called timerDisplay that will store how long to display our dialog.
public float displayTime = 4.0f;
public GameObject dialogBox;
In the Start function, we need to make sure that the dialogBox is disabled, and initialise timerDisplay to -1:
In the Update function, we just check whether the dialog is currently displayed by testing if timerDisplay is superior or equal to 0. If it is greater than zero, then the dialog is currently being displayed. In this case, we decrease Time.deltaTime to count down and then check if our timerDisplay has reached 0, which means it’s time to hide our dialog box again, so we disable the dialog box:
if (timerDisplay >= 0)
timerDisplay -= Time.deltaTime;
if (timerDisplay < 0)
Finally, we need to write a public function called DisplayDialog that our RubyController will call when Ruby interacts with the NPC frog. This function will show the dialog box and initialize the timeDisplay to the displayTime setup:
public void DisplayDialog()
timerDisplay = displayTime;
We can now open our RubyController script again, and replace the Debug.Log we wrote earlier for our raycast with this code:
if (hit.collider != null)
NonPlayerCharacter character = hit.collider.GetComponent<NonPlayerCharacter>();
if (character != null)
All we are doing here is checking if we have a hit, then trying to find a NonPlayerCharacter script on the object the raycast hit, and if that script exist on that object, we display the dialog.
Don’t forget to assign the Canvas child of Jambi in the “Dialog Box” setting in the NonPlayerCharacter script in the Inspector. Now try again to get Ruby to interact with Jambi by pressing X when she’s facing Jambi. This time, the dialog box will appear and then disappear after 4 seconds (or however long you set the displayTime value to in the Inspector).
In this lesson we’ve seen how raycasting is extremely useful in interactive applications because they allow you to detect things like colliders in a given direction.
As well as checking what is in front of a character, they have a lot of other uses, depending on the type of game you want to make. For example, to check whether there is anything between the main character’s position and an enemy position, you could set up a raycast between those two points and, if it returns a hit, then something is between them, so the enemy can’t see the main character.
In the next lesson we will add another crucial part to our interactive game: audio and sound.