Proper UI/Object setup for client/server?

This has been frustrating me for two weeks, now. My project utilizes the Steam OSS, and I have session hosting/searching/joining working, but I’m totally stuck on trying to manage objects after that point.

Here’s my project architecture, thus far:

  • To aid in the centralization of code, ALL of my maps have an EventManager actor blueprint added to them… tasked with distinguishing which map we are in, creating & storing widget blueprint references, and displaying the correct ones based upon the current map. There are also a number of RPC events here to aid in the process of UI updates, though I’m not sure this is good practice.
  • Game launches into EntryMap, with EntryGM being the default game mode. This game mode displays the main menu, which includes options to join or host a game. Choosing Join tells the EventManager to start searching for sessions. Choosing Host fires off an event in EventManager, which creates a session and (upon successful creation) opens the LobbyMap with listen as a parameter.
  • LobbyMap has LobbyGM as the default game mode, as well as LobbyGS (GameState), LobbyPS (PlayerState) and LobbyPC (PlayerController). This game mode displays a Lobby widget, which is supposed to allow players to join a match, set match settings, chat with each other, state they’re ready to play, etc…

This is where I’m stuck. I obviously need to know when a player joins/leaves the lobby, and then update the UI accordingly, BUT… Who owns which PCs? Who should create what (and within which classes)? How should lobby data be replicated, which RPCs should be used (and where) to keep the UI synchronized across the server and all clients? I’ve tried using UniqueNetId to distinguish who is who, but I can’t find the proper place to gather this information. I keep juggling my implementations around, running into NULL objects, playerstates, widget references, etc on either the client, server, or both.

Someone please point me in the right direction… I’ve read so much documentation and code examples, but still feel like I’m at square one.

Yes, but GameMode only exists on the server. I’ve tried overriding Login to get a unique id and send a multicast RPC to let all clients know “This PlayerID joined the lobby”, but I ran into troubles with this implementation since it gets run on the server (FOR the server) before the lobby widget can be created. So it works for clients, but the server won’t have a valid/visible lobby widget.

Are you saying I should create a replicated variable in the gamestate class to hold all player ids/data?

Or are you talking about AGameStateBase::PlayerArray?

Thank you so much! I was actually trying to read this earlier today but the URL was outdated. Should be a big help.
EDIT: You were right. It answered most of my questions. Looks like I should be utilizing AGameState a lot more.

Somewhere along the way, I fell short of exploring AGameSession as an option. I think it’s because I’m using the Advanced Sessions plugin, which I’m pretty sure handles the sessions for you. Perhaps I’ll need to modify the plugin to utilize my own custom session class as a base class. I’ll need to do more research on this. Thanks for the info & suggestions!

GameMode is what creates the PlayerConrollers and gives them a Pawn. GameState should hold all the PlayerControllers.

This is a huge topic, I’m going to try my best to set you in the right direction here based on my experience.

To aid in the centralization of code, ALL of my maps have an EventManager actor blueprint added to them… tasked with distinguishing which map we are in, creating & storing widget blueprint references, and displaying the correct ones based upon the current map. There are also a number of RPC events here to aid in the process of UI updates, though I’m not sure this is good practice.

Going to first comment on this design. You should be utilizing a combination of UGameInstance and AHUD for this. UGameInstance for anything menu related. AHUD for anything game mode specific.

I’m going to try to break this down question by question for you.

I obviously need to know when a player joins/leaves the lobby, and then update the UI accordingly, BUT… Who owns which PCs?

The PlayerController only exists in two places. The Authority (Server or Hosting Client) and the Owning Client. Any other connected clients are unaware of all other PlayerControllers but their own. The PlayerController cannot be used directly to determine when a player leaves or joins on a Non-Authority. What you can rely on is the PlayerState. PlayerState exists on both the Authority and Non-Authority across all connections. When a PlayerState is created the GameState is notified via AddPlayerState. This is a virtual function and can be overridden. Alternatively you can utilize the Broadcast functionality of GameModeBase which sends a message to each connected PlayerController.

Who should create what (and within which classes)?

I really want to answer this one but need a little more context. A lot of this is handled by the engine for you if not all of it. It really comes down to what you’re trying to create, is it a custom class or are you asking about one of the many built in classes. I can answer the latter if you give specifics.

How should lobby data be replicated, which RPCs should be used (and where) to keep the UI synchronized across the server and all clients?

Make use of the GameState, PlayerState, and Pawn classes. These are the only classes built into the engine that exist across all connections that hold player information. Focus primarily on those first two.

Edit: To add on to that last point also look into AGameSession. Based on your post you may want to spend some time looking at the UT source code and how they handle lobbies.

Dumdidum droping this here. Pretty much explains most stuff you asked. How you mange what you need is Up to you and your responsibility to structure things the way you want. Everything technical the Compedium covers.