TANKS! Unity Tutorial - Phase 7 of 8 - Game Managers

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Phase 7 - Managers. Okay, so in this particular section we're going to create the game manager and the idea is that by the end of it we will have rounds with tanks where you can drive around and fire and destroy another tank and at the end of it you have a UI that explains that you have won that round, and then after several rounds have been won that you've won the game. There is it turning red. And you get this text at the end, Player (number) wins the round! And you'll notice also that we've colored the UI to match the color of the tank. So we're going to start by creating the spawn points for each tank. So for that we just need a position reference in the world, so we're going to use an empty game object for that, it's very simple, we're just going to go to the hierarchy and we're going to choose Create Empty. I'm going to rename this one SpawnPoint1. Then I'm going to duplicate it. Control-D or Command-D depending on your platform, and call it SpawnPoint2. And I'll give you some positions for those. So SpawnPoint1 is (-3, 0, 30). With a rotation of (0, 180, 0). It's going to be a lot quicker to read it off the slider because I've got both spawn points on at once. So SpawnPoint1 position (-3, 0, 30) Rotation 180 in the Y axis, 0 in the other two. SpawnPoint2 (13, 0, -5) ensuring a rotation of SpawnPoint2 is (0, 0, 0). And naturally this is one of those things that is not going to make a huge difference. If you decide you want to move those spawn points somewhere else in your game feel free to go ahead and do that. Obviously you want to keep the Y axis positioned at 0 at all times because that's where you want the tanks to start. Because we're dealing with these spawn points which are effectively empty game objects, something that just has a transform component it's very difficult to see them in the scene. So you have to select the translate tool to be able to see their handles, so SpawnPoint1 is here, SpawnPoint2 is over here. Now there's a quick way to rectify that which is to select the SpawnPoint and give it what we call a gizmo. So a gizmo is a 2D representation of something in the scene view. So if you reselect SpawnPoint1. And then at the top of the inspector there's this little cube icon. If you click down you can choose from a number of gizmos. You can specify your own if you bring in a texture in Unity, you can set that as the gizmo, which is super handy. But for this we're just going to select a blue background. So that's going to then print the name SpawnPoint1 just like that. And then we'll do the same for SpawnPoint2 we're going to choose red. So that's going to match the color of our tanks that we're spawning as well, so SpawnPoint2 is red, SpawnPoint1 is blue. Okay, so that's where my two spawn points are. So we just, design-wise, decided to put them either side of a building so the players have to at least drive around to start shooting in the game. So that's gizmos. The next thing we need to do is create a screen space canvas. So when we first put in a UI element in to the game, a slider for the health, I mentioned that ordinarily our UI is over the entire screen. So that's what we're going to do now. So in the hierarchy I'm just going to choose the Create menu, I'm going to choose UI, and Canvas. And yet again it's going to appear huge, and it should look like that if you've done it right. And you can't really get it wrong because that's the default. Then what we're going to do is rename this MessageCanvas, so F2 on PC, or Return on Mac, just rename it MessageCanvas. Now we want to work with this canvas a little bit and add a text element that's going to be the basis of our messaging at the end of each round. So what I'm going to do is, first off I'm going to set the scene view to 2D mode. So at the top there's a little button here by the scene tab that says 2D, click on that. It's going to constrain to a particular axis. Then what I'm going to do is to click on my MessageCanvas and you can either hover over the scene view and press F on the keyboard to frame, or you can go to Edit - Frame Selected. Then you can just use your scroll wheel or gesture on your trackpad if that's what you use, to kind of frame that in the scene view. Then what we're going to do is actually add a text field, so I'm going to right-click on my MessageCanvas, so that I am immediately creating something under that parent object. And I'm going to choose UI Text What you should see then is you have something called New Text, it's a new text field, it's left aligned and it's kind of sat in the middle of our canvas. It should look like that. So basically with this thing we want it to be nice huge text and we want it to be stretched over most of the screen except for a border around the edge. So depending on what we put in to it we will use Best Fit on the text which will scale it if there's too much information on the screen, and will ultimately fit it in to this canvas. So on the Rect Transform for the text the anchors for X and Y should have a minimum of 0.1 and a maximum of 0.9. So it should look like this. So note that it's X, Y across, and then X, Y across again. So Min is the first two values, Max is the second, so 0.1, 0.1, 0.9, 0.9. Then what we're going to do is reset all of these values. So the anchors are basically where those edges start from, so if I put those all to 0 and I say that X and Y should have 0.1 it's going to take that as a proportion of the screen that we've got. So you should now see that your screen looks like this, it's got this border around the outside and our text is basically starting up in the upper left there. Okay, so we've made a new MessageCanvas, we've added a Text new game object to it. And on the rect transform the anchors for X and Y have a minimum of 0.1 and a maximum of 0.9, just creating this pad around the outside. Obviously it varies depending on width and height because of the aspect ratio being wider than it is tall. I'll reset all of those left, right, top, bottom to 0. So then we'll actually work with the text component. With that Text game object still selected we're going to scroll down to our Text component which is here. So obviously the main characteristic of text is what it actually says, so I'm going to write in TANKS! You can do that or I'm sure you can name the game something hilariously different. So feel free to do that. And then the font that we've included for you guys, which is a free for use font, is one called BowlbyOne-Regular. So next to the Font field just use the circle select to change from the default Arial to that one. And then because this entire block here is that text field we want to set the alignment under the Paragraph section to centre and middle. So that'll place it right in the centre of the screen. Then finally what we're going to do is enable Best Fit. And set the maximum size to 60, 60. And the color to White. So the font is BowlbyOne-Regular. I've enabled Best Fit which will give me a Min and Max size, I set the maximum to 60. And then I set my color to White just by dragging up in this color panel. It should look like this if you've got it right. Okay, so this might be tricky to see so what we're going to do is just add a drop shadow to it, nice and simple. And for that we have a separate component. So if you click the Add Component button and just type in Shadow that will jump to that very simply and you can hit Return. And for the shadow we're going to set the color, so first off we're going to choose a kind of brown color to go with the sand. So that is 114, 71, 40, and the alpha remains the same, it should be 128. Then because that's only just poking out from just behind the tank's actual text I'm going to set the Effect Distance to -3 in X and Y. It should look like that. If you want to get artistic at this point, feel free. And finally, once we've designed that I'm just going to save my scene really quick and then I'm going to disable 2D mode because I don't want to look at this stuff any more I want to go back to actually messing with my levels so I'm going to uncheck 2D mode on the scene view and then I'm going to select my level art and I'm going to frame that and then zoom back in to where I was. So one more time, I was on my MessageCanvas in 2D mode, framed, and I'm just going to reselect my level art, hover, press F to frame, when not in 2D mode and zoom in. We've added out text component and we've set the text to say TANKS! or something otherwise hilarious. We have used circle select to choose our font. That font is BowlbyOne-Regular. It's a free font that we took from Google. Thank you Google. We've set the alignment to centre and middle. So that it sits right in the centre of where we've set it up. We've enabled Best fit. So basically Best Fit will allow that to scale up to the maximum size. So if we end up putting a lot of text in to this text field then it will be between 60 and 10, which is the default minimum if the amount of text forces it to be smaller those are the things it'll be between before it starts actually clipping over the edge of the rectangle. Then we set the color to White. And we added a shadow component to the text and we set the Effect color to be brown and (-3, -3) for the Distance. So that's back to the left and down a little. And then we disabled 2D mode to go back to the level itself. Then we need to get back to actually framing our tank and worrying about how the camera will behave when there's more than one tank. So what I'm going to do is select my CameraRig game object. Now what you'll notice with the camera rig is that we now have something missing. So before we dragged out single tank on and dropped it on to the targets array, the thing at the bottom here, this thing. Now it has a size of 1 because we've already populated that field, but this is missing because we've deleted our tank so it can't find it any more. But that's fine because we don't actually want this to be there at all, we want the script to handle it for us. So what I'm going to do is to go to my CameraControl script, I'm going to set my size back to 0, so it doesn't exist any more and then I'm going to double click on my CameraControl script to go back to editing it. So you may remember when we first came across this there was that HideInInspector bit at the top? And now comes the time to uncomment that bit so that this Targets field is no longer seen in the inspector. So we're not deleting that part, we're just removing the / and * either side of it. So it should look like this. So when you save your script and return to the editor you'll notice that the camera control no longer shows that bit. And compiled, there we go. Okay, so your Targets array should disappear it's still there, it's still accessible via the script, it's just not visible for you to drag and drop stuff on to, which is exactly what we needed. Then we are ready to actually create our game manager, so I'm going to save my scene and I'm going to use the hierarchy Create button to create an empty and I'm going to name it GameManager. So we're just using this as a thing that can host our GameManager script, we could really attach this to anything, we could attach it to the level art or something that we know is always going to be in the scene but for our purposes it just makes sense to have a dedicated game object we can select and go back and set everything up on. So our GameManager is going to be an independent object. So in the Scripts Managers folder you will find two files, one of them is the GameManager, one of them is the TankManager, and we'll come on to explain what the tank manager does shortly. For now I'm going to just grab my GameManager and drop it on to my GameManager object. So grab the GameManager script, drop it on to the GameManager object. So let's actually do things the other way round this time, we're going to populate these variables and then we'll look at the script and see how it works because there's a fair bit to it. So we have a number of rounds to win and as we said earlier there are 5 rounds to win. There's a start and end delay to each round which will allow the players to actually read the text that's on the screen. And then there is a reference to our CameraControl, so remember we said that this script would tell the camera where the tanks were and, instantiate them and whatnot. So it needs a reference, so that's the first thing we're going to setup. So the CameraRig has that cameraControl script attached to it, so this is expecting something of type Camera Control. So that component is attached to the camera rigs so if I just drag and drop my CameraRig on to that, then that will assign it. Then, under Message Canvas, I have my text game object. that's my Message Text, I'm going to drag and drop that on to assign it too. And finally my Tank prefab, let's zoom out a little, is in my Prefabs folder, so I'm going to select Prefabs, and I'm going to grab and drop my tank on there. Then you will notice conspicuously there is an array at the bottom called Tanks. Which if you expand has size. So as we know there are 2 tanks in our game. So the size needs to be 2 so there are 2 items in this array Player1 Tank, Player2 Tank. If you expand that once you've typed in to it and hit return you will see there's Element 0 and Element 1. So just a reminder, any array always starts at 0, so that's why you're seeing instead of 1 and 2, it's 0 and 1. So the two things that we actually need here are just the color that we want it to be, so this color will apply to the tank itself and it'll also apply to the name of the tank or the name of player1 and player2 in the UI itself. So this color will get used for both of those things. I'm going to setup red and blue for this. So Element 0, I'm going to click on the color block. So that big black square there, click on that to bring up the color picker and then the color I'm going to use there is (40, 100, 178). So shade of blue like that. You'll notice that this conspicuously matches the name tag gizmo that we gave Spawn Point 1. Funny that. So we will drag on SpawnPoint1 as the Spawn Point. Then Element 1 will have a different color. And that is a red which is 229, I'm going to recap these in a moment. (229, 46, 40). (229, 46, 40) R, G and B. And you guessed it, SpawnPoint2 is the spawn point to drop on. We've expanded our Tanks array so the GameManager will create these tanks and color them and also color the UI for us. We've assigned all of our references, like the text and the camera rig. And then the colors we've used are blue of (42, 100, 178). That's for SpawnPoint1. And then color of red (229, 46, 40). for SpawnPoint2, which should also be dragged on. Okay, so let's talk about our GameManager. The GameManager is in charge of the game, weirdly enough, the clue is in the name, but the way that that works is pretty much what you're seeing here, so it's in charge of initialising the game, it needs to spawn as many tanks as we've told it to, in this case 2. And it needs to setup the camera targets, so it needs to say 'hey camera control, these are the tanks that I've spawned and you need to focus in on them when the game starts'. Then it needs to run the states of the game. So we're going to look at the code in the GameManager shortly and you'll see that it's basically a simple sort of state machine that's running round starting what happens during round playing and what needs to be decided when the round ends. And all these things link in to the Tank Manager, which is a separate script. So the Tank Manager handles shooting and movement for the tanks, and also visual elements, I.E. the UI. So what you need to understand is that each tank gets assigned it's own tank manager, and that tank manager is then in charge of turning off input, control, and shooting. So every time that tank gets spawned in the world it doesn't know what it's got to do but it has it's tank manager there to sort it out. So obviously this game is extendable so we've put this kind of third one there to kind of mean 'and so on' as we populate that array with more tanks it can do more things. So shall we have a quick look at the TankManager script? Because that is the first one that we need to understand. Double click on the icons to open both those scripts. And make sure you're looking at the TankManager. So the TankManager is already complete and we'll go through that briefly now so you can understand what it's doing, what it's role is and how it interacts with the GameManager. Most of the scripts that we're dealing with today and in fact all of them other than this one are mono behaviours. So after it says public class and then the name of the class you've got a colon and then MonoBehaviour. CameraControl : MonoBehaviour. So what that means is that this script you can drop on to a game object and it will have functions that are called back such as Awake, Start, Update, Fixed Update, those sorts of things. That's all part of mono behaviour. But this one does not have that. It's not inheriting from anything before that. So everything that you see in TankManager it it's own thing. We've also go this attribute at the top, Serializable, so what that's doing is saying to Unity 'when you have an instance of this you can show it in the inspector'. Most of the time you don't need that because by default fields are serializable. But when you've got a class that you make yourself you need to mark it as serializable so that it'll show up in the inspector. Okay, so let's have a look at the public variables. So we already know the first two, we've got the color, which is the color the player is going to be when it's spawned, the tank and it's name. And we've got a transform representing the spawn point so that's obviously where and in what direction it's going to be spawned. So very crucially, what you're actually seeing here is the result of what you just filled in in the inspector. So in the inspector we have this thing called Tanks so each of these elements is effectively those two things, these are individual tank managers and those are the only things that are down in the inspector. That's because the Tanks array in the Game Manager is an array of tank managers. So it's showing those two because we've told it to but the rest we're saying 'hide those from the inspector'. so if I jump over to Game Manager really quickly. so there's an array of TankManagers so these classes called Tanks, so that's why you're seeing that in the inspector. Okay, so the rest of these public fields we've got HideInInspector to stop it from showing up because we don't want people to be able to adjust those. So first is the PlayerNumber, so that's going to filter through to the shooting Script and the Movement script to tell each of those scripts what input it needs, because you'll remember they're based on the PlayerNumber. Next we've got m_ColoredPlayerText. So you'll notice that when we showed that little video the text that came up was in the color of the player. So the way you do that is you use HTML-like rich text and we'll show you how to do that in a moment, but that's just a variable to store that. Next we've got a game object which is storing the instance of the tank. So if we need to turn anything on or off we need to get that referenced through the instance. And the number of wins that the tank currently has, so when it gets enough wins it'll win the game. Next we've got a few private references so we've got references to the TankMovement and TankShooting scripts so we can turn those on and off when we need to. And a reference to the Canvas game object so that we can turn the UI on and off. Next we've got a public function called Setup. So this is public because it's going to be called by the GameManager when it first creates these tanks. Seeing as the tank has been created it's going to find the references to the Movement and Shooting scripts by saying that the Instance, get component from the instance. So set the instance, get the components there. Next, a little bit more obtuse, we've got the Canvas game object so what it's going is it's got the instance, it's finding a component of type Canvas in it's children, because you'll remember all of the UI is underneath a canvas, so we're finding that canvas in the children, and then we're getting the game object that that's on. That's what that bit is doing there. Next we're setting the PlayerNumber on the Movement and Shooting scripts. And then there's the HTML-like rich text. So this looks a little bit scary and confusing, but basically what we're doing is taking bits of the text and then putting stuff in between them. So for example, if you saw in the video earlier we showed, at the end it says 'Player 1 - X amount of wins' 'Player 2 - X number of wins'. So what we're doing with this is we're just adding bits to a string. So this is a string here, in inverted commas, and then we add to it something that's converting a piece of code to a particular color by using this thing called ColorUtility ToHtmlStringRGB, very long winded thing but it basically takes in a color, and will then color whatever the text is after that, and the thing it's coloring is Player. So if you've seen HTML before you'll know that HTML tags fit inside angled brackets. So it starts here, it then says the color that I should color it is this thing that takes in the PlayerColor, so that will result in a color reference. Now we finish that tag and the piece of text its actually coloring is the word Player. Then we put in a space. Then we put in the PlayerNumber And then we finish coloring. So all of that stuff is saying 'Player 1 in particular color by using this rich text'. It looks a bit lengthy but once you piece through it it makes sense. And like I said, have a look at the completed scripts, it will have all the comments in there to tell you exactly what we're doing. So the next line is getting an array of mesh renderers. So mesh renderers are the things that actually show up your 3D objects in the scene. So the tank is made up of a series of mesh renderers, like one for the tracks, one for the chassis, one for the turret, etcetera. I'm just going to show that real quick. So I've just dragged in my prefab just to demonstrate but you'll see there's this mesh renderer component and if I toggle things on and off they disappear because they're not being rendered any more. That's just what a mesh renderer is doing. Okay we're making an array of mesh renderers called Renderers and we're setting that to all of the components in Children that are mesh renderers. So the instance, get the components in the children that are mesh renderers and return them. And then what we do is we loop through all of those renderers and set their material's color to the player's color. So all that's doing is it's finding all the mesh renderers, getting the color and changing it to the player color that we chose. Then we've got just a few more public functions that are going to be called by the Game Manager. We've got DisableControl which turns off the script and turns off the canvas and we've got EnableControl that turns on the script and turns on the canvas. Finally we've got Reset. So what Reset does is it sets the instance back to it's spawn point. It turns it off and then it turns it back on again and the reason it does that is that all of the tank's apart from the winner will be off, but we need to reset all of them so we need to turn everything off first before we can turn it back on again. Okay, so that's all there is to TankManager. Let's see about the GameManager. So just to jump back to this slide real quick. As we've said the Game Manager is using an array of TankManagers, so when we look at the GameManager in a second we're going to see that at the start it's going to use the TankManagers to spawn those. So those were the instances that you just saw referenced inside TankManager. And then we're going to get in to actually looping through things. So let's take a look at that. So if you switch over to the GameManager script you should see the stuff that we're looking at right now. Either that or you can look at the screen. So there are three blocks of comments in this one. There's one at lines 19 and 21. I'm just going to remove those. There is one at line 66 to line 74. And then there's a really long one which is line 108 all the way down to 152. Cool, so apologies, we had to comment those out otherwise you would have seen some warnings and queries in the console that kind of make it awkward for us to teach you stuff, so we just commented those out. Okay, so we've already gone over the public variables, I won't bore you by doing it again. The private variables, so we've got an integer which stores the current round number. So obviously as it counts up then you get to display what the current round number is when it starts. Then we've got these two WaitForSeconds things. So in coroutines you can put a delay in your function course. What is a coroutine James? I think we'll cover that shortly. Good idea. But basically these WaitForSeconds things are what a coroutine is looking for a delay so we've got a start delay and an end delay. We'll change those in to these WaitForSeconds classes so we can use them. So basically we have a little pause within a function, but they need to be converted to a type that is called WaitForSeconds and you'll see why shortly when we explain coroutines. Okay then the last two things we've got, two instances of TankManagers, so they're referring to specific tanks that are the RoundWinner and the GameWinner. And we'll use those for the message at the end of each round. So next we've got this start function, which is setting up those WaitForSeconds. So we've got a StartDelay and an EndDelay, which are just numbers in seconds and then we're creating new WaitForSeconds for the start and the end. Then we've got SpawnAllTanks function and SetCameraTargets function. We'll cover those now and then come back that StartCoroutine thing at the end. So SpawnAllTanks. All that's doing is it's going to loop through the TankManagers. For each TankManager it's going to set the instance belonging to that TankManager to an instantiated prefab, so we're calling Instantiate(m_TankPrefab, and then we're spawning it at the spawn point of the tanks. With the same rotation. So this may look confusing but it's basically a long line that we've moved on to a new line because it doesn't make any difference and it's easier to fit on a projector. So we're just instantiating a tank per instance as required. Okay, so now we've created the tank we want to set it's player number, and since this loop is going from 0 upwards that doesn't really work as nicely as starting from 1 so we're just saying that the PlayerNumber is i + 1, so it starts at 0 so the PlayerNumber would start at 1, and so on. Otherwise when you start the game you'd get 'Player 01, Player 1 failed', you don't want that. so you need to just add 1 in a minimum of Player1, Player2 and so on. And then finally for each tank we're going to call that Setup function which is the first function that we covered in TankManagers. Just a quick reminder, TankManagers - Setup. So it's the thing that's in charge of movement, shooting and coloring and whatnot. SetCameraTargets, we're creating an array of transforms called Targets and we're setting it to be the same length as the TankManagers array. Then we're going to loop through this Targets array and we're going to set each target equal to the TankManager's instances transform. So each tank's instance that transform. And then finally we can set the CameraControl's targets to this Targets array that we just created. so we're just looking at the TankManager and saying 'here's all the positions of the tanks you've just created, and we'll just assign them to the CameraControl script'. Okay, so the last thing that the Start function did was it did something called Start Coroutine and then it had a function within it's parameters, which is a bit weird. So let's find out what that's all about. So just to jump back to the slides, so we've just been talking about Start and what actually happens there, and we say that it starts the GameLoop to continue. So it's starting the GameLoop coroutines. But we haven't talked about these yet, we're talked about functions and how you can use those. We need to talk about coroutines. So the GameLoop is going to start the round. Obviously you're going to start the round, people are going to start playing, driving around. It's then going to wait. And then they're going to be playing, so this is when they're actually firing, shooting each other, running around,. Then it's going to wait. And then the round is going to end. Ordinarily in functions, when you run a function all the commands just go straight through and something happens or you'll loop through something but it's instantaneous. But often in programming we need to to kind of pause and do what we call Yield and wait for a certain number of seconds or wait for a certain condition to be valid. And that's where coroutines come in. So an ordinary function might look like this - void, a non-return type and then the name of the function () and then some commands within that. Whereas a coroutine has, first off you'll notice a different return type, instead of void we have this thing called IEnumerator. As if the word coroutine wasn't weird enough you now have something else to worry about, IEnumberator, but don't be afraid of that, it's just the return type. And you'll also notice within this we have the word yield in the middle of it. So what we can do is start some basic commands or anything that we want to happen straight away like any old function but then we can stop at this point called yield. At the point where you come in to contact with yield what happens is execution exits that function. It goes away. Then it waits for a certain period of time or some condition to be true. And then after that period of time or that condition it comes back in again at the same point that it yielded. And then it will continue on with the coroutine. So instead of just going straight through and doing everything instantly you put in a little pause in the middle of your function, which could be very useful. And it becomes really useful when you start putting things like While Loops in So what you can do is have a while loop like this with a yield instruction in the middle. So if you haven't heard of while loops, it's kind of like if you think of an if statement of checking a condition a while loop is just there to say 'whilst this thing is still true, then we're going to do what's in those brackets'. Okay, so in this coroutine the first part of the function would start normally, then it would hit the while loop, so let's suppose that the condition is true. It goes in to the while while loop and hits that yield. Then it leaves again, waits for some period of time, and then comes back in to the yield and continues with that while loop. Whilst within a loop we've exited and then come back in again after a period of time. So supposing that that condition is still true, we'll then go back in to the same while loop. So let's say for example, this might come up, we say that the condition for that while loop is while there is more than 1 tank left keep doing this loop. And then let's say that the yield was come back next frame. So then what we'd have is a function that wouldn't finish until there was only 1 tank left. So you'd say while there is not 1 tank left do this, come back next frame. Oh there's still not 1 tank left, come back next frame. Oh there's still not 1 tank left, come back next frame. Until there was, and then the function would finish. Could be quite useful. You remember the start function calls StartCoroutine? The coroutine that it's starting is the GameLoop. So let's have a quick look at the GameLoop. To see what's going on there. GameLoop is saying yield return StartCoroutine(RoundStarting()); So do you remember when it says yield it waits for whatever it's got to the right of it to finish before it continues on. So when it's saying yield return StartCoroutine it's waiting for that coroutine to finish before it goes on to the next one. So what's going to happen here is it's going to do RoundStarting, wait for RoundStarting to finish, then come back in, then it's going to do RoundPlaying, wait for RoundPlaying to finish, then it's going to come back etcetera for RoundEnding. Finally we're going to check if there is a GameWinner so if GameWinner is not equal to null. So if there is a GameWinner then load the current level, Application.LoadLevel loadedLevel, that's just reloading this current level. If there isn't a GameWinner then it's going to call StartCoroutine and notice that there is no yield return on that bit. So it's not going to wait and then come back in we're just going to call that then the function will finish and there will be another GameLoop running. So now we're getting to the point where we're looking at how the GameManager and TankManagers interplay. So this slide is going to kind of populate over time but the way that we're going to do this is we're going to animate in each point, we're going to talk about it and then we're going to fill in that part of the script, so hopefully we can kind of step you guys through it and it will make sense. So we've just talked about the GameLoop. So the GameLoop starts off with the RoundStarting. So the first thing that needs to happen in the RoundStarting is we need to reset all the tanks, so we need to set them up at their positions on the spawn points, we need to enable their controls and all these kind of things. So the way that we do that is to parse to the TankManager and 'hey, can you reset everything and reposition everything?' and that's what the TankManager does, so every tank remember has a TankManager assigned to it and we do that. We start off our GameLoop, we reset the tanks, and the TankManager's in charge of resetting things so De/Reactivating and setting the positions back where they should be. And we start off by disabling all the tank controls. And we have in the TankManager DisableControl() so we can't move, shoot and the UI is off. When the game starts you'll see Round 1 and the tanks are there but they don't have their Health UI just yet. Then we do three more things to start the round, we need to setup the camera position and the size, so in other words we take an average of the two tank's positions and we setup the zoom using the size. We increment the round number so it will, if it's 0 we make it Round 1. And then we setup the Message UI which will say either Round 1 or whatever is appropriate. So let's go and actually look at that in the script now. So currently RoundStarting just has this yield return m_StartWait so all it's going to do is wait for 3 seconds and then do nothing else. So what we want to do is put in a little bit of code before that. First off we said we wanted to reset all of the tanks. So before the yield return put a couple of lines so you've got some space and we've got a function called ResetAllTanks. So put a call to that. Next we'll put DisableTankControl because we don't want people to be able to control their tanks while the round is actually starting. We want to wait for the RoundPlaying for them to be able to control everything. Then you remember we wanted to make sure that the camera is set to that exact size and position. We don't want it to smoothly transition to that, we want to set it. So on the camera control, so m_CameraControl.SetStartPositionAndSize() So all that's doing is on the camera control calling the function. Next we want to increment the round number because a new round has just started. So that's m_RoundNumber++; All that does is just add 1 to that round number. And now we've got the incremented round number we can set the message text to be something appropriate like Round and then the round number. So m_MessageText.text because it's the text part of the text component, well named isn't it? And we're going to set that to Round + m_RoundNumber Now it's very crucial here that you put Round and then a space inside your string, otherwise it will say, it will just say Round1 like that on the screen, which you don't want. So make sure you put a space in the string and then we're adding on the actual number on to the end of that, and it will parse it straight in to the UI text. Okay, so let's save the script there and give that a test, see how that all works. Okay, so I'm going to save my script, switch back to Unity. And I'm going to press Play. Okay, so this isn't working as well as I thought it might. Interestingly it's just counting up the rounds. Why would that be James? Probably we need to actually enable the control in, like, the RoundPlaying bit, that'll probably do it. Okay, so what we've done so far is to say 'we want to start the round', and all that RoundStart knows how to do is to setup the UI, setup the tanks and then just be like 'okay, well I'm going to wait, oh hey, there's a new round, I'll wait now, oh hey, there's a new round'. It doesn't actually let us play the game, because we didn't do that bit yes, so I'm going to stop play and jump back in to my code. Okay so RoundPlaying, we've got this yield return null there so what yield return null means is 'come back the next frame', that's all it's doing there. So just to jump back to the slide. As we've said before, we've done RoundStarting and those are the things that happen TankManager is taking care of the Resetting and DisablingControl so that's all good. But RoundPlaying, so what do we do in RoundPlaying? Well we need to enable the TankControl when we played just now we noticed you couldn't drive or shoot. So EnableControl is in TankManager. It can move, it can shoot and it sets up the UI. And the thing it needs to do is empty that message. So as soon as we start playing we don't want to see the word Round 1 on the screen, we want to scrub that out. So we don't disable the UI or do anything else we just empty the message string. And then as James was saying earlier with the coroutine we just keep waiting until there's one tank left. Let's see how we write that. So at the start as it said on the slide we want to enable TankControl so you can actually play the game. So we've got a function that does that and all this function does is loops through all the tanks and calls their EnableControl. So nothing really to it. Next we want to empty that message text, so m_MessageText.text = string.Empty so that's just a blank string. Naturally you could also just do that if you wanted to, but we're not doing it that way, we're going to go with the string.Empty. Okay, so it's looking good so far but now all it's going to do is it's going to enable the TankControl, blank the string and then wait at one frame and then go on to the round ending. So we don't want that, what we want is to use that while loop that we saw earlier. So for a while loop it's while( and then the condition within the ). So we've got a function that's OneTankLeft which returns true if the there is one tank left, or less. Yeah, there is a condition where it can be a draw, you'll see. Yeah, so this will keep doing whatever is within the brackets as long as there is not one tank left. So the thing that needs to go in there is our yield return null. What I should be doing with my while loop is placing it around yield return null. Like that. Adding lots of lines for no reason. There we go. So until that's true it's just going to keep going away one frame and waiting for it to become true. Okay, we'll save that, switch back to Unity, and I'm going to hit Play and see what happens. Okay, I've just muted my audio for the sake of everyone So I knew it, I can drive around. That's pretty cool, I can shoot stuff. Okay, so this is maybe a point to, you know, hook up with a neighbour and start shooting at each other, but, what you'll also notice is that that's great, now I can move around, but I've got no idea who won that round or what happened. I'll just keep infinitely adding new rounds and keep playing in an endless war, which I think we all know is wrong. So we need to have a RoundEnding, we need something to happen, we need some logic to say 'what's going to happen when one tank kills the other tank?' So back to our slides. RoundEnding is our next coroutine within the overall loop. So the first thing we need to do is really disable the controls. It's kind of great to be driving around and showboating once you've won the game, but, yeah, it's a little bit jazzy for my tastes. So really we're going to disable the Tank Controls you're going to stop dead, the camera is going to focus in on where you are, it kind of puts a nice pause in the game as well. And then we're going to clear the existing RoundWinner, so if we've already had a round played we want to clear out that winner and decide who's just won this round. We want to check to see if any of the round winner's have 5 rounds won and therefore are them game winner. And then we want to put that in to the Message UI and say 'okay, well first off this person just won the round, this tank has this many kills, this tank has this many kills'. Or we're going to say 'hey, this person won the entire game'. So let's have a look at how that works. So back in our code the next coroutine, remember IEnumerator means coroutine. And we've currently got a yield that's got the EndWait. So before we wait we need to do a bunch of stuff. Like we said, we're going to disable TankControl, so we'll call that DisableTankControl function. Next we said we need to clear out our RoundWinner before we can check for another one. So what we're going to do is set m_RoundWinner to equal null. So that's just saying 'for this round we don't know who's won yet, we'll need to check'. So the RoundWinners remember are TankManagers, so it's referring to a particular tank, same for the GameWinner. It's referring to a particular tank via it's TankManager. So once we're not sure if there's a RoundWinner we then can check, so we say that m_RoundWinner = GetRoundWinner. So what that's going to do is it will assume that there is one or fewer tanks left, and then go through all of them until it finds one that's active and return it. Shall we have a look at that function? Yeah we can have a quick look at that. So if you scroll down, it's around 130 for me but it might be a bit different for you. But what you'll see is GetRoundWinner. And it's going to go through all of the tanks. So it's looking for the array, looking for everyone in the array, IE the length of the array. And then if it finds a TankManager who's instance if active So it's activeSelf is true. And it's going to return that tanks. If it gets all the way through the array and hasn't found anything, then it's going to return null, so if it's not found anything that's active it's going to return null, that means there's going to be a draw. And that happens if both tanks manage to blow themselves up at the same time. It is possible, I've seen it, maybe once. Okay, so now after we've found our RoundWinner we're going to check whether it is null or not. So if that RoundWinner is not equal to null. So ! for not equal to. Then what we can do is add to that round winner's number of wins. So m_RoundWinner.m_Wins or m_Wins ++ just to iterate it once. So the RoundWinner remember links to the TankManager for that tank so it says 'hey, this tank, the number of wins that tank's got, lets add 1 to it'. Okay next, after we've added 1 to somebodies number of rounds that they've won they might have won the entire game. So now we can check if there is a GameWinner. So m_GameWinner = GetGameWinner. Yeah, let's just take a look at GetGameWinner. So yet again it's very similar to GetRoundWinner, instead of checking if it's found something that's active we're just saying 'hey, has this particular tank in the list of tanks in the array, is the number of wins equal to the number of rounds required to win the entire game? If so, return that particular tank that's got the right number of wins. If not return null'. Once we've found a GameWinner we want to get a message based on whether there is no RoundWinner, no GameWinner, whether there is a GameWinner, etcetera. So we've got a function for doing that and what we're doing is creating a string called message and setting it equal to the EndMessageReturn. And then once we've got that message setting the MessageText's text to that message. So if we wanted to do anything else with the message in-between then we could do that here. We're not actually going to do anything so we're just setting the text straight away. So we're calling this EndMessage function. So EndMessage is how we actually calculating what to do at this point so, or what to write on the screen, so I'm going to scroll down and look at this very confusing looking set of strings and text. So like before we have this way of coloring text and we had that function further up which uses rich text. So we've got a bunch of different things that it could do, so by default what we do is we say 'okay well let's just have a default condition', and that default condition is that there's a draw. Because when there's a draw we don't really need to increment anything at all so we don't worry about it at all. So we're just going to put in DRAW! as our default text for EndMessage. Then we decide if something different that a draw has happened. So we check 'hey, is RoundWinner not null?' I.E. is there a RoundWinner? Then we're going to send something to the message. So the message this time will be RoundWinner with colored text saying 'hey, Player 1 WINS THE ROUND!'. So then we have something added on to the message so remember message is our overall piece of text that we're going to put in to that text field. So we just keep adding stuff to it in order to create a paragraph of different things within the overall message. The message as a string then gets added all of these, so these \n just means go to a new line. So we're just spacing out this text, it's having 'this person won the round, move down a couple of lines, then start writing something else'. Then once we've moved down four new lines we have a for loop. So with that for loop we're going through all of the tanks that are available and within those we're adding to the message some information. So quite simply what we're doing here is adding on that particular tank colored with PlayerText then a colon and a space, so it might be Player 1: and then the number of wins that are found for that tank. So it looks at the TankManager and it says 'how many wins have you got so far?' Print that as a number, then put in a space and the word WINS. Then put in a new line and do it again for the next tank in the loop. So you will have Player 1: this many wins, Player 2, and so on. Then what we're doing after that is saying 'hey, is there a GameWinner?' Because if there's a GameWinner we don't want it to do any of the stuff above, we don't want to actually write all of this stuff in, so instead of saying 'hey message +=' we're just clearing it out by saying =, we're saying the message is going to be exactly this thing and in that instance, we check the GameWinner, color it with the right text and we say 'space WINS THE GAME' so it might be Player 1, it might be Player 2, WINS THE GAME, that's the only message you'll see on the screen, you won't see the number of other wins that were won or anything like that, we clear it out, put it in. That's whoever won the game. Then we return that message so whenever we call EndMessage we return that piece of text depending on what's just happened in the round. So when we're using EndMessage back up in RoundEnding that's what we're doing. And finally the piece of text after it gets assigned to the component's text field, message, there And I'm going to just jump back on to my slide really quick, what we're going to do now is save the script, but we need you to pair up with someone and fight because it's a tank game and that's how it works. So remember you've got WASD, for one player, and spacebar to fire. Then you've got arrow keys and return for the other player. So save your scene, grab a neighbour and give it a test. Okay, so we're going to do the same. Subtitles by the Amara.org community
Info
Channel: Unity
Views: 88,300
Rating: undefined out of 5
Keywords: unity, architecture, game, coroutine, manager, tanks, unite, training day, boston, tutorial, Unity (Software)
Id: M4bH9lWOJE4
Channel Id: undefined
Length: 55min 56sec (3356 seconds)
Published: Wed Oct 14 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.