Turn-Based Combat in Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
- In this video we'll have a look at creating one of the classics of gaming. Turn-based combat. Turn-based battle systems have been part of gaming since the very beginning. And games like Civilization, Pokemon, and Final Fantasy, are known for this particular style of combat. So let's try creating a simple, yet solid turn-based combat system in Unity. But first, special thanks to InfinityPBR for his support on Patreon. And this video is sponsored by Milanote. Milanote is a tool for organising your creative projects into free-form visual boards. It can be used for any creative project, but it's particularly well suited for the messy, early stages of video game development. It's just a perfect place to store your ideas and make sense of them as they grow. You can get started with Milanote for free. And I've actually used it to plan out the turn-based battle system we're going to be making in this video. In fact, let me just jump right in and show you. So as you can see, I've gone ahead and set up this board in Milanote on creating a turn-based battle system. And I've gone ahead and created four columns. One for the UI. As you can see this links to a separate board. And in here we have a bunch of concept art for the UI. And I've been really inspired by Pokemon, especially some of the newer versions with a bit more colour. And of course the old Final Fantasy games. Also I have a to do list for the UI. And as you can see, I've gone ahead and crossed all of them out. And that's actually because I prepared the UI beforehand. It's really, really simple. In fact, let me just go into Unity here and show you. All I have here is just the main camera with the green background. And then I have a UI canvas. And in here we have an enemy battle HUD. Which is just an image with a name text, a LevelText, as well as a UI slider for the HP. And I've simply copied that for the player battle HUD. As well as created a dialogue panel. Which again is just an image with a dialogue text object. As well as two buttons, one for attacking, and one for healing. If you've never worked with UI in Unity before, don't worry we of course have plenty of videos on the subject that will teach you how to create simple UI like this. I've gone ahead and put one of them inside of the board here actually, and we'll have links to plenty in the description. I also have a column for the environment. I want to keep it simple. Just have two units, a player and an enemy. And remember you can always add more to the system afterwards. So I've gone ahead and created a simple background already, as you can see. We just need to add two battle stations. Which is what I've chosen to call these two patches of grass here. So inside of Unity, all we need to do is just find a sprite that we like. I've gone ahead and created a simple grass patch sprite here. And I'll of course include all sprites along with the finished project in the description as well. So let's just go ahead and drag this in. And as you can see, that creates a sprite renderer. I don't want this to be UI, I want it to be an actual game object. And let's just place it right above our HUD here. And let's duplicate it and move one down here as well. I'm also just going to select these two and set their order in layer to minus 10. This way when we place in units, they're actually going to appear on top. Let's rename the first battle station to EnemyBattleStation. And the second one to PlayerBattleStation. And if we have a look in our game view, that looks really cool. The only thing that I'll note about the way that I've set up the UI is that if we select our canvas. As you can see, I've set the UI scale mode to scale with screen size. And this just means that if we resize our game view, our UI is going to scale with it. Which is really nice in this case. So we have now added our two battle stations and we can check that off as well. And I've also gone ahead and created a column for gameplay. Our combat is of course, going to be turn-based. And what this means is that we're handling one turn at a time. In other words, we can introduce the concept of a game state. So in my game I want to have five states. You can easily add more for increased complexity, but I think these five are pretty good starting point. We have a start state. This initialises to combat and sets up all the units. We'll then have a player turn, where the player can choose between different actions. After that we'll have an enemy turn, where the enemy chooses their actions. And we'll loop between these two until the game is either won or lost. We also make sure that whenever we transition from one state to another, for example the player takes its turn. We then want to make sure to leave plenty of time for the player to take in what's happening before we transition to the next state. So that is kind of the basis of how our gameplay should work. In fact, we can see it in action in this GIF from Pokemon. So that is kind of the basics of how we want our gameplay to work. Now how do we lay this out with logic? Well, if we have a look at the core systems that we're going to need, we're definitely going to need some kind of unit script that we can put on the enemy and the player. That has information about their stats, such as HP and damage. As well as name and level and so on. We'll also have a battle system script, which handles all of our game states. And finally, we'll have a battle HUD script that is responsible for updating our unit UI. In this example, we give the player two actions. An attack action and a heal action, but you can of course add more. All right so with that explanation, that's jump right into it. And let's have a look at creating our first core system, which is the unit. So inside of Unity, let's start by creating our enemy. Let's right- click and go Create Empty to create a new object. Let's call it Enemy. Let's reset the transform. And if we just have a look in our scene view here, we can now see that this is an empty object. I'm just going to go ahead and place it on top of the battle station so that we can see what it's going to look like. Let's then add a sprite to our enemy. So I'm going to go under Sprites, and I have one here called Dwayne. And I'm simply going to take Dwayne and drag him under the enemy object. As you can see, we'll need to reset the transform again here. And I want to move him up to make sure that he's kind of standing on top of the pivot point here. So that once we spawn him in on top of the battle station, he's going to be actually standing on it. And it's not just going to snap to the centre of him, which would look really weird. Remember when doing this to always be in pivot mode. If you're in centre, you won't be able to see the difference. We then go ahead and create a unit script on our enemy. Let's hit Add Component. Let's type Unit and hit Create and Add. And let's double-click to open this up in Visual Studio. And this script is actually going to be really simple. We can go ahead and remove our two functions here. We don't need those. Instead what we want are a bunch of variables describing our unit. So let's create a public string with the unit name. Let's have a public int with the unit level. And let's also add in damage. So we'll create a public int with the amount of damage our unit we'll be able to do. As was a public int for the max HP. And let's also have one for our current HP. This way we can keep track of our HP during the fight. And if our unit is maybe already damaged when it enters the fight, we'll be able to control that as well. So let's save that. Go back into Unity. And right away we can fill out these variables. So the unit name here is going to be The Rock. Let's give it a level of say two. A damage of five. Let's set the max HP to 30, and the current HP to 30 as well. All right so now that we've created our enemy, we can go out and turn him into a prefab. So let's just drag him into the project panel. And there we go, we've now made a prefab out of him. And we can go ahead and remove him from the scene. The reason we're doing this is that we want to be able to load in different units depending on who we're fighting. And let's actually go ahead and do the exact same thing with our player. In fact, we can just use the enemy here as a base. So let's go ahead and rename him to Player. Let's move him down here to where the player is supposed to stand. Let's drag him into the project panel as well. And this way we can create a new original prefab. There we go. So we now have both an enemy and a player prefab. And in fact, we can double-click on this player prefab to go into prefab editing mode, which is pretty cool. And here we can remove the Dwayne graphic and let's instead dragging a rock. I think the players should be a rock. That's just inherently cool. And let's reset that transform here. Let's drag it up and place it so that it kind of aligns with the centre of our object. There we go. And let's go ahead and rename the unit here to Actual Rock. Let's set the unit level 999. This is a beefy rock. We'll set the damage to 10. The max HP to 25. And let's say our current HP is only 20. So we've taken five damage before this fight started. And let's exit prefab mode up here. And we can now remove this prefab from the scene as well. Awesome. So with that, we've actually gone in and created both our enemy and our player units. We'll add a tiny bit more to the unit script itself, but the base of it is there. And we can now move on to our battle system and defining all of our game states. So let's go into Unity. Let's create a new empty object. Let's reset the transform and let's name this Battle System. Let's drag it to the top here so we can always see it. And let's add a new component and call it Battle System. We'll hit Create and Add and open it up in Visual Studio. And we're going to need our Start function, but we can definitely get rid of Update. Now before we get started on an actual logic here, we need to define the different states that our game can be in. And to do this we'll use something called enums or enumerations. These are perfect because they allow us to define different states, and then create a variable that can always be only one of those states. So we'll start by defining them. We'll create a public enum up here. Notice that I'm doing this outside of the class. And let's call it BattleState. We'll then open and close some curly brackets. And a lot of people put a semi-colon here. You should not do that because we are not calling a function here. We're defining an enum. And in here we'll put a START state. We'll put a PLAYERTURN state. An ENEMYTURN. A WON and a LOST state. And now inside of our battle system, what we can do is now create a public battle state, and just call it state. And now we can set this state equal to whatever we want. So inside of our Start, once we load up the battle scene here. We're simply going to set state equal to BattleState. And here we get all the states. And we of course want to choose START here. So you can see how easy it is to keep track of our states this way. And in fact, if we save this script and go into Unity, we'll even be able to preview our different states and see what state we're currently in inside the inspector. Really, really cool. So now that we are in our start state, we can go ahead and set up the battle. So let's create a SetupBattle function. And let's create that down here. Void. SetupBattle. And in here we of course want to spawn in our units. So we'll create a reference to them. We'll create a public game object. Let's call it player prefab. And we'll create a public game object, enemyPrefab. And where do we want to spawn these in? Well we want to spawn them in on our battle stations. So we'll create references to those too. We'll create a public transform. And this is just a transform, not a game object. Because we just need the location of these battle stations, not the entire object. And this is going to be the player battle station. And we'll of course also have one for the enemy battle station. Then when we set up the battle, we can go ahead and instantiate or spawn in a player prefab. As child of, and on top of, the player battle station. There we go. And we can do the same thing with our enemy prefab on top of the enemy battle station. And if we save that and go into Unity, we can drag in our player prefab, our enemy prefab, as well as our player battle station, and our enemy battle station. And on our prefabs here we want to to make sure that their position is set to see zero, zero, zero. We'll do that with the enemy as well. Just so that when we spawn them in, they'll be right at the centre of our battle stations. And if we now hit play, as you can see, we're now spawning in our units. Awesome. Of course currently we don't know anything about our units through script. We're just spawning in the objects and nothing is really happening on the UI. So let's go ahead and change that. So, first of all, we can get a reference to what we're spawning in by creating a game object variable here. We'll call it the playerGO or player game object and set it equal to the game object that we spawn in. And what we can then do is actually get the unit component on that game object. So we'll do playerGO.GetComponent of type Unit. And we want to go ahead and store this in a variable because we'll be referencing the unit a bunch of times whenever we need information about health and so on. So let's go to the top here and create two variables. These don't need to be public. We don't need to see them in the inspector. We'll just create a Unit, playerUnit. And a Unit, enemyUnit. And then down here we can simply set playerUnit equal to player game object.Getcomponent Unit. And we'll do the exact same thing with our enemy. So game object, enemyGO equals the instantiated object. And we'll set enemyUnit equal to enemyGO.getComponent of type Unit. There we go. And what this allows us to do is now get information about our units. In fact, let's go ahead and display the enemy unit name to the player. We access this by simply going enemyUnit.unitName. So let's go ahead and put this unit name inside of our dialogue text right here. Of course to do that, we need a reference to this text object here. So inside of our script, we go to the top. And because this is UI, we need to include the UnityEngine.UI namespace. And we can then create a public text variable called dialogue text. Then down here, we can simply set dialogueText.text equal to enemyUnit.unitName. And let's just add a tiny bit to this. Let's just say a wild. And then we'll insert the enemy unit name, approaches. There we go. So if we save this now and go into Unity, select our battle system and remember to drag in our dialogue text. And hit play. A wild The Rock approaches. Awesome so we're now gathering all this information about the units that we're loading in. And we have total control or where we place it. So let's go ahead and place this information on the HUD overlays. And we could do all this through the BattleSystem script, but that would get very long. So let's go ahead and split it into a separate one that we place on our battle HUD objects themselves. So I'm assuming you're going to select like these two. And that's create a battle HUD script. Again let's double-click it to open it up. Again, we can go ahead and remove the two functions here. Let's create a public Text. And again, remember this is UI so we need to use UnityEngine.UI. And this is going to reference our nameText object on the UI. We'll do the same thing with our levelText. And we'll also create a public slider to control our HP slider. We then create a function that is going to update these UI elements. So let's create a public void. And we're making this public so that we can call it from within our BattleSystem script. And it's just name it something like SetHUD. Of course we need some information about what we should set it to. So let's just take in a unit and call it unit. This way we have access to all the information we need. And here we can then just set nameText.text equal to Unit.unitName. We can set levelText.text equal to let's put level. And then plus the unit.unitLevel. For the HP slider, we can set the maxValue equal to the unit.maxHP. And hpSlider.value we'll set equal to unit.current HP. There you go. Of course, we probably want to update the HP a bunch of times every time someone attacks or heals. And so let's just create a quick function for that that only updates the HP. So let's do a public void. SetHP. This is simply going to take in an int with the HP. And here we can just set hpSlider.value equal to that value. Cool. So that's actually it for a battle HUD. All we need to do now is go into our BattleSystem. And in here we need to create a reference to each of the battle HUDs. One for the player HUD and one for the enemy HUD. So we'll just create a public BattleHUD, call it playerHUD. And a public BattleHUD called enemyHUD. And inside of our SetupBattle function, we then say playerHUD.SetHUD, and we'll pass in the playerUnit. And we'll do the same thing for the enemy. So enemyHUD.SetHUD enemyUnit. There we go. Let's save this, go into Unity. And you can see both of the battle HUDs now have some variables that we need to fill out. So for the enemy battle HUD let's drag in the NameText. LevelText. And the slider. And we'll do the same thing for the PlayerBattleHUD. And for our battle system we just need to drag into two battle HUDs. Awesome. If we now hit Play, we can see immediately our HUD updates to show the names of our two units, the level, as well as our current HP. Awesome. So we can check off our battle HUD here. And I think it's time to dig into some of the player actions. So inside of our script here in our battle system, let's have a look at what should happen when we're done setting up our battle. Well, at this point, I think we should go ahead and transition to the player's turn. So we'll simply add a line at the end here that says state equals BattleState.PLAYERTURN. And we can call a function called PlayerTurn. And let's go ahead and create that. So we'll create a void PlayerTurn. And for now all we want to do here is just make it clear to the player that he can choose an action. So let's set our dialogueText equal to something that suggests that. Let's just put choose an action. There we go. Of course currently we're calling the SetupBattle, and we're saying that a wild enemy approaches. We're then updating our HUD and straight after that we're calling our PlayerTurn. And all of this is going to happen inside the Start method. And that's not really something we want. We want to add a bit of delay here as we talked about. So how do we do this? Well to wait in code, we use coroutines. Now we use coroutines quite often here in the channel, but if you've never seen one before, the syntax is a bit frightening. But just think of them as functions that are running separately from everything else, which allows us to pass them whenever we want. So to turn this SetupBattle here into a coroutine, we replace the void with IEnumerator. Again, I know the syntax is weird. And now before we go ahead and change to the player turn we can put in yield return new. WaitForSeconds. And here we can input the amount of seconds we would like to pass. So I'm just going to to put in two seconds. Now whenever we are calling a coroutine we need to add a tiny bit of code. We need to add StartCoroutine. And then wrap our entire function call in that. So that's all the syntax we need. I know it's a bit weird, but it's always the same way. So you can just have a look at what I'm doing here. We're just calling StartCoroutine. Then the name of the function. We're replacing void with IEnumerator. And then we're putting in a yield that waits for two seconds. So with that, if we save and go into Unity and hit play. As you can see a wild The Rock approaches. And after two seconds, it says choose an action. And we now either press Attack or Heal. Of course, currently nothing happens. So let's go ahead and add some logic for the buttons. Let's start with the Attack button. So to do that, we go into our script. And we'll create a new function here called OnAttackButton. And we want to trigger this function whenever the OnAttackButton is pressed. However in order to do this through the UI, we need to mark it as public. And what we want to do here is to check if it's currently the player's turn. So if state is not equal to BattleState.PlayerTurn, then we'll simply go ahead and return out of this function. We don't want to do anything more. But if it is. Well then we'll continue down here. And then we'll call some kind of PlayerAttack function. In fact let's make this a coroutine so that we can pause during our attack. So we'll do StartCoroutine around that function call. And let's go ahead and create the function as well. So we'll create an Ienumerator. We'll call it PlayerAttack. And here we want to damage the enemy. We then want to wait for a few seconds. So yield return new. WaitForSeconds. I'll put in two again. And then we want to check if the enemy is dead and change state based on what happened. So we'll do that in a sec. But for now, let's just make sure that this function is triggering. So if we save this script and go into Unity. we now navigate to our UI. Let's find the dialogue panel, and here have the combat buttons. I'm going to select the AttackButton. And under any UI button there is an OnClick event. Here we can choose what happens when this button is clicked. I'm going to hit plus. I'm going to drag in the Battle System. And let's go under the BattleSystem script. And here we can now call the OnAttackButton function that we just made. Awesome, that is now going to trigger that function whenever we press this button. Of course we probably want to make our PlayerAttack actually do something other than just wait two seconds. So let's have a look at damaging the enemy. Well this is actually fairly easy. We have a reference to the enemyUnit. And so we can simply go in here and we could actually modify the HP directly, but it's better practise to create a function that does this. So let's create some kind of TakeDamage function that takes our player unit's damage. So playerUnit.damage. Let's go ahead and create this function here. So let's go into our Unit script. And in here let's create a public void TakeDamage. This is going to take in an int with the amount of damage. And it's then going to subtract that damage from our current HP. So minus equals. And in the case that our current HP reaches zero, we want to let our battle system know that this unit has died. So we want to go ahead and say if our current HP is less than or equal to zero after we subtract this damage, well then we want to return either true or false. So true if the unit has died, and false if it hasn't. So we'll go ahead and change the return type of this function from void to bool. And we can now return true. And if not, then we can return false. So this function is going to subtract it the damage, check if this unit has died and return true if it has, or false if it hasn't. And this way inside of our battle system, we can now gather this information in a boolean. So we'll create a boolean called isDead and set it equal to the result of this function. And then down here when we check if the enemy is dead, we can simply go if isDead. Well then we want to end the battle. And if it's not, well then it's the enemy's turn. There we go. We also just damaged the enemy so we need to update the enemy HUD. So we'll go enemyHUD.SetHP. and we'll pass in the enemyUnit.currentHP. And let's also update the dialogue text. So let's go with dialogueText.text equals the attack is successful. Then if we die, we'll change the state to BattleState.WON. Because we just killed the enemy unit. And we'll call some kind of EndBattle function. Create this in a sec. And if the enemy isn't dead, we'll go ahead and set state equal to BattleState.ENEMYTURN. And we'll start a coroutine with some kind of EnemyTurnFunction. That we'll also create in a sec. So let's start by creating the EndBattle function. Void EndBattle. Again you could turn this into a coroutine if you want to. But I don't need to here because I'm simply going to update the dialogue text. So if state is equal to BattleState.one. Well then we'll go ahead and set dialogueText.text equal to you won the battle. And if not, and the state is equal to BattleState.LOST. Well then we'll display you were defeated. And then I'd probably load out of the battle screen and so on, and so on. So that's it for our EndBattle function. Now let's do our EnemyTurn. Again this is a coroutine so let's do an IEnumerator. Call it any EnemyTurn. And now in here you can definitely add some kind of AI to determine what the enemy does. If he also has an attack and a heal, you might want to attack if he has a chance of killing the player. Or heal a few times he loses health. You can put in what every logic you want here. For now we can just make him attack every time. So to do that let's create some dialogue text that says that the enemy unit is attacking. So enemyUnit.unitName attacks. We'll then wait one second. And that will damage the player. So playerUnit.TakeDamage based on the enemy unit's damage. Again, we want to check if the player has died. So bool isDead. We want to update the player HUD. So plaerHUD.SetHP. Based on playerUnit.currentHP. Let's wait one more second. We can just copy this line here. And then let's determine whether or not the player's dead. So if he is dead, then we can set state equal to BattleState.LOST. And again, call the EndBattle function. And if not, then we'll set state equal to BattleState.PLAYERTURN. And called the PlayerTurn function. There we go. That's an enemy turn. So we display in the dialogue that he's attacking. We wait one sec. Then we damage the player. You wait one more second. And then we determine if he's dead or not and change states. And with that, we should actually be able to go into Unity and hit play. A wild The Rock approaches. We'll choose the attack action. The attack is successful. And as we can see the rock loses health, it attacks. We lose health and we're back to the player turn. And we'll be able to continue like this until either we or the enemy dies. So we'll go ahead and attack it one more time. And we won the battle because its HP is now zero. Awesome. So that was the battle system and all the game states. That was it for the unit and the attack action. And we can just go ahead and add a heal action real quick, just for fun. So doing this is much easier. We simply need to duplicate the OnAttackButton function here. And let's call it OnHealButton instead. We only do this if it's our turn and it's going to start a coroutine called PlayerHeal. And all this is going to do is be an IEnumerator called PlayerHeal. And of course we need to heal the players. That's go into our unit and create a heal function. Public void heal. It's going to take in an amount. And it's just going to increase our currentHP by that amount. And if the currentHP gets greater than the maxHP, well then we'll simply set currentHP equal to the max HP. Then inside of our battle system, we can call the playerUnit.heal. Let's just heal by five points. We'll update the UI. So playerHUD.SetHP. PlayerUnit.currentHP. We'll update the dialogue text. You feel renewed strength. Wait two seconds and change to the enemy's turn. And that's it. We can now save that, go into Unity. And here we of course want to make sure to select the heal button and add an OnClick event to that as well. We'll find our BattleSystem OnHealButton function. And if we now play. And it's maximised the game view here. We can choose, of course the attack action, that still works. We take some damage from the attack, from the rock. We'll press Heal. And we immediately regain some HP and we feel renewed strength. And there we go we've now created turn-based combat with different actions and an enemy. And it's all working. Awesome. That of course means that we can check off the last thing that we needed to do here. And there of course a bunch of ways that you can expand upon this. You can change the UI based on game state. In fact, let me just go ahead and add some of those here. So I'll just drag in a column. Let's call it making it cooler. I'm just going to add in some bullets that you could add. You could change the UI based on the game state, similar to what they do here in Pokemon. You could add animations to units and health bars. I think a good example of this is definitely Final Fantasy, where you can see that there's all these kinds of animations going on whenever someone attacks. In fact, let's just go ahead and drag that right in here. You could add more actions for the player and enemy. Add more units by adding some kind of unit select game state. And of course you can integrate it further into your main game. If you're not sure about where to go from here, you can always take out this really cool video I found by Code Monkey that shows a lot of the animation stuff and even has some UI overlays for showing damage which I think is really cool. So I'm just going to to go ahead and add that in here as well. So anyway, there's plenty opportunity for you to go nuts with it from here. That's pretty much it, this video. If you enjoyed it, make sure it's subscribe and ring their notification bell so you don't miss the next one. Also, don't forget to check out Milanote. Simply click the link in the description to get started. On that, thanks for watching. And I'll see you in the next video. Thanks to all of the awesome Patreon supporters who donated in October. And a special thanks to InfinityPBR. Lost to Violence, Loved Forever. Ruonan, Chris. Jacob Sanford, faisal marafie. Peter Schwendimann, Leo Lesetre. Dennis Sullivan, Alison the Fierce. Stig Christensen, Kirill Svidersky, Gregory Pierce, Naoki Iwasaki, TheMightyZeus, Daniel Dusanic, and Erasmus. You guys rock.
Info
Channel: Brackeys
Views: 360,684
Rating: undefined out of 5
Keywords: brackeys, unity, unity3d, asset, assets, model, beginner, easy, how, to, learn, tutorial, tutorials, tip, game, development, develop, games, programming, coding, basic, basics, C#, ui, ugui, rpg, battle, system, combat, turn, based, turnbased, turn-based, strategy, pokemon, final fantasy, card-game, role playing game, intermediate
Id: _1pz_ohupPs
Channel Id: undefined
Length: 29min 39sec (1779 seconds)
Published: Sun Nov 24 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.