Hey guys, welcome back to the Waveshooter Tutorial Series, and we are at tutorial number 10. Nice job guys, you made it this far. We did set up a camera shake in an older tutorial, but what happened is I don't think it looks good. It works well, it just doesn't look good enough, and I have a simple solution to make it look better than what we had it look before. So we'll be doing that, and then we'll be refactoring the enemy system, which is going to allow us to make more enemies and just build upon the base enemy system really easily.
But... let's get into it. So we're going to be going into our camera 2D script.
And the first thing I want to change is this zoom in intensity. I think I set it to like 0.015 as my intensity. And that was a good value. I found that worked well.
I also don't like what I did right here, where I just set, I snapped back the position to 320 by 180. Because let's say the screen is shaking and it drifted off to a certain point on the screen. It's going to instantly snap back to that specific point. And it just doesn't look.
as good as I would want it to. So we're going to cut this line out with control X. And then I'm going to do, I'm going to go up here.
Let me add a line, the scroll over enter. Okay. I'm going to do an else. So if the screen is not shaking anymore, we want to do global position equals lerp. I guess I didn't have to cut it.
I'll just say where global position comma vector two, three 20 by one 80. That's the position we're moving to. at 30% speed. So now we are interpolating our position back to the base position that we were using before. So I think it's going to look a lot better when we have it this way, but we're not done yet because I have to go into the enemy script and we're going to update it.
And I think I found 80 as a good value at like 0.2, at 0.2 time. Okay, so now we can run it. We're going to run our arena.
You can see right off the bat, the screen shake system looks a lot nicer. And I... I like this this a lot more I don't know it just looks nicer than what we had before and you could change these values if you want to go all the way up to 120 or like 0.3 for like 0.3 seconds see how that looks but I think 80 at 0.2 is gonna be one of the best values yeah it just has too much extra shake because at 0.3 so if I set that back to 0.2 let's see how that looks but it's kind of just a matter of fine-tuning and figuring out what values work for you yeah I like that um so refactoring the screen shake system is this was an easy way of doing it i've there's a discord user um on our discord server link in the description if you're interested in joining they messaged me and they they they were showing me their screen shake system and they were using tween which tween is just like the lerp but tween actually has more customizable parameters and they can overshoot values and have different curves so it's a bit more advanced than lerp but it does work really well okay so i think that's going to be it for our screen shake system rework But now let's jump into the enemy system. So let's open enemy.gd and open our enemy scene.
So a lot of our enemies are going to be the same. They're usually going to have a lot of the same behavior. When we get hit by a bullet, we want it to hit turn white, we want to knock back, we want it to lose HP, and we want to get stunned. All this.
So we don't want to be making enemies and copying and pasting code across because if we have to refactor the knockback script, let's say if there was a problem in the knockback trip and we have multiple enemy duplicates we would have to change that in every single enemy script and that is that is not a good way of doing it and it's just a lot of work doing it that way so we're going to be using something called scene inheritance that godot uses that will inherit the same scene like this this entire same tree every enemy will have this same same tree and they can add more scenes into this tree, but they'll have this same base tree. Before we set up scene inheritance, what we're going to do is I'm going to make a new script down here in the file system. I'm going to right click new script.
We're going to call this enemy core. And instead of inherits node, we're going to do inherits sprite or whatever you're using for your enemy. We're using sprites in this case.
A lot of tutorials actually use kinematic bodies, but we're not using any physics detection. So we're just using. simple base sprites. So make sure it's inheriting sprite and we can hit create.
So now locate your enemy core script, double click it, and we're going to be copying and pasting lots of these base stuff from this enemy script back to this enemy core script. So speed, velocity, stun, HP, blood particles, this can all go in our enemy core script. I'll talk a little bit about setting these properties without having to change the script. So we're going to save that. And now we're going to get some errors in our enemy script because these variables no longer exist in this current script.
But what we can do, remove this extend sprite, grab the enemy core script within the file system, drag and drop it. So now you see it says extends enemy core.gd. So it's no longer extending to its base sprite class. It's extending to a script that we have built and And this script goes back and inherits from the space sprite class.
So we're still inheriting from a sprite, but we're also inheriting from our core script right here, which is allowing us to use these same variables from this core script back at our enemy. Now, this is a really nice way of doing it, because if we want to add in our variable, we just add it to the script. We don't have to copy and paste it if we have multiple enemy scripts.
So that's that's one of the great ways of setting up scene inheritance. I guess this. It's more just like inheriting a script, but it works really well. I don't want to copy and paste the movement that we have right here, because the thing is that you might have a different movement system for different enemies. You might have ones that, I don't know, dash towards a player or something.
Every enemy, we might not want to use the same move script. We might want enemies to dash towards a player or have different movement systems. So...
Simply copying and pasting this into the enemy core process script might have some problems with other enemies if you're trying to have a different movement system So the best way I see of doing this is we're gonna still cut it and we're gonna put in our enemy core system And in this system, we're just gonna be setting up a function. We're gonna call it basic movements towards Player and we're a paste this code in saying it wants the Delta variable because we don't have the same delta variable we have from our process event. So if I wanted, usually the process event has a delta variable that you can grab and use right here. Problem is this function that we set up our custom function does not have a delta variable.
So the best way I found of doing this is actually setting a delta argument like that. And then when we call this function, we'll actually give it our delta value. So now the enemy can choose, okay, if we want some basic enemy movement that we're we are going to be using for this enemy we can copy this function paste it right here and just give it our delta variable and we could save that and it's grabbing this function and still moving towards the player with all of this code without having to copy and paste it because if we wanted to change our base movement system if we copied and pasted it enemy to enemy it's going to become a problem of changing it that's why we're using this basic movement system towards the player and this function works really well for stuff like that.
Okay, now with HP, we are probably wanting the same thing with the HP camera screen shake. We might want to have a camera screen shake a different value between enemies. We can set that up as a variable.
So I'm just going to copy and paste that in. So we'll cut this and we'll paste that into a process function. and you could separate every single one of these into different functions but i think that kind of gets tedious because in every enemy you usually want them to die so you're probably going to have to be like check hp basic movement and basically you're going to have to put in every function but if you want have different enemies that don't have the same hp system then go for using a different function but for now we're going to be using the same movement system so we'll be using this one right here we're also not using our delta variable right here so We can just do underscore delta to tell Go.NET that we're not using the delta variable. It doesn't really matter, but I find just doing underscore delta can help improve performance.
Okay, so now we're going to go back to our enemy script. And let's see what else we want. We have our hitbox area entered where we knock back.
We see that we do have an arbitrary knockback value. So we can also set that as a variable. So we're going to cut this.
and we are going to paste this into our enemy core. So you might be worried that we're copying and pasting a signal into the enemy core, but since the enemy core is inherited from the enemy script, we've already connected the hitbox entered, so it should be going through the enemy core either way. And we do have a problem here too.
On our stun timeout, we set it as the original color, and we can easily fix that as well. We'll just paste that in to the enemy core, and we'll fix all this later. Stuntimer timeouts. And we can save all of our scripts. So we basically have an enemy core that holds all of our code that we had in our enemy, but now the enemy is using its basic movement towards player as its own code.
So we could save that and let's run it and make sure everything works and we haven't broken anything. And you can see it works! The enemies still move towards the player, they react the exact same way. And that's the nice thing about scene inheritance is if it was working in the original scene it usually is going to work in the inheritance. There you go.
We have awesome killing enemies, great stuff going. Let's go back to our enemy core and let's fix these arbitrary values and make them actual variables. But before we do that, I'm actually going to talk about exporting a variable.
When we export a variable, let's say, let's go to, let's do that on the HP right now. I'll just say exports integer var HP equals three. So what this basically does, is export. If I save the script and I un-fullscreen it, I go back to our player and you see there's an HP value now in our script variables category and it's saying HP equals three and we could set that in the editor without making any changes to the code. So this is nice if you have multiple scenes and they're using the same script but you want to have different variables and you don't want to hard code them in, you can just set them as an export variable and you can change those variables within the editor without making any changes.
But since we set it equal to 3, the default value in this category is going to equal 3. And we also have to specify our type, which in this case is an integer. That way when I hit up and down, it's not going to increase like a float. It's going to be whole numbers.
Because if I did a float, I save this, now it's going to allow me to do 3.1. If I go back to an integer, I cannot do 3. If I try to type in points, it's not 3.1, it won't work. It will just round down to 3. So.
That's the great thing about exporting variables is you can set these easily. Okay, so we're going to be doing the same for our speed. It's going to be an integer because we're not going to be using values in between. And yeah, we'll just do export int var speed equals 75. Now we can edit our speed and our HP within the editor. Now there's a couple more things that we're going to have problems with.
Our knockback, we probably want to have... We probably want to have that as a variable so we can edit our knockback. So we can try to set a value for our knockback and set it as an export. The problem with our current knockback system is it's using our speed variable velocity multiplied speed by multiplied by delta.
So if our character moves faster he's instantly going to be able to knock back faster no matter if we change this value or not. And we're actually going to fix this right now. We're going to cut this code of moving our global position with our velocity and speed and we're going to paste that right here. We're only going to do that if the player is not equal to null and we are not stunned.
As soon as we're stunned, global position plus equals velocity multiplied by delta. We do not want to incorporate speed in our knockback. And so that will instantly fix our knockback system where we can set this as a value and that won't be affected by the speed. Now our problem is with The new knockback system implementation, we're going to have to be using a different value than six because the enemies are going to move back like six pixels when we do that. So if I jump back into the game, let's see this.
We have some enemies spawn in here really soon. I'm hitting him and he's not really moving back. He's kind of just sitting there.
He is moving back just a little bit, moving back like six pixels. And to fix this, we're going to go back into our enemy core script and we're going to have to set this value a lot higher. So we're going to like do 60 let's see how that looks so this is knocked back at 60 still not really much you can still see that they're getting knocked back though so let's set it to like 6 000. let's try 600 let's see let's see how much knockback that will be you're saying 600 is pretty good value I was thinking that it's going to knock back a lot more, but that's like what we were having before. So we'll set that as our default knockback value, but we'll set it as a variable in the editor so we can change that as well.
So go back to our enemy core script. And instead of having this as an arbitrary value, we're going to go back to our variables and I'm going to set export integer var knockback. And I will set that to 600 by default. And then I can copy this knockback variable name and we can paste that in.
as our 600 but then we also have a problem with this arbitrary setting up our color value because that's only going to work for our red color so if i try to change the color to like so let's say we set it to a yellow color and then we save and run it the thing is it's going to turn back to red as soon as we as soon as he goes out of his knockback stage because he turns white and he has to turn back to his original color so you see how he's turning red which i mean that could be a cool idea that resets to a different color every time you hit them just like showing a different state but for right now that's not what we are planning on doing so we can exit if we go back into our enemy core to fix this i'm going to copy this hex code value so i can get back our enemy that's original color i'm going to select the enemy copy and paste the hex code value that way i have him back at red so instead of setting him back to this value let's create variable to start variable current so Color variable current color equals modulate. That gets compiled at compile time as soon as I hit that run button. And modulate isn't going to be set up yet. So we're going to get an error. Oh, modulate doesn't exist.
So on ready, make sure once everything is initialized, then we set our current color to modulate. And this is our modulate color. So now we can copy this current color and set modulate back to current color. And we can run back our arena script.
And let's make sure everything is working. But you should see it's working the exact same as last time. time it's just using more code so now if I set them to yellow I'm gonna copy my hex code value before I send to yellow so I'll make sure I don't lose this color value and I'll set it back to yellow I'm gonna save it you can run it and now I'm gonna be moving around enemies are yellow I hit him and he's still yellow his blood is pink we'll have to set it up so the blood will become the color of the your current color so I'm just paste my current modulate back But now it works with multiple colors and now we have our enemy core setup.
So you're probably thinking this is the exact same as what we had before. But now we set it up in a way where it's expandable. We still need to set the screen shake value though. So let's do that first. Export integer var screen shake.
We'll just set that to 120 by default. And we'll set this screen shake value right there. And you can run it and make sure everything works still. and you have enemies, correct color, same screen shape, everything works. So let's say we want to add multiple enemies now.
Let's get into the scene inheritance, because we don't want to keep having to set up the same scene tree with the hitbox, collision shape, and the stun timer, and have to connect that all up. I think that would be lame, having to set that up each time. So what we can do is we can do scene, click up here in scene, do new inherited scene, select your enemy.tsen, and we're going to hit open.
Now we have an... unsaved scene so you're gonna see these nodes in the scene tree are grayed out and that's because they are inherited of these values and it's saving the enemies scene right here and it has a little scene icon and this will take me back to the source scene that's inheriting from these are all being inherited from the current this current scene the enemy scene but I can still edit these values like let's say I wanted to change the collision layer well I could do that to this one it just won't affect the original enemy but if I go to the current enemy and I say okay I want the color of all the enemies to be yellow. I save that, I go back here, and now this one is yellow. Now I set it back to my current value and I save. Now this one is back at my current value.
So the thing with this system is if you have the first enemy and you make changes to the first enemy, it will make changes to all of them. So either you inherit from this one again, let's say with this one we're actually going to be using this one as the enemy one. We can just inherit back at enemy one.
That way we can change the color of this one. say we change that back to yellow we could save this one it'll want to save to its own scene that's okay we can hit enemy1.tscn hit save and this one's still going to be red because this one is its own kind of scene but it's just going to be inheriting from this base scene so now we can rename this enemy base and save this one and then with enemy1 i'm going to set it back to its original color value and then we can save here and that's basically that's how you can deal with setting up the original enemy without having to change all of the other inherited scenes. But we have our speed, we have our HP, we have our knockback, and we have our screen shake.
We can also set up our own scripts because right now we're using the old enemy script right here. And I'm actually going to rename this enemy script so we have a good understanding of what it is. I'm going to rename it to enemy simple. Let's call it enemy simple.
Rename. And it might say another resource is loaded, enemy simple, and it's going to have an extra script that's empty up here. That's because we renamed it and it's just seeing the old script still. So we hit close. That's just fine.
So now it's called enemy simple.gd. And so we could set up different scripts for each enemy. Now that we have enemy one, let's actually make another inherited scene. So go back to your base scene. Do new inherited scene and make sure you have selected the base scene and hit open.
We're gonna call this Enemy two let's make our second enemy and let's say we want to make him yellow like we had before We have our yellow enemy and it's enemy two. Let's say the yellow enemies are actually a lot faster. Let's go like 140 as their speed and we'll just set their hp like one and we can set knockback to like twice as much too like 1200 And screen shake we could set quite a bit lower we could set this to like 70. yeah we'll go we'll go 90. and then you hit ctrl s and you save we already made a new enemy we didn't have to change any code we just had to change a couple parameters and the script variables so if we wanted to have a different movement system we can't just change the enemy simple script because that would change this enemy script the enemy one because that's it using this same script so if we wanted to do a different movement system you'd have to clear the script right here add a new one enemy we can rename it i don't know enemy uh dash dot gd or something and then you'd go into enemy core you add your own function for the different movement system like basic dash movement and then you call that in this new script you'd have to make a new script and you call it in the process make sure it's inheriting and you do that all there.
For now, I'm just going to leave it as enemies simple.gd and leave that there. But if you want to have different movement systems, you just do it that way. But the only problem is we have new enemies. We're going to have to fix our blood particles.
The problem is we can't have it as the same color as the current enemies because it's going to be hard to see the enemies above it. So we'll have to figure out solutions for that. It should just be lowering the values. But for now, we'll just... deal with the normal blood particles.
But we're going to run into a problem. It's going to only spawn the original enemy, enemy one. We want to spawn enemy one, enemy two, and so forth.
So I found an easy way of doing this. We're going to be using our same export variable system, export array. And in this array, we are going to be using packed scene.
And so a pack scene is like one of these TSCN files right here. Like enemy2.tscn. That's a pack scene.
Then we're going to do variable. We're going to call this enemies. And we're going to save that.
So what this is going to do is if I go to my arena node 2D, it's going to say enemies. This is array size 0. I click on it. It expands this and I can set size to 1. Okay, we want one enemy to spawn. Or we want two different types of enemies to spawn. Then I can load a pack scene.
Enemy one it will load this one to the array now I can load another one or enemy two and I'll load our enemy two in the array for some reason the previews don't look Right enemy two should be yellow. I think it has us. I think it's a problem with seeing inheritance But it will be yellow. It's just gonna show up as red on the preview, but they're still not gonna spawn the enemies but we do have a Variable that has our enemies contained in it.
So we don't we no longer need to preload this because now we are going to be using this one. So when instancing a node, we want to pick a random enemy within our array. So to do this, before I change this, we're going to have to set up a little variable up above here.
We're going to call this variable enemy number. It's going to pick a number from what these scenes are because it numbers them zero to however your size is. So 0, 1, 2, 3, 4, and it'll go all the way down. And you can see our size is 2, even though it only goes up to 1. So the 0 index in our array is enemy 1. The 1 index in our array is enemy 2. So we can do this to randomize the enemies that respond.
So enemy number equals rand range. We're going to do 0, because that's our start, is the 0 index, to enemies. enemies.size minus one. We can't just do round range though because it'll give us values in between.
We want integer values. We don't want float values. So we'll have to do round random range and do the parentheses around this random range. The reason why we have to subtract one from our enemy size is here.
If I show you, it says enemy size is two, but our last index is one. So if we subtract that, our last index our enemy size will actually be one but it will align with our last index and we're going to be using this indexing system so if i do enemies and i put bracket enemy number So let's kind of break this down piece by piece. We set up an enemy number variable.
We do random range. We make sure we round this so it's integers. So we get zero or one in this case because we only have two enemies.
And when we're retrieving our index like right here it has to be within zero or one even though our size is two. Because if we write one in our index it will retrieve this scene which is the enemy two. If we write 0, it will get enemy 1. So that's why we have to do enemies.size minus 1. So it will align with our array values. This is a bit confusing, so I hope you guys kind of figured out how this works.
It's just doing array values and aligning them correctly and getting a random enemy from those index values. And this will basically return the enemy that we selected from our enemy number. So if I save this, now I run it. Enemies are gonna be spawned randomly now.
Oh, we got a yellow enemy. Shoot him. He knocks back quite a bit See if we get any red enemies.
Oh, there we go. We got red enemies, too I think I should make the yellow enemies a bit faster You don't seem fast enough and you can see it randomly selects the enemies and it all works Only problems we have right now is the game is way too difficult So if I go back into our arena script, I'm going to set the difficulty the wait time instead of minus equals 0.1 Let's just make it slowly ease in. I feel like it eases in too much.
So let's actually minus wait time by like 0.25. Let's try that. 0.025.
That way it slowly eases into more and more enemies. We got a yellow enemy. They still have red blood. We'll fix that next tutorial. Don't worry about it.
Spawning in a bit more rapidly. And I think the difficulty curve now is a lot better with having a lower value. And we have enemies spawn in randomly. And we have enemy inheritance.
So I think this is actually, this is really good. Because now you guys can actually add a lot more content really easily within this project. We'll fix the blood particles. It's going to have to, it's going to take a couple things with color.
Because we're going to have to make it a bit darker. So you can actually differentiate the enemies from the blood. And we'll also make the blood fade out as well.
So I hope you guys enjoyed this tutorial. And I'll see you guys in the next one.