Default pawn multiplayer

In my game the player will have multiple characters to choose from, each with different movement, animations and actions.

For this, I made a CustomGameInstance where after the player chooses a character a value named “Character” is set to what he/she choose (for basic example I used male/female mannequin). Then I made a CustomGameMode, in which I thought that I could simply do the following to select the character:

If male is chosen then it will set the default pawn class to ThirdPersonCharacterMale, or if female is chosen then it will set the default pawn class to ThirdPersonCharacterFemale. The default pawn in the CustomGameMode is set to ThirdPersonCharacterMale.

While in SP mode it does work as I wanted, but as soon as I enter multiplayer mode, then those that join the game will have the same “gender” as the host. If the host started as Male mannequin, then those that join, no matter if they chose Female, when joining they will have Male mannequin as well.

What do I need to change/make sure is set so that the “gender” of those that join, will remain as the one they chose before joining?

Your code is in the Construction Script which is for Editor only code. So this code runs when the BP is placed in the editor, this is not executed at runtime. So the other players most likely just get the replicated or the default value.

2 Likes

If that is the problem, then where should it be placed? Or how else I should do the character selection for MP?

Or how else I should do the character selection for MP?

If you want every player to be able to do that. A quick way would be a setting in a config, a command line switch, a command line argument, or the more natural approach, in a lobby UI.

1 Like

You took that a bit too literal. I have a character selection screen, that is not what I meant. How should I go around the problem that those that join have the same “gender” and not the one they chose (female in this case, if host chose male).

//Edit: If i simply take the “code” to Event Graph/ Begin play, then it stop working and it doesn’t change the character/“gender”.

I see, well you can override GetDefaultPawnClassForController in your gamemode. And read from the controller which gender they want to use.

2 Likes

@Valentinvis , there seems to be a fundamental mis-understanding of the unreal networking framework. As a direct answer to your question, the reason connecting players are getting the pawn class chosen by the host is that the GameMode does not replicate and/or run on the clients. So when a client connects to the host, the GameMode running on the host gives that client a pawn as defined by DefaultPawnClass…which you defined on the GameMode running on the host. So whatever the host picks, everyone gets.

In order to make what you’re after work you’re going to have to setup some communication between the server and client where the client requests a specific pawn class.

3 Likes

Yeah, I was thinking a bit about that, and I came to the same conclusion. It was working on the host because he was creating the character “gender”, but then when the client was connecting, he was getting what the host “made”/set as default. I did a simple print string and I saw that GameMode was printing the message only on host, and not on client, and also I remembered this part only after trying that: “The Game Mode is not replicated to any remote clients that join in a multiplayer game; it exists only on the server”.

Can you explain a bit how can I do it? Sorry, I’m new to this, and I’m still learning.
I’m using Advanced Sessions plugin if this matters.

Have you tried what i suggested? Because that should be the “some communication between the server and client” part. This method is called when a pawn needs to be spawned for a controller. If the player has selected his character, gender whatever, the GameMode in the server can use GetDefaultPawnClassForController to spawn the particular Pawn chosen which then gets replicated to everyone else.

2 Likes

I don’t understand how and where to use it, can you give more details? What gh0stfase1 said is not only useful in this situation, but understanding how to communicate between server and client will help me in future with other situations as well.

In your GameMode class you can override GetDefaultPawnClassForController. To do this go in your custom GameMode and click this.

override_Class

Then you add your custom logic. In this example i read from my PlayerState the boolean bFemale, that each player could set in their lobby UI for example. This could be anything an enum, a string whatever you need to identify the character for your logic.

The pseudo implementation looks like this

We basically just check the value and return either the Male or Female Character class, the rest is done by the GameMode. The GameMode will instantiate the appropriate Character and takes care of replication for us. So it is similar to your BP from your question, you just need to place it in the appropriate place. But what @gh0stfase1 wrotes is important, the concepts for multiplayer can be tricky. The GameMode exists only on the Server, this Code is run only on the server. But creating the Character is replicated on ALL clients. The Controllers exist only on the server and respective client, but the PlayerState is on all clients and server. Google for “unreal multiplayer pdf” and you find a very informative document explaining all this concepts in more detail.

2 Likes

Here’s one
UE4_Network_Compendium_by_Cedric_eXi_Neukirchen.pdf (1.6 MB)

I somehow missed the word override from your first post, and that caused a bit more confusion when I read your second post.

I have two questions about this:

  1. Isn’t PlayerState created with each level, so how can you carry it over from level to level? Inside a CustomGameInstance variable?
  2. When and where do you set the value to PlayerState (character as in my example/Female as in yours) so that it takes affect before the override of GetDefaultPawnClassForController call? I did a simple test with printing a string of local time, and on client side, the PlayerState is created after on server side the override of GetDefaultPawnClassForController is being called.

PlayerState was just an idea, you don’t have to do it, and in fact you don’t need it. So if you say it doesn’t fit timing wise, setting it on the Controller works just as well. The server and the owner of the controller replicate the controller, so having properties just between the two works in this case just fine.

Apologies, not to step on any toes but that is not going to work. It’s not that it’s in wrong it’s just incomplete. GetDefaultPawnClassForController is being called by the GameMode on the server which means it’s working with the server side version of the controller. Yes, the controller is replicated but that does not imply that all replicated variable values replicate from client to server. That would go against the server authoritative model…replication happens down, not up. If you want to effect values on the server version of anything then you must explicitely do so using server side RPCs.

Now, the issue you’re running into is that you want the connecting client to already have decided which pawn to choose BEFORE connecting. When a client attempts a connection to a server a chain of events fires off which creates the player controller, player state and pawn and hands that over to the connecting client. By time the client has a window to take action, the pawn has already been created. So what do you do? In keeping with the GetDefaulPawnClassForController theme, one solution is that you need to send the server version of the player controller what the client wants they’re pawn to be. Here’s an example implementation (albeit in C++) of exactly what you’re trying to accomplish.

Spawn Different Pawns For Players in Multiplayer - UE4: Guidebook (gg-labs.com)

Don’t let the C++ scare you if you’re not familiar…generally speaking it flows just like the BP stuff. In here the author is using GetDefaultPawnClassForController. The crucial piece that you’re missing is the push of the desired pawn to the server.

I don’t have the cycles right this second to walk you through creating that in BP, although it’s fairly straightforward. If you haven’t made any headway on it by time I’m free tonight then I can put an example together.

2 Likes

No worries, that is an important thing to point out. I just wasn’t thinking about the order here.

1 Like

Yeah, you are right about this. The players don’t need to have unique characters, which means having the character selection each time in the lobby or where else when you join a server created by someone, will only waste time, and so (at this moment at least) the character selection is done in kinda of main menu/player menu where other information about the player is showed from the server with the DB, and the character selected is saved in DB, so that the next time a players connects in the game, his/her choice will be preserved.

I noticed that when I added the time print string, everything was created before, so no matter if I were to use PlayerState/PlayerController, they will not work in this case at least because of the order they are spawned.

There is nothing to apologies, after all this forum is made to ask questions to get help. Not only that but we think in different ways, and that doesn’t mean we are wrong, sometimes there are just multiple ways to get the same result.

If you don’t mind I would love to see an example when you have the time. As I mentioned before, I’m now learning this whole multiplayer, and some things are a bit harder to get around at first, and in my case I learn a lot faster from examples.

I come from Java programming, so looking at code isn’t all that bad, even tho I didn’t used C++. That being said, I’ll still look over that article, maybe it has something else that I’ll find useful.

Yes but it’s the proper thing to do. Generally it takes time and effort to respond to posts and it can be rather irritating when someone comes in from the side after you’ve already put time into the topic. It’s a professional courtesy…after all, we tend to be developers in our primary lives :slight_smile:

I’ll throw something together tonight. You’re not far off as it is. Here’s a quick off the cuff textual rundown in case you want to try in the interim …

  1. Create a custom game mode (you have)
  2. Create a custom player controller (you have)

Starting in the controller…

  1. Add a variable to the controller which will hold your desired pawn class. Set the variable type to Pawn and choose class reference from the pull-down.
  2. Add a custom event to the controller called “ServerSendPawn”. With the event node selected, in the details panel, set the “Replicates” pull-down to “Run on Server” and check reliable. Add an input variable to the function and set the variable type to Pawn and choose class reference. Off of this event, use the input variable to the event to set the pawn variable you created in step 1. Right after that call getgamemode and off of that call RestartPlayer. Feed a reference to “self” to the input of that.
  3. Off of begin play add a branch node. Feed “IsLocalController” to the input of the branch node.
  4. Off of “yes”, call ServerSendPawn and feed it the pawn you want to have (I think you were storing this in your gameinstance)

All that little dance is doing is, if the controller is the local controller then it sends the desired pawn to the server version of the controller so it can be set and retrievable by the gamemode when the time comes. One subtle trick to note here is the call to RestartPlayer. As I mentioned in a previous post, the creation of controller, player state and pawn happens rather rapidly. There is an off chance that you could get the call to the server to set the desired pawn in time before the pawn gets created…not likely but possible. The call to RestartPlayer will simply re-trigger the pawn creation event (getting rid of the old pawn in the process). This means that there’s a chance that for a second you might see the old pawn on the client but it should quickly be replaced with the desired pawn.

Now over to the custom game mode…

  1. Override GetDefaultPawnClassForController (you have)
  2. Cast the input controller to your custom controller
  3. Off of that, get your variable you used to store the pawn and pass that as the output of the function (note: I’m purposefully leaving out error checking for the sake of typing. What @dowhilefor provided would be more proper)

…and you’re done. At a high-level the logic flow looks something like this

  1. Client connects and is given a controller, player state and pawn
  2. Once that makes it over to the client, beginplay fires on the controller which recognizes that it’s the local controller
  3. The client version of the controller reaches out to the server version of the controller in order to notify it of the desired pawn
  4. The server side controller sets the desired pawn and asks to be restarted…thus destroying the old pawn and creating the new.

There of course are all sorts of little things to add to this like what to do if the desired pawn is the pawn the controller already has? Maybe ignore the request and don’t restart the player. Little things like that but you get the picture.

3 Likes

An adjustment is needed to the above instructions. Before calling RestartPlayer you first need to destroy the pawn you have. This is due to the following section of code in AGameModeBase

Essentially if the pawn you’re controlling is “alive” then instead of killing it and spawning a new one it simply moves it to the start position. To get the code we want to execute (GetDefaultPawnClassForController) we need to destroy the current pawn first.

With that in mind, the relevant BP pieces are…

MyGameMode_BP

MyPlayerController_BP

…and the result

2 Likes

It worked, thank you!

Yeah, I’ll add my touch to it, but the example is perfect.

Also, thank you dowhilefor for your time as well!