Lesson 57 - Projectiles and Stomping Attack

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Right, continuing on from our previous lesson, we're going to now add to our AttackController script a projectile attack, as well as sort of a stomping attack that's kind of common to a lot of platformers. So getting right to it, we'll start by importing a new image. So here in our Characters, CheeseHead folder, we'll import the projectile asset. It's going to Tabors yet again, now this time projectile. And as usual, set that to Point and we're going to need a new GameOject that's parented to the Fist. We'll call it FistBack, so for the other Fist for this projectile attack, we'll add this GameObject. And we'll pretty much just reuse the previous asset for the Fist, but make it slightly different. So we'll make a SpriteRenderer component that's on the Player layer, as the other Fist. We'll drag the Fist there and we'll make it a little bit, so we'll kind of give it a bit of a shaded appearance. So for that, let's just set the color to sort of a darker orange and we'll offset it just slightly. So you can kind of see it there, I'll show you in the scene what it's going to look like.

That's what it's going to look like when we have a projectile attack, which you'll see a bit later. So we'll add a projectile state to the Attack enum definition. Projectile. And we'll also need to set an attack button for handling this in the Input Manager. Add a new axis. So call this Projectile, map it to the C key. Or alternatively the left click I suppose, or right click actually. And so we'll add this to the existing AttackController script. Well just like with the the main Fist, we're going to have the SpriteRenderer either enabled or disabled depending on whether or not we've initiated the attack. So we can just do that with the quick and simple ternary here. So we'll say if (PlayerState.Instance.Attack == Attack.Projectile) Then GetComponentsinChildren<>() and we'll grab the SpriteRenderer. And this actually grabs not just the SpriteRenderer in the child but also in the parent. So it's kind of a misnamed method, but nonetheless we'll use the indexer to grab the second SpriteRenderer, which would be the child SpriteRenderer in this case for the child GameObject.

That's one we'll want to grab. We'll say dot enabled. So if it's Projectile, we'll return SpriteRenderer. True, else we'll be false. And down here where the existing code is, we'll be able to make use of this and just simply add to this conditional. If the Attack state is Attack.Punch, or if the PlayerState is either Punch or Projectile, we can kick off this code block here. And then we can set up the initial conditions for the Projectile as it differs from the Punch attack. So here are the initial conditions for the Punch attack. Here we'll say else if (Input.GetButtonDown("Projectile") && PlayerState.Instance.Attack == Attack.Passive) and we'll only allow Projectiles if there are no other Projectiles currently in existence. So we'll also check to see if that's the case with GameObject.Find() and we'll see if the method returns a instance of Projectile(Clone) which we'll later on you know will be instantiated we actually throw out a Projectile. So this we'll check if it finds one or actually specifically if it returns null. In other words if it doesn't find one, then it's OK to throw out another Projectile.

Well you can pretty much take a lot of this code right here with some minor differences and say Attack.Projectile, the ending position's going to a little bit closer than the Punch position, so we'll multiply by that multiplier. And the pause will be greater too, it'll be double the time so it doesn't pretty much doesn't come out immediately. So have a pause of 20 for the out. And we're going to want to instantiate the Projectile based on a prefab that we're going to create later. So we're going to need a reference to that that we'll establish in the inspector through this public field. So public GameObject Projectile. And again, kind of reusing the previous code that we had for the punching action. Here when it reaches MaxPause, we'll check to see if if it's a Projectile attack and we'll create the instance accordingly. So we'll say if (PlayerState.Instance.Attack == Attack.Projectile) then we'll instantiate a Projectile. So GameObject.Instantiate() a Projectile, referring to that field that we'll set again in a moment. So yeah, we're going to want to create that prefab in the inspector.

Let's create the basis for that for with a new GameObject we'll put it on the Fist Collision Layer. We'll give it a SpriteRenderer that we'll put the Tabors on that. And the Player Layer, we'll put it to 1. So it's just in front of the back Fist, sort of but behind the main Fist. That's closest to us. And it's going to need a... Well, we'll need a Box Collider, so a Box Collider 2D and I figured that these values were appropriate for that. 0.4 and 0.38. So we'll set the transform position through a script once it's instantiated, so that's why I won't need to set the transform position in the inspector. So now we're going to want to also add a new script for controlling the Projectile, so call it ProjectileController. Alright, so for the ProjectileController, we'll just need a few fields, an int called Direction, an int for Speed and a float Timer. And in Start() we'll say Direction equals.. We'll take the PlayerState DirectionFacing to set the Direction. And again we'll need to cast this because it's an enum, so we'll cast it to an int.

And then we'll create the transform.position for this Projectile according to where the CheeseHead is, or actually where the Fist is to be more specific. So, transform.position will be assigned the result of GameObject.Find() we'll find the Fist GameObject and then take its transform.position. And then we'll just subtract a little bit, so it's just behind the Fist sort of. So we'll subtract a new Vector3() we'll subtract this amount on the X value and 0 and 0 for Y and Z respectively. This is possible, this mathematical calculation between a Vector3 and another Vector3 through something called operator overloading. It's a bit of an intermediate to advanced topic, so it's not really worth going into. It's a C# particular topic, but basically there is a way that you can assign this sort of ability to calculate based on what operator is used for calculation between two, you know class types. In this case two structs, both Vector3's. So that's why that's possible.

I don't really have to worry about that too much though. And we'll assign Speed to 12 and see how fast that is. It could be too fast or not fast enough, we'll test it out later. And then in Update() we'll say transform.position equals new.. This will actually create the motion for the Projectile we'll just want to move on the on the X axis. So it's a new Vector2. This is also a funny peculiarity, I'm putting a Vector2, assigning a Vector2 to a Vector3. That's also allowed well because implicitly the Vector3 allows for a truncated version of it. In this case a Vector2 that just ignores the Z value. So it's a little bit of a shorthand, I otherwise wouldn't recommend using the shorthand. But yeah, it works because it's code in there by the Unity programmers, so we'll use it. So transform.position.x + Time.deltaTime * Speed. So use that deltaTime so on every frame it increments differently depending on the framerate. So yeah this is not going to be moving on the physics due to updating through the FixedUpate() and physics engine, we'll just be changing its transform.

But nonetheless we have a physics Collider, so still we'll interact with the other physics objects. And we'll give the Direction as well the X plane and we need to get also a Y position. So we'll just give it the existing Y position and we'll also get a rotation, so the Projectile rotates as it's being thrown. So Rotate() we'll use the Rotate() method, a little bit easier I think. So Rotate(0, 6 * Direction * -1) because just to make sure it rotates in the direction that it's traveling in. It's kind of funny that it has to be represent this way, but that's how we do it. So also multiply it by Time.deltaTime * 60. So close to 1 if it's 60 frames per second yet again. And we'll also take the Timer we'll be using this for destroying the GameObject. Once it kind of goes offscreen if it doesn't hit anything. So we don't end up with a bunch of Projectiles piling up. So we'll first say Timer += Time.deltaTime * 60. Could have used another Timer class like we did the previous project, but in this case I don't really want to mess around with that, so we'll just use this kind of notation.

And if the (Timer > 120) 120 ticks, roughly two seconds. GameObject.Destroy() it will be this GameObject, which is referenced through this gameObject property. And also here for the Projectile, we'll want to set the collision, the code that initiates what happens upon collision with the OnCollisionEnter2D() method that we saw in the previous lesson. So void OnCollisionEnter2D(Collision2D coll) and it we'll be if (coll.gameObject.Tag == "Enemy") So it's, because it's on the same Collider layer as the Fist, it will by the same collision matrix specifications of colliding with Rnemies only. So we still need to check in the if it collides with the Enemy. And if it does, then GameObject.Destroy(gameObject) destroy the Projectile. And it, the object that it hits, referenced by the coll input pramaeter, coll.GameObject.GetComponent<Rigidbody2D>() we'll want to influence its Rigidbody like we did with the Fist, so grab the Rigidbody2D component, and then immediately stick the AddForce() method to that. We'll need a new Vector2() And what we'll set that up in a second. The second argument will be again ForceMode2D.Impulse, and for the Vector2 the AddForce() we'll add to the X value. A value of 11 multiplied by the PlayerState.. Actually, let's just take the Direction.

That's for the X value, and then on the Y we'll just give it a value of 14. We don't worry about Direction. Alright, now let's go back to the to the inspector and run this and see how that works. So it's mapped to the C key. I never did assign in the inspector, we have to make a prefab of the Projectile then assign it to the to the AttackController. So let's create a folder for Prefabs call it Projectile, and then make that into a prefab by just dragging that there so we can get rid of this temporary GameObject. And here in the CheeseHead GameObject, we're going to.. Sorry, in the Fist GameObject, we're going to have a public Projectile field that we need to assign to this Projectile. Alright, we'll run this, and it's mapped to the C key. We'll see how that works. Pretty good, maybe it's a little too strong of an impact to our Enemy but we can always tweak that later, but it's working. So that's good. And I just noticed that, depending on if we're facing left or right, the Projectile instantiates a little bit ahead of the GameObject, or the Fist ,or behind it a bit. So what we can do is we can just multiply this by Direction, and that should fix it for us. Alright, great, now to move on to the stomper kind of attack.

We'll be a little bit easier to make this happen. So for that we'll create a new GameObject parented to our CheeseHead called Stomper, and it's going to have a attack layer, or a Collision layer of its own. So call it Stomper, and we'll have it in the collision matrix, we'll set the collision between a Stomper and the Eyeball is the only allowable condition with a stomper. And it's going a Box Collider with these values that I figured out. 0.23, .29 and .08, go into the Scenes folder so that's going to be our little Stomper hit box basically. And we'll also want a script to manage what happens when it collides with our Enemy. So call it StompController. Just to keep these scripts organized, we'll put in our CheeseHead folder here. Excuse me, that was the wrong place. Put it in our Player folder and Scripts in the Player's sub-folder for Scripts for their assets. And now we'll establish what's inside of this StompController with the following code. All we'll need is actually just an OnCollisionEnter2D() method, so we'll just establish that here with void OnCollisionEnter2D(). We'll perform this check as we've done before with if (coll.gameObject.Tage == "Enemy") then GameObject.Destroy() Coll.gameObject. Sorry, coll.gameObject.Tag. And we'll apply this force like we did before with GetComponentInParent() get the Rigidbody2D and apply AddForce() to it.

We'll put in the Vector2 add a second new Vector2() and give it the ForceMode of ForceMode.Impulse. And here we'll just say, just give it an upwards force of 5. So this we'll actually make our... This will actually make our CheeseHead character bounce up in the air, it'll add force to that CheeseHead character and destroy the Enemy. So that's why it's written like that with GetComponentInParent() So let's test this out. Awesome, it works great. Alright, so we're about to end this lesson, so what I want to do now is just add some audio for all these attack components that we've established. So let's add some audio to our assets here. Create an Audio folder, and we'll import the following assets. Yes and we'll reference all of these in code in their respective places. So for, so here we'll just run the GetComponent() grab the AudioSource.

Actually, because we have two, we're going to want to return an array. So GetComponents() and grab the first one in the index for the WithPunch sound effect and then just run directly through this after we grab it. The Play() method available to that AudioSource and I'll do the same thing but grab the other AudioSource for the Projectile sound. That's it that's all we do differently there. And for the jumping sound effect, we'll go to PlayerController, and where we have a jumping code... Once the character has actually jumped, once the AddForce() has been applied, we'll do GetComponent(), AudioSource.Play() That'll handle that. And for the stomping sound effect, just go to StompController. And the same thing, we'll just do the same thing right there. Alright, we'll test this out. The game. Alright, that's it for this lesson. In the next lesson, we'll add some background elements and parallax scrolling. It'll be a lot of fun. 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