World Interactions - Projectile
Tutorial
Beginner
1 Hour
Overview
Summary
In the previous lesson we added a broken robot that was hostile toward Ruby, but we still have no way to fix them. In this lesson we will build on everything we have learned to make our character throw a cog to fix the broken robot.
Language
English
Recommended Unity Versions
2018.3
Tutorial
World Interactions - Projectile
1.
Creating the projectile
To create our projectile, we will use the sprite in the Art/Sprites/VFX folder called CogBullet. To make our cog smaller, select it in the Project window, set the Pixels Per Unit to 300 in the Inspector and click Apply. You can play around with that value if you want to make the projectile smaller or bigger.
Because the cog will be moving and might hit something, we need to give it a BoxCollider2D and a Rigidbody2D to detect any collisions. This time, the default Box Collider that is the full size of the sprite will work fine as we want all of our sprite to collide with things. Don’t forget to set the Gravity Scale of the Rigidbody2D to 0.
Now, in the scripts folder, let’s create a new script called Projectile. Instead of moving the projectile ourselves, like we did for the main character or enemies, this time we will use the physics system. By giving a force to a Rigidbody, the physics system moves the RigidBody for us based on the force, and we don’t have to change its position in the Update function manually.
First, we need to create a Rigidbody2d variable and store our Rigidbody in it in the Start function, like we did previously.
Rigidbody2D rigidbody2d; void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); }
Then create a function called Launch that takes a Vector2 direction as a parameter, and a float force. We’ll use these to move the Rigidbody: the higher the force, the faster it goes.
The function simply calls AddForce on the Rigidbody, with the force being the direction multiplied by the force. When that force is added, the physics engine will move our projectile every frame based on that force and direction.
public void Launch(Vector2 direction, float force) { rigidbody2d.AddForce(direction * force); }
Because we want to detect collision, we will need an OnCollisionEnter function. For now, we’ll just Destroy the object, but we’ll change this to fix the robot later on:
void OnCollisionEnter2D(Collision2D other) { //we also add a debug log to know what the projectile touch Debug.Log("Projectile Collision with " + other.gameObject); Destroy(gameObject); }
Add that script to your projectile, and make a prefab out of the projectile. When your projectile is a prefab, you can delete the one in the Scene, because we don’t want a projectile by default in the Scene.

2.
Launching the projectile
Now that we have a prefab of our projectile, we need to throw it so we can fix those robots.
Let’s start by creating a public variable in our RubyController script that we will call projectilePrefab. We’ll make it a GameObject type because that’s what prefabs are; GameObjects saved as assets. Because we’ve made it public, it appears in our Editor as a slot where we can assign any GameObject. Drag and drop the projectile prefab into that slot. Remember that if you did that in the Scene, it will be an override to your prefab (with a blue line next to the entry), so use the Overrides drop-down in the top right corner to apply them to the prefab.
Next, write a Launch function in the RubyController script that will be called when we want to launch a projectile (such as when a keyboard key is pressed).
void Launch() { GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position + Vector2.up * 0.5f, Quaternion.identity); Projectile projectile = projectileObject.GetComponent<Projectile>(); projectile.Launch(lookDirection, 300); animator.SetTrigger("Launch"); }
This uses a Unity function that we haven’t seen until now: Instantiate. Instantiate takes an object as the first parameter and creates a copy at the position in the 2nd parameter, with the rotation in the 3rd parameter.
In this case, the object we copy is our prefab, and we place it at the position of our Rigidbody (but offset it a bit upward so the object is near Ruby’s hands not her feet) and with a rotation of Quaternion.identity. Quaternions are mathematical operators that can express rotation, but all we need to remember here is that Quaternion.identity means “no rotation”.
Then we get the Projectile script from that new object and call the Launch function we wrote earlier, with the direction being where our character looks and the force 300. The force value is very high because it is expressed in Newton units. This value is a good value for our game, but if you want to play with the strength, you can expose a public float variable and use that as the force.
Also you can see that we Set a trigger for our animator (see previous lesson for how it all work). This will make our animator play the launching animation!
The last part is to detect when the player presses a key, and call Launch when they do. Add this to the end of the Update function in the RubyController script:
if(Input.GetKeyDown(KeyCode.C)) { Launch(); }
We use the Input class we saw earlier, but this time we use GetKeyDown, which allows us to test a specific keyboard key press, here the key “C”. That will only work on a keyboard. If you want to make it work across devices, you can use Input.GetButtonDown with an axis name, like we did for movement, and define which button that axis corresponds to in the Input settings (Edit > Project Settings > Input). For an example, take a look at the Axes > Fire1.
Now press play and then C to launch a cog. You should either see no cog or a cog briefly appearing then disappearing immediately. And our console has 2 error lines:
  • A null reference exception
  • A log telling us the cog collided with...Ruby.
If you double click on the null reference exception error, it will open your Projectile script at the line rigidbody2d.AddForce(direction * force), which means our rigidbody2d variable is empty (contains null), despite us getting the Rigidbody in Start. That’s because Unity doesn’t run Start when you create the object, but on the next frame. So when we call Launch on our projectile, we just Instantiate and don’t call Start, so our rigidbody2d is still empty.
To fix that, rename the void Start() function in the Projectile script to void Awake(). Contrary to Start, Awake is called immediately when the object is created (when Instantiate is called), so rigidbody2d is properly initialized before calling Launch.
Now for our second problem: our projectile colliding with Ruby. This is because we create it on Ruby, so as soon as it’s created, the physics system lets us know that the projectile Rigidbody is colliding with Ruby collider. And we call Destroy in our OnCollisitonEnter function, so it gets destroyed immediately.
We could test to see if the object collides with Ruby and then don’t destroy the projectile if it does. But we’ll still have the problem that when it collides, it will stop moving immediately.

3.
Layers and collisions
The right way to fix this is to use Layers. Layers allow you to group GameObjects together so they can be filtered. In our case, we will make a Character layer to put our Ruby GameObject in, and a Projectile layer to put all our projectiles in. Then we can tell our physics system that the Character and Projectile layers can’t collide, so it will ignore all collision between objects in those layers.
To see which layer a GameObject is in, click the Layers drop-down in the top right corner of the Inspector. All objects start in the Default layer (layer number 0). Your game can have up to 32 different layers.
If you click on it, a popup will show some built-in layer already defined by Unity, but we want to create our own, so choose Add Layer. This will open the Layer manager.
Layers 0 to 7 are locked by Unity and you can’t change them. So in Layer 8 enter Character and in Layer 9 enter Projectile.
Now open your Ruby prefab and change its Layer to Character. Save the prefab and do the same to the Projectile prefab, but this time setting its layer to Projectile.
Then open Edit > Project Settings > Physics 2D and look to the Layer Collision Matrix the part at the bottom, to see which layers collide with which:

By default, all are ticked, so all layers collide with every other layers, but we want to uncheck the intersection between the Character row and Projectile column, so those two layer don’t collide anymore.
Now you can enter Play Mode, and your cog won’t collide with the Ruby anymore, but it still collides with other objects like boxes or enemies.

4.
Fixing the Robot
For now, our projectile just destroys itself on collision. But the goal is to fix those aggressive broken robots with it.
The first step is to write a function in the Enemy script to fix the robot and handle how the robots react when they are fixed.
  • Add a bool variable called broken and initialize it to true, so that our robot starts broken.
  • At the beginning of the Update function, add a test to see if the robot is not broken, and exit the function if it’s the case (when fixed, our robot will stop moving, so exiting the update function early will stop the code that moves them to be executed):
void Update() { //remember ! inverse the test, so if broken is true !broken will be false and return won’t be executed. if(!broken) { return; }
  • And to finish we write the function that can be called to fix the robot, which will be very simple:
//Public because we want to call it from elsewhere like the projectile script public void Fix() { broken = false; rigidbody2D.simulated = false; }
We just set broken to false and the simulated property of the rigidbody2d to false. This removes the Rigidbody from the physics system simulation, so it won’t be taken into account by the system for collision, and the fixed robot won’t stop the projectile anymore or be able to hurt the main character.
Now all left to do is to modify our OnCollisionEnter2D function from our Projectile script. We try to get an EnemyController from the object the projectile collided with, and if the object get one, we fix that enemy.
void OnCollisionEnter2D(Collision2D other) { EnemyController e = other.collider.GetComponent<EnemyController>(); if (e != null) { e.Fix(); } Destroy(gameObject); }
Note that we also removed the Debug.log too because we don’t need it anymore.
Now our projectile will fix the robot who will stop moving. You can then go against them and won’t be damaged.

5.
Cleanup
One minor issue with our current solution is that, if we get Ruby to throw our cog and it doesn’t collide with anything, the cog will keep on going outside of the screen for as long as the game runs. As the game progress, this could cause a performance problem if suddenly we have 500 cogs moving outside of the view.
To fix this, all we need to do simply check the cog’s distance from the center of the world and, if it is far enough away from Ruby that’s she’ll never going to reach it (let’s say 1000 for our game), then we destroy the cog.
Let’s add this Update function to our Projectile script:
void Update() { if(transform.position.magnitude > 1000.0f) { Destroy(gameObject); } }
Remember that position can be seen as a vector from the center of our world to where our object is, and magnitude is the length of that vector. So the magnitude of the position is the distance to the center.
Of course there are others way to handle this, depending on the game. For example, we could get the distance between the character and the cog (with the function Vector3.Distance(a,b) to compute the distance between the position a and position b). Or, we could use a timer in the Projectile script, so when the cog is launched you set your timer to something like 4 seconds, decrement it in the Update function and then destroy the cog when the timer reaches 0.

6.
Optional : Animating the fixed robot
This step is optional as it won’t add features to the game and isn’t related to the projectile, but it will make the game prettier. We will add the animation for the fixed robot. Please refer to the previous lesson for detail on how to create animation and use the Animator.
  • Create a new animation clip for the enemy (using the frames 15, 16, 17 and 18 for the Mr Clockwork) called RobotFixed. Remember to set the Sample to 4.
  • In the Enemy Animator, create a transition from your running Blend Tree to that new animation. Don’t forget to disable Has Exit Time. There’s no need to do a transition the other way around because, once fixed, our robot will stay fixed.
  • Create a parameter of type Trigger called Fixed, and set it as the condition for the transition:
Now in your Enemy script, in the Fix function, just add the line:
animator.SetTrigger("Fixed");
Enter Play mode and launch your cog on a robot to fix it. It should now happily dance!

7.
Conclusion
This lesson dived deeper into the physic system with how layers are used by the physic system, or how force on rigidbodies work so that the physic system move objects for us.
In the next lesson, we will finally make the world bigger by allowing our camera to move and follow our player around.