Hello guys.
Welcome to the video.
In this one I want to show you how to set up difficulty for more than one player.
Because as of now, system works only for one player.
And if we have two players in the game and one of them will die, then it will restart the level.
That's not what we need.
So I want to introduce a live count in this video so players can have a live points that they can spend
to respawn.
But before we do that, let's fix that bug that we've seen in the last video.
So I did spend some amount of time to check what's up.
And I found the problem.
I found the root of the problem and let me explain it to you first.
So when we die it is fine.
And then when we're using the second player and pressing the jump button, we can see these two bugs
over here.
And first message over here gives us an information meeting reference exception while executing performed
callbacks of a player jump.
Also, if you go here, it leads you to this method of a private void jump.
Because that was used first when I pressed the jump button.
And the problem is actually not in the dust fix or anything else in here.
The problem is that we're trying to use jump method from a player that we destroyed.
It means that we did not unsubscribe from the event when player died.
That's the problem.
Now look, I died only once and I have this message once.
If I die again and try to jump again, you'll see this message twice.
Because now we're trying to access two players that were destroyed before I got this.
Because we can see a message over here.
I'm missing reference exception.
The object of type has been destroyed, but you are still trying to access it, so that's the problem.
We do not unsubscribe from the event.
Uh, luckily for us, I found the fix, and it works like that because of the change we did to the.
Input system over here, so we will have to adjust it slightly.
And before we do all of that, let me take these two methods.
And I want to move them all the way down here because they take a lot of space and they're just in the
beginning of the script, and we do not come back to them so often.
Right?
Besides, we're going to have to create three new methods here.
So I'm going to keep all of the input below.
Now what is happening here.
We do not unsubscribe from the event on disable.
That is the problem.
Luckily for us it's a very easy fix.
Um, let's start here.
We're going to remove this and we're going to type on jump performed just like we did in the player
manager over here with the add player in a very similar way.
Then we're going to click Alt Enter to Generate method.
And over here we have a method with a context.
If you cannot generate method with a hotkey just like I did, you can manually write the same method
and it will work.
I want to move it below over here.
And then inside we're going to call jump button.
And also I forgot to use buffer jump in the previous input we had.
So we need to do attempt buffer jump.
That's what we need to do okay.
Very nice.
Now let's go here and let's do minus equals on jump performed.
Cool.
Now let's go ahead and check if this one fixes the issue.
I'm going to go to play mode.
Uh, I'm gonna make the character going to kill him, read a new one, and there is no more bugs like
we had before, so it works correctly.
I love it.
Now let's go back and let's do this adjustment for these two lines of code over here.
So I'm going to just remove it and I'm going to type on movement performed.
Let's generate method inside.
We're going to do uh move input equals to context of read value vector two.
And basically it's the same as we had before just in a method that's the same job.
Uh on movement canceled.
We're going to do generate method.
Move input equals to vector 2.0.
Nice.
Now we need to go over here.
And replace it in the same way.
On movement cancelled and on movement performed.
Right.
This will fix the problem.
Very nice.
Now let's continue development of the player manager.
I want to have a player's life count in the game.
So we need to make a life count and we need to deduct points from that life count every time player
dies.
And then if we don't have lives anymore, we need to disable ability to join the game.
So let's start with simple.
Let's make UI so we can see lives.
I'm going to take fruit UI, duplicate it.
Let's rename it to.
Life point UI.
And I'm going to attach it to the center.
And these elements have a -22 on the Y.
So I'm going to do the same over here.
-22.
Okay, let's change the icon as well.
This can be changed to.
Lifepoints text and.
Life Points icon and we can go to Project Graphics main character virtual GUI.
Let's take the idle sprite and drag it on the icon over here.
I think we can reduce size to 100 by 100.
And let's just move it above a little bit like that.
And maybe we can flip it.
Okay, good.
Now we have a UI.
Let's go ahead and make a method on the UI in game script so we can update this UI.
Going to make a reference serialize field.
Revit text mesh Pro guy life.
Lifepoints text and then somewhere below.
We need a method.
Public void update.
Life points UI.
Going to do live points text that text equals to.
And we need to variables over here int live points and int max live points.
Okay then let's go here and do live points plus slash plus max live points.
Okay, I like it.
Now, the only thing we need to do is to have the values for this method.
We can do that on a player manager.
So.
I'm going to make a variable public int life points.
Okay.
And I think we can have max player count as a maximum life points, so each player can die only once,
or one of the players can die twice.
I think this is acceptable.
I like the idea.
So we're going to go.
To the place where we remove the player and we're going to do live point minus, minus.
And then if live points is less or equals to zero, it will mean we do not have live points anymore.
And we can take player input manager disable join in and this will stop ability to join the game.
Also, every time we remove the player, let's do UI in game instance, update live points UI and we're
going to give it live points as a current value and max player count as a maximum value.
Very nice.
Let's save this and let's go back.
And we need to assign this lifepoints text like that.
Also, we need to go to player manager and let's say we have max player count two and Lifepoints two.
Let's go to the game.
Why did we not update the text?
Because that's why.
Because by default we need to update text by default as well.
We're going to do that later.
As of now let's do two out of two okay.
So I'm joining the game I have two lives.
We can see that.
And there is a second player joining the game with a gamepad.
Come on, boy.
Oh, I have Bluetooth switched off.
Okay, now should be good.
Yeah.
Connected.
There is a second layer.
Now I'm going there and I'm going to kill it.
And we have one out of two lives.
Then second layer go in there and.
Get damage and there is zero out of two and we cannot respawn the player anymore.
Uh, joining is disabled.
Very good.
So now when that happens, instead of just doing nothing, we can restart the game just like we did
before.
Let's go to player manager.
And over here we're going to check.
If we have no life points anymore, we're going to do a game manager instance restart level just like
that.
Very nice.
Now there is another thing we need to do.
This one will disable drowning on level restart and it will not be enabled when you start the game again,
because in the future we're going to need to set this GameObject to don't destroy on load.
It's going to be like this.
Don't destroy on load this GameObject, because we're going to need to set default values on this GameObject
in the main menu.
So let's keep it like that and let's see what is going to happen when you restart the level after death.
Let's go there.
Gonna kill it again.
And we restarting the level.
And now I cannot join the game because this object is not destroyed on load, and this object of the
player manager have joining enabled as false.
And we cannot add new player.
So we need to fix it somehow.
And I think we should make a method that would enable joining and update live count in the player manager.
But every time it's going to be called from the game manager in the start method, because we have game
manager on each new scene and their start will be called every time we load new scene.
So.
Let's go and make a method.
Um, public void.
Enable join.
And update life points.
It's pretty long one, but it's okay.
Then we're going to do player input manager.
Enable join in.
And we're going to do uh live points equals to max player count.
And also we want to uh take UI in game instance update live points UI.
And we're just going to do live points max player count just like that.
And then we need to go to the game manager.
And in the start method, after we make sure we did create all of the managers, we're going to do player
manager.
Dot instance, enable join and update live points.
Very good.
This not only will help us with the level restart, but also it will help us not to create a player
in the main menu because there we'll disable this by default and it will be enabled from the game manager
every time we start the level.
So let's go ahead and try this.
Okay.
We've got two out of two going to make a player.
Kill it.
Then second player joins, goes there, we kill it.
Level is restarted and we have everything again from the start.
And it works clean and nice.
Okay, I see a message there, so I need to double check.
What does it say?
Multiple player input managers in the game.
Okay.
It is fine because it is getting destroyed.
From the awake.
Yeah, we can ignore it because the extra object is destroyed.
It's fine.
Let's do this game object.
Nice.
Now another problem we need to fix is the player's reference.
We have this variable over here that is accessed by many scripts in the game.
And we need to solve this somehow, because now we can have more than one player.
So I'm going to remove it from here.
It will show me a bunch of errors, and we can see to which places we need to pay attention.
Let's click over here and let's find one of the.
Lines are over here.
Okay?
This one is not needed anymore.
We can safely remove it.
This was just a placeholder.
Now let's see what else.
Um.
Over here, when we pause the button, we get an access to the player, and that is done only to disable
the input.
We need to do the same.
But for all of the players in the game, I think we have to go to the player manager first, and we
need to make a method that would return us this list.
So let's go somewhere below.
And let's make a public list of player, and we're going to give it a name of get player list.
It's going to return me player list.
Just like that.
Now let's save this.
Let's go to UI in game.
All the way above.
Over here.
We can make it as a list.
List of player.
And let's name it as player list.
Uh, let's click alt enter, use system collection generics.
And then we're going to go back to that pause button method.
Over here.
And instead of this we're going to do player list equals to player manager dot instance get player list.
Okay.
Now we need to disable input for each player in the list.
We can do something like.
This.
For each.
VAR in player list.
Player.
Player input.
Disable.
Then I'm going to copy this.
Paste this in here and we're going to do enable.
All right, now let's go and double check this.
I'm going to go back.
There is some other message we have on the enemy and this will be fixed.
No worries.
For now, let's just, uh.
I'm in the salt.
Okay, let's go back and do this.
We're going to go to play mode.
I'm going to make two players.
I'm going to pause the game and none of them can be controlled.
This is good.
Now I'm going to unpause the game.
We are back in the game and we can control characters very well.
Now for the enemies.
This one is on the enemy and in this one we updated the reference of the player.
So that's what we need to do.
But now I guess it's better to use list as well.
Uh, just in case we want to have access to all of the players.
So let's go ahead.
Whereas the players reference.
Can I see it?
Oh, it's a transform.
All right.
Okay.
This what are we going to do?
We're going to turn it into a layer and it's going to be a list.
Of players.
Um player list, same as we did just now.
And then when we need transform, we're going to get access to the transform.
Okay.
So player list.
Very good.
Now let's go down here and instead of.
This we're going to do player list.
Equals to player manager dot instance get player list.
Just like that.
And now we want to call this method when player responds.
And also when player dies.
This is what we want to do.
So.
Let's go to player manager.
And I guess we can make another action.
Just to make it clear and understandable, I'm going to do public static event on player death.
Okay.
Let's go to remove player.
We're going to invoke it here.
We're going to do it above.
No, no, wait.
We can just invoke it in the end.
On player death not equals to null.
Then invoke like that.
And then on the enemy.
We need to go here and do player manager on player death.
Update layers reference.
And then we need to unsubscribe from this.
So we're going to go.
To die.
Method.
Player manager.
On player death.
Unsubscribe.
Very nice.
Love it.
So this does half of the job.
Now we still have to do something here.
Enemy chicken uses players reference over here to flip to know where it needs to run.
We can see a message here with an error.
So what I want to do is something pretty simple.
I want to update the reference of a player according to the amount of players we have, and we're going
to check how many players is there, and we're going to choose the closest one because chicken is supposed
to chase the player, so it's fine if she has a closest one.
I think that's a pretty good solution.
So let's go and make.
I want to transform player.
Very nice.
Now we're going to go.
Here.
First of all, we don't need to use transform because player is a transform.
It's fine.
And then somehow we need to update this information and it should be relevant.
And also it should be updated like pretty often.
So I think it would be a good idea to do it in the flip.
Every time chicken flips is going to check where is the closest layer.
And so this would be going to do we want to have float closest distance.
Equals to float max value.
So this will have the biggest value there can be.
And then we're going to check distance to each object.
And if new distance is less than current closest distance then we're going to update closest distance
to the new value we've got.
Um let's do this for each for each player P in player list.
So we're going to check each player in the list.
We're going to do float distance to player equals to vector two dot distance from transform.position.
To t transform position.
Then if distance to player is less than closest distance means we found our new closest distance and
we need to update it, we're going to do closest distance equals to distance to player.
And also we want to update player reference because now we found new closest player.
So I'm going to do player equals to p transform.
Very good.
So this is supposed to do the job?
Uh, maybe let's wrap this into a method.
Extract method.
Find closest layer.
Just like that.
And it should be good.
Uh, there might be something else, but to be very honest.
Oh.
What is it?
What did.
What does it say?
The event is never used over here on player manager.
We need to go and check if we invoking the on player death.
And I did make a mistake because this one should say on player death.
Very nice.
Uh, one more adjustment I want to do.
As of now, when we have two characters in the game, they can push each other.
And I don't mind if player cannot pass by each other.
I like it.
And also, I like when you can stand on the head of the another player.
So we're going to go ahead to the asset prefab.
Player.
Let's make a 2D sprite square and we need to add a box collider 2D on this, and I want to place it
on the head of the character.
Something like that.
Yeah, this should work.
Now let's make it a bit slimmer.
And let's give it a name.
Something like a head.
Later.
Now we want to go to layers and we're going to add layer players.
Had.
Okay.
Now let's take this object and let's enable players head over here.
Then we want to go to Edit Project Settings Physics 2D.
And.
None should be able to collide with this except the player.
So I'm going to disable everything except the player.
Just like that.
Then let's close it.
Let's select the player itself.
And on what is ground we're going to choose player's head as well.
So now we are supposed to be able to stand on the head of another player.
Let's make it slimmer like that.
And let me double check this.
It should be good and it should be fun.
So I'm going to join and stand here.
And then second player is there, and he can stand on the head of the character and jump from it.
And now you can make lots of nice puzzles where one player should give another player, uh, a platform
so you can jump from it.
Yeah.
Nice.
Right?
What if I run and it doesn't move together?
Would be fun also.
But if you move together.
Yeah, this could be fun, man, I like it.
Good.
Um, there is something else.
As of now, we do not use difficulty of the game.
It's not working in any way.
So this is what we need to do.
First of all, on player manager, we removing the life.
In any case, even if it's an easy mode and on player we did remove fruits only if this is a normal
difficulty, right?
So this is what I want to do here.
I think we can, uh, let's remove this as well.
And, uh, give me a second.
I need to think.
I think we can keep this one as it is.
We just need to go to player manager and use game difficulty here.
So.
We can do lifepoints only if difficulty is normal and we don't have any fruits, or if difficulty is
difficult.
Uh, I mean hard.
So let's do private bull can remove.
Life points.
And over here we need to do something like if game manager dot instance dot fruits collected is less
or equals to zero and difficulty manager dot instance dot difficulty equals to normal, then we can
return true.
Also we want to return true if difficulty manager instance difficulty says that difficulty is hard,
then we also need to return true.
And if no we're going to return false.
Okay so how this works.
We're using this boolean method to check if we can remove life points.
And we check if it's a hard mode of the game.
Then we return true and we're going to remove life point and none of this check will matter anymore.
Then if this one is not true, if game is not hard, it's going to proceed further.
And then it checks if fruits collected are less or equals to zero means we don't have any more fruits.
And difficulty over here is normal.
It means we can remove life points and we're going to return true.
And if this condition is not met, then it's going to be skipped and we're going to just return false,
which means we cannot remove life point.
Okay.
Let's do if we can remove live points.
Then do minus five points.
All right.
This is supposed to work.
Maybe there is a cleaner way.
I'll think of it in the end of this section.
As of now, this should be good.
Now let's go to unity and let's try this.
Now we don't need to update difficulty on the players prefab.
We can only control that from a difficulty manager.
And let's see, I'm going to change difficulty to normal.
I'm going to collect some fruits.
And now I'm supposed to get damage and lose the fruit, but not the life point.
And yes, that's the case.
It's working.
This will work for the second player as well.
Yes.
Okay.
This is good.
And now if I die, I'm losing life.
Point.
Nice.
Now let's try this again with the hard difficulty.
Can I go there?
And it doesn't matter how many fruits I have.
Because I'm losing life points, so it works.
Love it.
There is something else I want to do before we go further.
And I promise this is the last one.
If game is easy, then we don't need to see the lives at the top over here because you know there is
no point of counting lives because you cannot lose the game.
So if that's the case, then we need to disable this game object.
I can go over here.
And not to mess with the code too much.
I'll just make this disabled if game is easy.
So what I'm going to do if, uh, difficulty manager instance difficulty equals to easy.
Then I'll take live points.
Text.
And transform parent.
GameObject, set active faults and return.
I think this is supposed to work.
Let's go and try this.
I'm going to go to asset prefabs.
I'll take difficulty manager.
I'll make it easy.
I'll drag it here and let's go to game mode.
And it's not there.
It's fine and we can play the game and enjoy the game without being worried about losing it.
All right.
This is perfect.
I don't think we need to do anything else for now.
This is pretty much a lot of functionality we did in one video.
There are still some things we need to do, but we're going to do them later.
As of now, this is good.
Let's wrap this up.
Let's make it as normal.
Let's apply overrides.
Let's take skin manager, player manager in game UI and we can apply overrides on all of them.
So it will be applied to different levels as well.
And let's go I'll see you in the next one.