Now that we have a better understanding of how our game produces this sense of a changing and moving game world, let's get back to where we left off with coding our little game that we were prototyping and make it more game-like, rather than just an infinite test of patience, which it was like when we left it off. That way, we will have a much more solid foundation to work with when we move deeper into the C# and coding principles in the coming videos. Let's open up our projects here. I thought maybe a good idea at this point would be to add challenge by spawning enemies over time. Going to our code here, let's go to our sphere controller, and let's add some code. This time, let's add a timer so that every so often we spawn a new instance of our Sphere enemy.
Let's add some code. Add this code. Initialize that value to 1. Now we are going to want to...Or we could put it anywhere in the update. Let's put it here and write a little comment. Let's make it every five hundred ticks on the timer value. Let's write a conditional, if (Timer % 500 ==0). Now, modulo, or modulus, depending on how you prefer to call it, is an operator that refers to when you divide a value if there is a remainder. This conditional right now is saying that if our Timer, when divided by 500, leaves a remainder of 0...In other words 500, 1,000, 1,500, and so on, then we are going to want to spawn, which we won't write just yet, a new instance of our enemy Sphere. Then we are going to want to increment in the update because it updates running in a loop, remember?
We are going to want to increment Timer, so on every run-through of the Update() method, it increments to the next highest value. On the first update run-through, it will be 1. On the second, it will be 2, and so on. Remember, if we are running on sixty frames-per-second, as we were discussing in a prior video, this timer value, we would expect to increase by about sixty every second or so, or something like that. That means every whatever it is, five seconds or less than that, actually, we will spawn a new enemy. Timer++. Plus, plus is just the easier way to increment a value by one. That is all that is. You can just say, "Timer = Timer + 1", and it gets the same result. They both mean the same thing, but Timer++ is easy to do, to write, and you will see it all the time in coding.
Save that. We won't run it yet because... Okay, I can run it and show you that the timer does, in fact, increment. We made it a public variable timer. Go to Sphere. Let me close all these things, or minimize them. Don't really need them up right now, except for our script here. We see timer has now been made public in our code. It is publicly visible. It is visible in the inspector. When I run the game, you will see that value does, in fact, increment. There, you see that? Right about five hundred, we would want to spawn a new enemy. Let's write that in. It's quite easy to do. Go back to the top of your code here and let's put in a vector three. I am kind of keeping these variables separate from the ones at the top with that return white space. I am just doing that for the sake of being more easier to look at right now. You don't have to include that space or not. It is just a stylistic choice right now, so it is easier on the eyes. RandomSpawn. We don't need to... That is fine.
Down here in our Timer conditional, we will spawn a random instance of the sphere by writing the following code. We are just going to specify the range in which we are going to allow our enemy sphere to spawn in. We don't want to let them spawn just anywhere in a game world. We want to contain it within this block, within our viewable space. That is why it is -6.2, and 6.2F for the X value. Much the same for the Y value. I can't even remember what it was. Was it 3.6? I will still leave it at that. 3.4F. We don't really care about the Z value, but we are going to initialize it anyways to the transform.position.Z value of this current sphere game object. Transform, that is. We will actually instantiate, create an instance of another sphere enemy by writing in the following. I am really screwing that one up, aren't I?
How about I use IntelliSense? Write 'this', which is a prefix you can add. Not necessary in this context, but just so it is clear when you are looking at it that I am referring to this game object. I will show you what I mean in a second. RandomSpawn, which is going to be the Vector3 position, the random position, in our game world. Just a default rotation, which will be the same rotation as this current game object right now, which is going to be 0, because we don't really care about rotation. This particular method requires three arguments, so that is why we are putting in rotation. Don't worry about methods right now. We will actually be going into it in depth in the next few videos, and arguments, or anything like that. I am just mentioning that really quickly. When I say, "GameObject.Instantiate()" and then write this stuff in, this.gameObject, which could just be "gameObject." Just so it is clear for you, this.gameObject means we are instantiating an instance of the GameObject that this SphereController script is attached to. It is not some other GameObject. It is this one, right here. Another instance of that is going to be instantiated.
Let's run this right now, and let's see if that is , in fact, what happens, keeping an eye on the Timer there. I probably should have made it a little less than 500 for testing, but oh, well. We are around 500, and there we get another Sphere enemy. Now I have two of them hunting me down. Pretty good, but you know what? It is going to get pretty... Pretty soon, it is going to get hard to keep up, so I think a good idea now is to give my Player 1 their [inaudible 00:08:33]. No surprise there. It is going to be a pretty short game, I think, if enemies are multiplying every less than five seconds. Maybe to counterbalance that, we can think about giving our Player 1 character some sort of special ability. I think maybe a good idea, and a very simple thing to do, is to allow our Player One cube to teleport, so it can maybe get out of a tight jam, or something like that. I will show you how we can do that quite simply with some code here.
Let's go back to the CubeController script. Again, you can enter this pretty much anywhere. No, we should enter it at the top. Okay, so we should enter this at the top of the Update() method. I will just write a comment and write a conditional, if (Input.GetKeyDown( KeyCode(Space) I will assign it to the space bar because I just feel like it. I think it is a good place for it. We are going to teleport. I haven't set the variable for that yet. Let's set up at the top of our script in a moment, but right now, let's just say, Teleport = 2f; Else... This is kind of ugly code. I am not going to deny it, but remember, we are not going to be too self-conscious about that right now. We are just quickly hammering stuff out and seeing what it does in our game so we can move quickly.
Here, I have to add that variable. float Teleport. Now, what I am going to do is, if the space bar is struck, we will take that value and add it to our current conditional. If we are currently moving in a particular direction, add that value to where we are moving, to F, in this case. I should stop saying F. F is just a shorthand, or it just implies float. It is something that is necessary in Unity. That is why I am saying 2f. It is just a particular thing about Unity. It requires a post-fix of F to floats, in most cases. That is it. You will add two to that value. Let's go to our game and press play and see if that works. Yep. Teleporting. Doesn't look very good, and we can definitely try to fix that up at another time, but perhaps a bigger concern to me at this moment is the fact that I can infinitely teleport, and that might be too much of an overpower in this game. It is a little too OP, as they say. Oh, yeah. I am down with the lingo. Okay, let's not allow it to be so easy to teleport.
Maybe what we can do is solve that problem by setting up a counter that is like a cooldown, so that when you hit space bar and you add that value and you teleport, it then sets off a cool-down counter and you can't do it again until that counter reaches 0 or something like that. Let's just quickly add that into our code. Go back to our CubeController here. Let's go back to our conditional here. In the case that we hit space bar, let's put a conditional around the teleport value. "If"... We haven't set up this variable, either, but we will. "If (CoolCounter == 0) if it is cooled all the way down, then it is all good, "Teleport=2F." We are allowed to teleport. We will reset the CoolCounter. We will have it have a cooldown of 100 ticks. Underneath here, we will just decrement with, just for consistency's sake, CoolCounter-- This will prevent it from going below zero. If (CoolCounter < 0) then CoolCounter = 0. This is a bad habit, me writing these variables that haven't been defined yet. It is easier with IntelliSense. You will come to learn that later. I will show you that in later videos. Let's actually make CoolCounter a public int CoolCounter, and that should all be okay. Let's save that.
Go back to our game. Go to our cube. Let me minimize this. Get it out of the way because we are only concerned about our controller script. Because we made CoolCounter a public int, you will see it just like we saw our other counter for the Sphere. We will hit play, and we will see how the CoolCounter, the cooldown counter, influences our game. There. I can't abuse the teleport mechanism until the counter reaches 0 and resets back to 100 after I have struck the key. That is one more improvement to our game. What I can do now, actually, is... I should probably still not be able to spam that value, so we will make the counter 500, so it starts at 500 and I can't use it again until it reaches 0. That will give us a little bit more space until we can use the Teleport mechanic. Now that our game is starting to take a lot more shape, I want to get rid of these placeholders of cube and sphere and so on. I think we have outgrown them and their use. Let's go to our project and let's get rid of everything that we don't need.
Basically, all we need is the script and the transform. Let's get rid of the mesh filter, box collider, and mesh renderer. Just right-click and hit "Remove Component". Let's do the same with our Sphere. Get rid of that. We don't have those objects anymore. We have the game object, but we don't have any visual representation of them anymore. What we are going to do is, I created some of my own images, my sprite images, as assets. We are going to import into the game now and use in the place of our rudimentary little cube and sphere objects. In order to do that, you can download these from the lesson on the webpage for LearnVisualStudio.NET. Once you downloaded them, go to your "Assets" pane here. Right-click and select "Import New Asset".
I have them in my pictures directory here. We are going to want to import, one at a time, import each one. "Cheesehead", "Eyeball". I bet you can bet where those are going to go. "Icesurface". We have three visual sprite image assets right here. Let's put the Cheesehead asset as a sprite on our cube by selecting Cube. Left-click "Add Component". In "Rendering", select "Sprite Renderer". Simply click and drag your Cheesehead sprite to where it says sprite. There you go. You see our little Cheesehead. It is a little too small now, I think, so instead of .5 scale, let's just put it back to one for X and Y for our scale. Actually, before we move forward, double-click Cheesehead. Just click it once, actually, I think. Select "Point" instead of "Bilinear". We don't want a filter. This is going to look kind of like an old, retro, 2D pixely appearance, so we don't want it to be filtered at all. We just want it to be pixely. Select "Point". We don't need MIT maps. That is pretty good. Just hit "Apply", and we're good with that. Let's do the same with Sphere. "Add Component". "Rendering". "Sprite Renderer". Now that we have added that component, we can click and drag and put our Eyeball...This is now our Sphere enemy. We are going to have eyeballs as enemies now. Totally logical, I know. That is good with that.
The background now is definitely lacking, so let's create a new GameObject for that and name it "Background". Do the very same, "Sprite Renderer". I almost forgot. Before I move forward, I will put that there, but now I have to remember to also click on our Eyeball and also set that to "Point". "Apply". Same thing with our Ice Surface. There you go. Everything is all set up, but, as you noticed, the Cheesehead character, or Player One now, and the Eyeball, are behind the Ice Surface. To show you that is actually where they are, I will disable the sprite renderer, and there you go. They are underneath the background game object. What do we do to fix this? Remember how I told you about how the Z value of the transform position could be used to layer things in front of each other? You could do that, but I am going to discourage you from doing that for the most part because there is a much better way, much cleaner way to do that, by using something called "Sorting Layers" in Unity.
What we are going to do is create sorting layers for these different game objects. Click here on background, for example, where it says "Sorting Layer". "Add Sorting Layer". Type in for this, "Background". Let's create our other sorting layers. You can do it here, as well, because they are shared amongst the game objects. Type in, "Enemy" our enemy layer and then "Player1". This is a little bit counter-intuitive. The way this is structured right now is whatever is on our background layer is going to be behind our enemy, and of course, behind our player. Now, with working with other kinds of applications out there like Photoshop or video editing tools, you may think that should be the other way around, that the background layer should be at the bottom.
It is just a peculiarity with Unity, but this is how you have to do it. Player1, in this instance, also will be in front of our enemy. If we took the enemy and put it there, then our enemy would always be in front of our Player1. For whatever reason, I feel that this is the best way of doing it, having our Player1 always on top of everything else. Now we have to assign that sorting layer to our objects, so here at the Sphere, we will choose "Enemy", and don't worry about ordering layer right now. That is if you have a bunch of different game objects on the same layer, and you need to determine which ones go in front of the other game objects that are on the same layer. We don't have other game objects sharing layers, though, right now, so we don't have to worry about that. Go to the cube, select Player1 for cube to be on that layer, and then the background. Just select "Background". We are going to save our scene. "Save Project", as usual. Get in the habit of that, and that is about it.
While that about concludes our intro portion of getting our feet wet with the general workflow of the Unity editor and writing script components in order to gradually build up a game, we can't really go that much further at this point without a better understanding of fundamental coding concepts and C# fundamentals, in particular. What we are going to do right now is put our little game we are building here on pause, and in the next block of videos, dedicate ourselves to learning the language details that we will need to know moving forward. Fear not, however. We will be coming back to this game and continue to flesh it out by applying the new programming concepts we learn as we are learning them. I know you may or may not be curious to see how this game pans out, so don't worry. I won't leave you hanging with that. By the end of our next block of videos, we will have something resembling something close to a complete game. One other thing worth mentioning is not to get too used to doing things the way we have been doing them so far, and the way we will be continuing to do them with this prototype game project.
I am specifically referring to the fact that we are not yet using things like the Unity physics engine for this game. Now, it may sound complicated, but actually, using the physics engine to manage stuff like movement and colliders makes a lot of otherwise complex functionality much easier. Believe me, no prior knowledge of calculus or any fancy math necessary. I promise. The physics engine Unity is quite easy to work with. I don't want to expose you to that right away with this game because,
A) It is a very simple game, and I thought it would be more instructive to roll our own solutions from scratch, and
B) It is easier to explain and understand the mindset of making games when you start off by seeing it as stripped down as possible, as we have here.
It is not that one way of doing things is wrong and another is right, it is just that I want you to have a full sense of scope by the time you complete this course. Remember, we will later on in the course, create a much more dynamic game, as promised, a platformer, and that one will use the physics engine. I think you will be getting the best of both worlds by seeing how both of these game projects are going to be constructed.
Great job, if you have been following along. I encourage you to move along with me with your fingers on that keyboard, as a big part of learning to code is just training that muscle memory. That is about it for this video. Good job. You are doing well. I will see you in the next video. Thanks!
Lesson 8 - Prototyping Continued