2D Game Development – World Interactions: Projectile
Day 065 #100DaysOfCode
Day 019 #100DaysOfGameDev
Today I am going to learn how to implement throwing projectiles at enemy robots using the Unity physics system. By throwing a cog at an enemy robot, this will fix them and stop them from being hostile.
The first step is to create the Projectile script that will control the projectiles:
With that completed, we need to get Ruby to throw the projectile at the robot so she can fix it. Obviously, this requires modification to the RubyController script. The first part of this is to declare a new public GameObject called projectilePrefab which will allow us to add any saved Prefab asset directly into the script in Unity, which is why it was made public.
Now the projectile, CogBullet, can be dragged over to the Projectile Prefab section of the RubyController script component.
From here, Ruby needs a Launch function in her script which can be called any time we trigger a projectile launch of the CogBullet.
Which will be called in Update:
If the game is tested here there will be two problems: a Null Reference Exception in Projectile with the rigidbody2d.AddForce and a log entry saying the projectile collided with Ruby which causes it to stop or disappear.
The former is caused by Unity not running Start when you create an object, but on the next frame. When you call Launch on the projectile and just Instantiate it without calling Start, the Ridigbody2D variable is still empty, or null. The fix for this is to change the Start() function to Awake() in the Projectile script. Unlike in Start, Awake is called immediately when the object is created (when Instantiate is called), so Rigidbody2d is properly initialized before calling Launch.
To fix the projectile’s collision with Ruby, we need to use Layers. Layers allow you to group GameObjects together so they can be filtered. The goal is to make a Character layer to put Ruby in, and a Projectile layer to put the projectiles in. Then you can tell your Physics System that the Character and Projectile layers can’t collide, so it will ignore all collisions between objects in those layers.
A game in Unity can have up to 32 layers, the first 8 of which (layers 0 – 7) are system-locked and can’t be changed. We can make layer 8 the Character layer and layer 9 the Projectile layer so that we can create the separation we need between the GameObjects to keep them from colliding.
Now go into Edit > Project Settings > Physics 2D and change the Layer Collision Matrix so that the Character and Projectile layers no longer collide. Problem solved!
Now that projectiles fire properly, they still just collide with the robot and get destroyed. What we want is for the projectile cog to “fix” the robot instead. This will require some changes to, you guessed it, the EnemyController script.
First, we will declare a bool called broken to track if the robot is broken or not and initialize it to true, as we want to start with the robot already broken.
Next, add a test at the beginning of the Update function to see if the robot is not broken. If the robot is not broken, we want to return and prevent the code from running which makes the robot move. This is because we want it so that a “fixed” robot doesn’t move.
To round it all out, a function gets written that can be called to Fix the robot.
The line “rigidbody2D.simulated = false” removes the Rigidbody from the Physics System simulation and it will no longer be considered for collisions. The “fixed” robot will not move, and will no longer stop projectiles or be able to hurt the character for damage.
The final step in fixing the robots is to go into the Projectile script and, if there is a collision with a robot, call the Fix function that was just written in the EnemyController script. This is done by modifying the OnCollision2DEnter function:
A problem that arises with the current game setup is the fact that if a projectile doesn’t hit anything, it will keep going outside the screen for as long as the game runs. With one or two active cog projectiles this isn’t a problem, but if this builds up and there are hundreds of them then there could be some serious performance issues with the game.
A good fix for this is to check and see how far away each cog is from the center of the game and, if far enough away that can never be reached, destroy the cog. This can be written into the Projectile Update function:
There are other ways to accomplish this task too. You could get the distance from the character to determine when to destroy the projectile, or you could use a simple timer so that projectiles get destroyed after a set amount of time. All will work.
Noticing that the robot continues to walk in place after being fixed, I then set about changing his animation to something else to indicate he was, in fact, fixed. To do this I had to create a new animation for the robot called Fixed which would make him appear to dance in place. This was done by loading the individual sprites of the animation into the Animation window and saving Fixed.anim.
Next, I had to go into the Animator and make a transition from the Blend Tree into the Fixed animation. This only had to be one-way as once a robot is fixed it will not be returning to any walking animation. By adding a new property to the animation and calling it “Fixed“, the transition is given that condition. After that, the Fix function from the EnemyController script gets updated and now the robot will dance when he is hit by a cog projectile and fixed.
This is how it all came together: