Development Blog

Production: Lobbies, timers and Preventing players from picking up the same character in a game session

Programming
After completing the Cognito implementation, which allowed players to create accounts, the next step was to set up the database for storing player progress and achievements. However, before diving into that, there was one small but important addition needed: a lobby. This lobby would serve as a space for players to explore and experience the game while waiting for others to join.
By introducing a dedicated lobby map, another key advantage was the ability to separate concerns between the two maps. The lobby’s game mode could focus on handling the GameLift logic—specifically, initiating GameLift and storing the necessary processParameters in a custom GameInstanceSubsystem class. This subsystem would persist through seamless travel between the lobby and gameplay maps, ensuring continuity and proper session management.
Another important aspect was ensuring that both game modes inherited timer functionality. In most online games, players aren't immediately transferred from the lobby to the game map as soon as the minimum player count is reached. Instead, there's typically a countdown or pre-game phase that allows for a smoother transition. This includes giving players enough time to load into the game map properly, followed by additional timers to manage the flow back to the lobby after a match concludes. By centralizing timer logic in a shared base class, both the lobby and game modes could maintain consistent and synchronized timing behavior.
UENUM(BlueprintType)
enum class ECountdownTimerType : uint8
{
	LobbyCountdown UMETA(DisplayName = "Lobby Countdown"),
	PreGameplayCountdown UMETA(DisplayName = "PreGameplay Countdown"),
	PlayTimeCountdown UMETA(DisplayName = "Play Time"),
	PostGameplayCountdown UMETA(DisplayName = "PostGameplay Countdown"),
	NoneTypeCountdown UMETA(DisplayName = "None"),
};

UENUM()
enum class EGameplayStatus : uint8
{
	WaitingForPlayer UMETA(DisplayName = "Waiting for player"),
	PreStart UMETA(DisplayName = "Pre start"),
	Play UMETA(DisplayName = "Play"),
	PostPlay UMETA(DisplayName = "Post play"),
	SeamelessTravelling UMETA(DisplayName = "Seameleslest travelling"),
};
After successfully implementing the initial lobby functionality, the next priority was to prevent players from selecting the same character. Since each character is essential for solving the game’s puzzles, it’s crucial that no duplicates are allowed within a session. This turned out to be quite challenging, as the game runs on a dedicated server setup capable of hosting multiple game sessions.

Initially, the logic appeared to work in the editor, primarily because all players in that environment share the same local server. However, once the game was built and run in a production-like setup, the system failed—as expected. In the main menu, players have no knowledge of each other until they connect to the same AWS-hosted session, making it impossible to validate character selection at that stage.

To resolve this, a new solution is required: storing player selection data in a DynamoDB table. This database will track which characters are currently selected for each active session, enabling real-time validation and preventing duplicate picks even before players join the server.