GameMode references PlayerControllers. GameMode only runs on the server.
PlayerControllers support “run on server” RPCs.
The only way this can work, is if there’s a player controller on the server, for each player.
Additionally, each local player gets a local player controller.
Additionally, remote players pawns get replicated to the local player, but don’t get a full PlayerController on the client.
In a game with 3 players, and a dedicated server, I would expect the following to be true:
Server:
PlayerController for Player 1 (server copy)
PlayerController for Player 2 (server copy)
PlayerController for Player 3 (server copy)
Client 1:
PlayerController for Player 1 (player copy)
Client 2:
PlayerController for Player 2 (player copy)
Client 3:
PlayerController for Player 3 (player copy)
On a listen server, I would expect this to be true:
Client 1 (listen server):
PlayerController for Player 1 (player and server copy)
PlayerController for Player 2 (server copy)
PlayerController for Player 3 (server copy)
Client 2 (player):
PlayerController for Player 2
Client 3 (player):
PlayerController for Player 3
The PlayerController will have the same class on client and server, else replication and Run On Server RPCs won’t work.
You can test whether a particular player controller is locally controlled (has an input device attached) using IsLocalPlayerController()
PlayerControllers on servers automatically get movement events replicated from the clients to the server, other more special events may need to be explicitly replicated. (I think movement replication is handled through the MovementComponent, not through the Controller, though!)
The APlayerController
subclass objects can get re-created when loading new maps, no matter whether you use “seamless travel” or just plain new connections. See AGameModeBase::OnSwapPlayerControllers()
.
This is easy to verify. Make a new input event, call it Test Event, and make it in turn invoke a run-on-server RPC event:
Start a listen server with a second client.
When you invoke the input event on the second client, the server will print a message from “player controller 1.”
When you invoke the input event on the server, the server will print the invocation message from “player controller 0.”
You will note that the local listen server player will ONLY print the “sending message” message, it won’t actually print the “running on server” message. Because, if you’re already running on server, the “send to server” message gets silently ignored, so you have to special-case this in each and every case where this comes up, which is an annoying behavior IMO. Would be better to just make it transparent, and wouldn’t cost anything. If I really didn’t need to send that message to myself, then THAT what I can special-case.
Also, the full “construct, begin play” process does not happen for the second player controller on the server; it goes through a slimmer start-up process. This is annoying as well, because making sure everything gets set up the same on client and server side requires additional hooks (on possess only runs on server, so that’s a good hook to use.)
Anyway, I haven’t found a good description of the exact semantics of when each of those different hooks are called, in which order, on which kinds of servers. This is all what I’ve found out through experimentation, which really shouldn’t be the way to learn, because I don’t know if this is the intended “only true path” or if it’s just “what happens to occur for me in this case.” If you know of a good networking document that actually lays out the full expectations and guarantees here, I’d love to read it!