There are a couple of factors here, and it’s unfortunate Epic does not make this more reliable out of box. I don’t know if these are all the issues you’re encountering, or some but not others, so here’s my brain dump.
First problem is the most complex one. ServerTravel, unless marked as seamless, means players completely disconnect from the server. From their side it looks like the server died, on the server side it is completely shutting down players and playerstates and rebooting into the next map. When players “realize” the server is back “online” in the new map, they completely rejoin the server as if they’re brand new players. So they go through the whole rejoin and reconnect process. The server will attempt to re-map/restore each of the players and their respective player states that they had previously, but the replication is done completely from scratch as if they had just joined a brand new server host.
Someone above mentioned the “CopyFrom” override, that I believe is what you need to use to restore information in custom overrides of APlayerState. If you were to enable seamless travel, none of that is necessary since players retain their original state instance. Unfortunately, you CANNOT enable “seamless travel” when playing in the Editor. It simply isn’t supported. So for a lobby to gameplay transition, you either never enable seamless travel to support the editor, or you develop and maintain 2 seperate workflows. The timing and issues you may encounter with seamless travel are vastly different from without it enabled. Headaches!
Now, what about the “validity” of the PlayerState? The timing of replication of these properties on structures like PlayerController are not guaranteed in the same order every time players connect. Which is very frustrating. I’ve seen PlayerState ready on BeginPlay, and then on the next run of the game, it’s not ready by BeginPlay. Sometimes it depends on who connects first, if anything is happening in the map (like join in progress in the middle of a match), it can change just based on the performance of your machine or network if its remote. This also occurs if you’re listening for OnPlayerJoin in the GameState, and the PlayerState may be valid but GetPawn may or may not be ready and linked up by the time that function triggers. Even in Unreal Engine’s Lyra sample project, they make note of the issue. If I remember offhand, they wait for the PlayerState to fully replicate and be valid along with the Player Pawn/Character before flagging the player is “ready” and firing event delegates. You have to either copy this or built out your own system so this initialization ordering is guaranteed between loads or even just playing in the editor. Fun!
Third issue you’ve touched upon. You mentioned the priority of PlayerState, that’s probably best to make highest priority. If there are too many other actors with higher priority on load, my guess without looking at the engine code is that it will push those higher priority actors first and maybe the PlayerState gets the short straw. By that I mean it simply doesn’t get pushed down if the other actors are “cutting in line” thanks to priority ordering and throttling. But also, depending on what you do in the PlayerState, you may want to update the frequency of updates. In Lyra, they ramp NetUpdateFrequency. They do it in LyraPlayerState -
// AbilitySystemComponent needs to be updated at a high frequency.
NetUpdateFrequency = 100.0f;
Lastly, “Run Under One Process”. This is PIE/play in editor specific. There’s a magical setting in the Play settings that when checked, will load multiple client players and the server under the same Unreal Editor process instance. This is very fast and efficient for iteration when developing. And rapid testing.
However, you’re gambling with this setting enabled because the issues and timings mentioned above will be different than when you play the game standalone. It’s very similar to not having latency, because it’s all locally connecting the players under one process. Speedy, but you lose observability of these mentioned issues. I’ve encountered many issues where logic running in BeginPlay of a player character blueprint would be valid with this option on. But then break and fail when “run under one process” is turned OFF. PlayerState checking is one of them, if not the most common one. To address this, I’ve overridden the OnRep_PlayerState to add a delegate event that Blueprints can listen to if they ever need to talk to playerstate from the Character’s Pawn base class.
Personally I intend to eventually do something similar to Lyra where all of this initialization ordering is handled in lockstep. It’s far too hazardous to leave things to chance between server travel, timing of network update ordering and prioritization under the hood. If someone goes and updates the priority of an actor and that messes with the initialization order of a player connecting and their PlayerState, that’s a big problem as you’ve encountered. I don’t find it acceptable.