Lesson 62 - Trigger Colliders and Causing Damage

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Alright, in this lesson we’ll want to randomly instantiate enemies when a trigger is reached. A trigger being pretty much just like any collider, but with a slight difference. We’ll want to set player and enemy interactions, specifically the part that has to do with dealing damage and possibly handle AddForce() a bit better. And we’ll want to keep track of score, awarding score based on damage done to the enemies and also have a difficulty level, which will determine the number of enemies that are instantiated when the trigger is activated. Alright, so we’ll start this lesson first by creating a couple couple of methods for the Enemies class, one will be a border hit check method, which will basically just check if the enemy has hit the borders of the screen and keep it within the bounds of the screen rather than fly off. And the other method will be destroy when out of bounds method, so if the enemy happens to somehow get out of bounds it will just destroy it. So let's add those methods to the Enemy class, the base Enemy class in Enemies.cs.

So you just add it anywhere, I’ll add it here. So it's a protected void, call it BorderHitCheck() we’ll want a float we’ll call force, to be passed in when we call this method. And we’ll basically use the speed of the enemy to calculate that force. So say force = force *speed. We can say *=, force *= speed. And then we’ll want a Vector3 for enemyPosition, which we’ll store based on the Camera.main.WorldToViewportPoint() method. We’ll pass in the transform.position for the enemy, and we’ll simply have a couple conditionals here to handle if it's at the edge of the screen. Then add the force if that is the case. So we’ll say if (enemyPosition.x < 0).="" so="" the="" left="" edge="" of="" the="" screen,="" we’ll="" say="" body.velocity="new" vector2().="" make="" sure="" it="" stops="" dead="" first,="" so="" we’ll="" say="" the="" velocity="" of="" 0="" on="" the="" x-axis,="" and="" then="" just="" pass="" in="" the="" existing="" y="" velocity="" for="" the="" y-axis.="" do="" the="" addforce()="" to="" push="" it="" back="" into="" play="" basically.="">

So AddForce() new Vector2. Get the force value that we determined above, and 0 for the Y. Not adding force to the y-axis. And we’ll say it's going in a positive direction. So to the right in other words. We’ll say else if (enemyPosition.x > 1), so the right hand side of the screen. We’ll say pretty much the same here. And kind of repeat this code, unfortunately. Try not to do that, but in this case we’re just going to repeat it, so we can set this value. So it's going negative direction to the left. As well as here. The force is going to be to the left. Alright, and now we’ll create the destroy when out of bounds method as well. So say protected void DestroyOutOfbounds() and I'll be pretty simple, basically just a single conditional say if (transform.position.y < -6)="" so="" 6="" units="" below="" the="" floor="" essentially,="" or="" actually="" six="" units="" below="" the="" center-point="" i="" think.="" can’t="" remember="" exactly,="" i="" think="" it's="" below="" the="" center-point,="" we’ll="" destroy="" the="" gameobject.="" so="" gameobject.destroy(),="" the="" gameobject="" or="" the="" property="" that="" refers="" to="" it,="" alright.="">

And now we’ll want to incorporate these methods into all the enemy classes, the different enemy classes. Some will maybe not use the BorderHitCheck() method and otherwise they might also get different force values applied. That's why I passed in this parameter passed for that. So let's just call these now in the individual enemy classes. Starting with Bouncer. So after MovementPattern() or whatever, put it right here: BorderHitCheck() with a force of 50 and we'll call the DestroyOutOfBounds(). Just copy this so I can paste it in for the rest of the enemies. And for the Gigantor, it will be just have the DestroyOutOfBounds() method. Because for this one, it's going to be moving really slowly, so we don't really care if it kind of veers off the edge and takes up a lot of space. So it's not that important for this enemy type.

Same thing for the ghost, because it can pretty much move freely anywhere. So just the DestroyOutOfBounds() method. For the Lush, BorderHitCheck() with a value of 20. And for torque, it’ll be BorderHitCheck() with a value of 80. And for tweaker, it will be BorderHitCheck with 50 is well. Alright, so now we’ll also want a world manager script which will have the score and the difficulty level set in, so go to Scripts’ World folder. And create a WorldManager script. And before I forget, might as well attach it to the Main Camera . So for this, we’ll have a public statically-accessible Level int the for Level. We’ll also have a property for the difficulty level, which we’ll see why in a moment. So we’ll say the backing field, private static int _difficulty. And then the public static int Difficulty. And we’ll just simply get the level that we have, so return Level / 3. So with every three increments of our Level that we’re at , we’ll have basically one more difficulty level, which will result in one more enemy being spawned at the trigger, which we’ll deal with shortly.

And so in Awake(), we’ll say void Awake() set the Level to start off with at 3, so we get at least one enemy to start off with. And we might as now set this now, we’ll have the target frame rate at sixty, which again we can reduce that for testing. And we’ll also want to have the V-Sync at 0. Now when we finally build this project, you're going to want to set the V-Sync to 1, enabled in other words, and that's just to make sure that there is no weird anomalies like screen tearing with the sprites or jitteriness. So make sure to do that, which I think will do in the last lesson coming up here. So say Application.targetFrameRate = 60. And QualitySettings, for now, the vSyncCount for this to work in the editor. So we can see this one testing set to 0. I got a little comment here. So you remember.

I don't even need Update(), and that’s it for the WorldManager for now. Alright, and now in EnemiesFactory, we’ll create a trigger collider on Awake(). So we’ll create it in code, you can also do it in the inspector by setting a collider as a trigger. So trigger colliders are the same as any kind of collider, except that it basically has one difference. It recognizes a collision, a collision event, between two collidable objects, once when they enter each other. So basically it triggers, it sets a flag when they intersect. Whereas, if it's not a trigger, there is basically a continuous collision event being read over and over again as those colliders are colliding, right? Or interacting in other words. So let's set our trigger collider right now in the EnemiesFactory script.

So this is actually going to be moved to another method, actually we’ll put it into the OnTriggerEnter2d() method, which is pretty much the analog of the OnCollisionEnter2D() method, but for particularly for a trigger. So we'll just say, and make sure the spelling is exactly as this, so OnTriggerEnter2D(), we'll say Collider2D. With the argument, input parameter, in other words, defined as other for another collider. Alright, now for the actual Awake() method that we have now, we’ll just set up the trigger collider. BoxCollider2D, the local name of collider. We’ll assign to it GameObject.AddComponent() method, give it a BoxCollide2D. And, of course we want to set it as a trigger, so we’ll access the collider through its local name. So, isTrigger is a bool, and we’ll say it's true. And we’ll set the collider values, the offset and size by saying, collider.offset = new Vector2 with an offset of 0 and 5. And then collider.size = new Vector2, 0.5f for x and 9.5f for y.

So that will result in a collider or that's kind of in the middle of the screen, kind of in the middle extending from the floor all the way to the top, so you can't avoid it once you reach the middle of the screen. Alright, so we’ll want to protect the player, the CheeseHead GameObject when it enters this collider, so we’ll want to tag CheeseHead as being on the Player layer for the collision layer, or sorry tag layer, I should say. I'm going to just one temporarily move this code, so we can have a clean starting point here. We're going to want to re-insert this later. So this could put it here and then later on copy and paste it. And it will fix this. Alright, so set up now our OnTriggerEnter2D() method, so when a trigger is achieved, when our player enters the trigger, we're going to need to check that. So if the other collider, this parameter that we’ve input, when the event occurs. if(other.gameObject.tag == “Player”) then and only then do we actually set off the sequence. So we’ll say destroy the collider so we can't re-collide with it again once we exit it and enter it back in.

And the Destroy() method, we’ll just pass in GetComponent() to get the Collider2D. It’s on the floor GameObject, once again, there is no existing collider, so it is going to be the one that we're creating from the Awake() method here. So get the Collider2D and destroy it. We’ll increase the level, so every time we create a new floor GameObject, the level will go up. Which, again, will result in more enemies over time. So we’ll say WorldManager.Level++. So now we’ll instantiate the enemies based on the level by creating a for loop, and we’ll go through that level number, and each time it goes through that loop we’ll instantiate a new random enemy. So we can handle it like this: for (int i = 0; i < worldmanager.difficulty;="" i++).="" this="" will="" be="" the="" for="" loop="" that="" does="" all="" the="" magic.="" and="" we’ll="" want="" to="" start="" with="" an="" int="" randominstance="" variable,="" that’ll="" just="" be="" a="" random.range().="" 0="" to="" 5="" basically,="" exclusive="" of="" the="" second="" input="" which="" is="" 6.="" it's="" kind="" of="" curious="" that="" they="" have="" it="" that="" way,="" i="" always="" get="" confused="" still="" to="" this="" day.="">

So 0 to 5 for 5 enemies, so any one of them we’ll say float randomX. So for the position, we’ll randomize that as well. So we’ll say the transform.position of the floor GameObject and plus or minus six units. So basically around the edges of the screen so I'll say. 6 time Random.Range() and we’ll do this -1 or 1 multiplier, that bit of code we've been doing quite a bit of. And also going to want to wrap that around, just to make a little readable. And we’ll say the randomY = Random.Range() 48 on the y-axis. Alright, so randomInstance is going to have a random number from 0 to 5, so we could do is we could have a set of if/then clauses and sort of check the number, and if it's 1 do this, if it's 2, do that, and so on so forth. But for this we're going to use something called a switch() statement, which is very similar to an if statement/ I'll mention how they're different in moment, but it's a little bit better used in this circumstance.

So say switch(randomInstance) We’ll need those curly braces within the scope of the switch statement. case 0, so if it's 0, that's the the number for randomInstance through this iteration of the for loop. Put a colon after that, and we’ll just put a comment for now, we’ll say “Gigantor”, so we’ll just remember to put the Gigantor’s information there. Just showing you right now how the switch statement works. Now we need to use the break keyword to break out of the switch statement. That's necessary or else it’ll just keep going. Case 1, we’ll say Tweaker, break. Case 2, Lush, break. Case 3, Bouncer, Break. Case 4, Toque, break. Case 5 will be the last enemy we have, Ghost, and one final break. Alright, so that's a switch statement. In its simplest usage, which is basically how we’re using it here, it’s basically an ordinary conditional that checks for different possibilities or cases for a single variable or operand, right?

So if this were instead a set of if statements, it would read something like “If randomInstance is equivalent to… else if randomInstance is equivalent to… else if randomInstance is equivalent to” so on and so forth. That's just a lot of redundancy and repetition, so in this case using a switch statement to check for multiple cases for the single operand is just a little bit more easier and more logical and easier to read. So now we’ll want to have the instantiating code inserted here for each of these cases. So we have the Gigantor firs. giantGeorge. Take that, and we have the Tweaker. Take that. Good old LushyLinda, can’ forget about LushyLinda. And Bouncer. TorqyTom, and GhostlyGail. And we’ll no longer need this little temp method I created just so that there is no red squiggly lines or error messages while we were waiting to use that bit of code. And for the positions, we're testing before just at the middle the screen basically. So now what we can do is we can use the randomize variables that we created here. So the new Vector3 will be positioned at randomX. So through each iteration, it’s given different for each enemy. randomY, and pass in 1 for Z value. I'll just copy and paste these throughout here.

And that’s that. Alright now, back to the Unity editor. Go to Project Settings > Physics 2D. We’ll make sure that the CheeseHead and Eyeball can interact. That's important. And we no longer want that fist there in front of our CheeseHead, that was just for seeing that in the editor. So we’ll just change the position of the CheeseHead fist to start off with, his normal position, which is 0 on the x-axis. And I want us to change some properties of the Stomper as well. The box collider will have these values, we’ll want give it a RigidBody2D. And so it doesn't affect our CheeseHead, push him around or or whatnot, make sure the Stomper is kinematic. I’ll set the interpolate and freeze rotation for Z.

Alright, back in code we're going to want to properly set up collision events now between the enemies and the player, as well as deal damage and have hit points and so on. So let's do that first by going to our Enemy class. The base class yet again, and we’ll have a hit points integer variable or field that's going to be inherited by each enemy type, and have its own hit points value given. So say protected int HP.. And for each enemy starting with Bouncer here, in the Start() method we’ll set its hit points. So HP = 1. So most of them will have 1, I’ll just copy this and paste in the other Start() methods for the other enemies. Ghost is going to have 1. Gigantor is going to take a little bit more to take down, so we’ll give it 3 hit points. Three hits it’ll take. And for Lush, it’ll be 1. For Torque it will be 2. And for Tweaker, it will will be 1 as well. Now, let's set how the enemies take damage by giving each enemy a DoDamage() method. Again in the base class, because they all have the same DoDamage() method, just the amount of damage that we do might be different.

So we’ll say, so this will be a publicly-accessible method. So it's a public void DoDamage(), pass in a damageAmount when it's called, for example when our fist interacts with an enemy. Pass in the amount of damage we're doing. And we’ll simply take away some hit points, so HP -= damageAmount, assigned to the hit points. And we’ll also say if (HP <= 0)="" we’ll="" destroy="" this="" enemy="" through="" the="" gameobject="" property.="" reference.="" now.="" when="" we="" attack="" an="" enemy,="" we’ll="" want="" to="" sort="" of="" push="" it="" away="" a="" bit="" and="" do="" the="" damage="" and="" also="" award="" us="" some="" points.="" so="" in="" the="" worldmanager,="" we’ll="" want="" to="" set="" a="" public="" static="" score.="" so="" public="" static="" int="" score.="">

And so let's get this all to work by starting out in the AttackController, in the OnCollisionEnter2D() method, we’ll want to modify this a bit. So let's take this out, we’ll start again. So grab the enemy coll parameter passed in here, we’ll say coll.gameObject.GetComponent() get the RigidBody component, and immediately assign it to a local RigidBody2D variable called Enemy. We’ll zero the the enemy’s velocity so that it doesn't just add force to the existing X or Y velocity. So this will make it a more consistent pushback when we collide with our fist, or otherwise collides with our enemy. So say new Vector2, and zero it out. Then we’ll apply the AddForce(), enemy.AddForce() new Vector2(). And we’ll do what we had before with the PlayerState.Instance.DirectionFacing * AttackForce for X. And then AttackForce for Y. We’ll want to ForceMode2D.Impulse again. Need another parentheses there. And we’ll also do the damage through the enemy’s DoDamage() method. So enemy.GetComponent, get the attached script component. This is kind of interesting the way we're doing this, so pay close attention to this, we’ll grab the enemy’ script component and select the DoDamage(). So with this attack method, we’ll do two points of damage. So the fist will do more damage than the projectile. Make sure the projectile does less damage. And then we’ll add to our score. So WorldManage.Score += 200.

Alright, can you take a guess as to why I said that this is interesting, the way that we're grabbing the enemy script component? Well, it's because we're using polymorphism here, right? We’re treating the enemy in particular in question as just a base class enemy. We don't really care about the inherited version of it, we just want to get the enemy and run Its DoDamage() method. That's all we want to do. So, this is a bit of a good example of how polymorphism can be quite useful. Otherwise, it would be a little bit difficult to try to figure out exactly what kind of script component is attached to the enemy GameObject. Is it a Ghost, is it a Bouncer, is it a Torque? You know we don't know. We don't care though, all we want is just to run its DoDamage() method. Just treat it just as an enemy, they all have the DoDamage() method. So that's polymorphism right there. Alright, now turning to the StompController. We don't even have to access DoDamage(), because going to the most powerful attack in the game. So you know it's automatically destroying all enemies, it’s going to be a kind of a difficult attack to achieve, a bit risky.

Alright, we’ll just change it up a bit so that we zero out our cheesehead before it pops up into the air. So again, this is more consistent. So say RigidBody2D, we’ll get the cheeseHead’s RigidBody. GetComponent(). We’ll say cheeseHead.velocity = new Vector2. For X, pass in the cheeseHead.velocity.x, and 0 at the Y. So we don't have a variable amount when we do the AddForce(). So it just pops up in the air in a consistent amount when we stomp on a enemy. We zero it out, and then we apply the AddForce(). cheeseHead.AddForce(), new Vector2(), 0 for X and apply an AddForce() of 5 and we’ll say ForceMode2D.Impulse. Continue playing the audio source, and also give us a score bonus. Since it’s a difficult attack, we’ll get a little bit extra. So 300 for this attack.

Right, now in the ProjectileController, our other attack, we’ll do similar to what we did before. So say RigidBody2D get the enemy reference. The RigidBody2D actually, and we’ll zero our its velocity, then do the AddForce(). We’ll say that enemy.AddForce() a new Vector2() convert to a float. The PlayerState.Instance.DirectionFacing times 11, and then fourteen on the y-axis. And then ForceMode2D.Impulse. And we also want to do damage, so enemy.GetComponent(), that polymorphism again. The Enemy component script, and run DoDamage() with a damage value of 1 this time. So the projectile, being at a distance, just is a little bit less damaging. A little more abusable I guess you can say. And WorldManager.Score will get a lower score. Because of its ease of use, so we’ll say 125 points.

Alright, one final thing we’ll want to do is also set what happens when an enemy collides with a player, essentially killing the player, which will basically amount to disabling our ability to move or attack as well as knock him out of the scene. So that will be easily done. Go to the Enemy base class, since this will work the same with all enemies, doesn't matter which one. We’ll have a void OnCollisionEnter2D(), make sure spell it right. Collision2D coll, so we’ll say coll.gameObject.GetComponent(), we’ll grab the PlayerController, we’ll disable it. So enabled = false, and coll.gameObject.GetComponent(), get the RigidBody. So we can apply the AddForce(). New Vector2() 0 for X, 300 for Y. And then we'll say coll.gameObject.GetComponent() Collider2D, the Collider2D, make sure it's disabled. So enabled = false. We’ll also want to to remove the the fist and the stomper and such. So the easiest way do that is just to iterate through them and delete them all with a simple foreach loop. So foreach(Transform child in coll.gameObject.transform), we’ll destroy the child.gameObject.

Alright, so that should be it. Let's now run and test it out. First just make sure the collision in fact does basically kill our object and the trigger works. So we hit the enemy and we bounce out of the scene. Let's now try to destroy the enemy, first with a projectile. OK, that good works. Now with our fist, try to do this. Not going to work with the ghost, we haven't set that up properly yet. Alright, so let's do it again, this time checking if the fist is working correctly. Yep! Alright, that's it for this lesson. And next lesson we’ll finally look at how to create platforms, being a platformer it's kind of important right? See you there.

Related Articles in this Tutorial:

Lesson 1 - Who This Course is For

Lesson 2 - What to Expect from this Course

Lesson 3 - Installation and Getting Started

Lesson 4 - Starting the First Project

Lesson 5 - Prototype Workflow

Lesson 6 - Basic Code Review

Lesson 7 - Game Loop Primer

Lesson 8 - Prototyping Continued

Lesson 9 - C# Fundamentals and Hello World

Lesson 10 - Variables and Operations

Lesson 11 - Variables and Operations Continued

Lesson 12 - Floats, Bools and Casting

Lesson 13 - If Statement Conditionals

Lesson 14 - If Statements Continued

Lesson 15 - Complex Evaluations and States

Lesson 16 - Code Syntax vs. Style

Lesson 17 - Variable Scope

Lesson 18 - Object-Oriented Programming Intro

Lesson 19 - OOP, Access Modifiers, Instantiation

Lesson 20 - Object Containment and Method Returns

Lesson 21 - "Has-A" Object Containment

Lesson 22 - "Is-A" Inheritance Containment

Lesson 23 - Static Fields and Methods

Lesson 24 - Method Inputs and Returns

Lesson 25 - Reference vs. Value Types

Lesson 26 - Introduction to Polymorphism

Lesson 27 - Navigating the Unity API

Lesson 28 - Applying What You've Learned and Refactoring

Lesson 29 - Constructors, Local Variables in the Update Method

Lesson 30 - Collecting Collectibles, Items and Powerups

Lesson 31 - Spawning and Managing Prefab Powerups

Lesson 32 - Implementing Powerup State Logic

Lesson 33 - Displaying Text, OnGUI, Method Overloading

Lesson 34 - Referencing Instantiated GameObjects, Parenting

Lesson 35 - Understanding the Lerp Method

Lesson 36 - Creating Pseudo Animations in Code

Lesson 37 - Understanding Generic Classes and Methods

Lesson 38 - Animations Using SpriteSheets and Animator

Lesson 39 - Working with Arrays and Loops

Lesson 40 - Debugging Unity Projects with Visual Studio

Lesson 41 - Camera Movement and LateUpdate

Lesson 42 - Playing Audio Clips

Lesson 43 - Routing Audio, Mixers and Effects

Lesson 44 - Adding Scoring Mechanics and Enhancements

Lesson 45 - Scene Loading and Game Over Manager

Lesson 46 - Understanding Properties

Lesson 47 - Controller Mapping and Input Manager

Lesson 48 - Understanding Enums

Lesson 49 - Dealing with Null References

Lesson 50 - Handling Variable Framerates with time.DeltaTime

Lesson 51 - Preparing the Project for Final Build

Lesson 52 - Final Build and Project Settings

Lesson 53 - Introduction to the Unity Physics Engine

Lesson 54 - Understanding FixedUpdate vs. Update

Lesson 55 - Movement Using Physics

Lesson 56 - Attack Script and Collision Events with OnCollisionEnter2D

Lesson 57 - Projectiles and Stomping Attack

Lesson 58 - Parallax Background and Scrolling Camera

Lesson 59 - Infinitely Tiling Background Sprites

Lesson 60 - OOP Enemy Classes

Lesson 61 - OOP Enemy Classes Continued

Lesson 62 - Trigger Colliders and Causing Damage

Lesson 63 - Multi-Dimensional Arrays and Procedural Platforms

Lesson 64 - Finishing Touches

Lesson 65 - Series Wrap


Please login or register to add a comment