Development Blog

Pre Production: Joining the server with the use of Unreal Engine Widgets

Programming Milestone Pre-Production
After testing AWS Lambda functions, the next step was to develop a method for creating game sessions and player sessions, ensuring players can successfully join the game server.
The first step was to create a Lambda function that enables users to initiate a game session. To prevent unnecessary session creation, two Lambda functions were required. The first is triggered when a player attempts to start the game, either retrieving available sessions or creating a new one if none exist. The second function, which is not yet implemented in Unreal, is responsible for creating a game session when the user chooses to do so. With guidance from the AWS documentation, the implemented Lambda function looks like this:
import { GameLiftClient, ListFleetsCommand, DescribeFleetAttributesCommand, DescribeGameSessionsCommand ,  CreateGameSessionCommand } from "@aws-sdk/client-gamelift";

const generateRandomName = () => {
  return Math.random().toString(36).substring(2, 8).toUpperCase();
};

export const handler = async (event) => {
  const gameLiftClient = new GameLiftClient({ region: process.env.REGION });

  try {
    // Step 1: Get Fleet IDs
    const listFleetsCommand = new ListFleetsCommand({ Limit: 10 });
    const listFleetsResponse = await gameLiftClient.send(listFleetsCommand);
  
    const fleetIds = listFleetsResponse.FleetIds || [];
  
    if (fleetIds.length === 0) {
      throw new Error("No fleets found");
    }
  
    // Step 2: Get Fleet Attributes & Find an ACTIVE Fleet
    const describeFleetAttributesCommand = new DescribeFleetAttributesCommand({
      FleetIds: fleetIds,
      Limit: 10
    });
    const describeFleetAttributesResponse = await gameLiftClient.send(describeFleetAttributesCommand);
  
    const fleetAttributes = describeFleetAttributesResponse.FleetAttributes || [];
    let fleetId;
  
    for (const fleetAttribute of fleetAttributes) {
      if (fleetAttribute.Status === "ACTIVE") {
        fleetId = fleetAttribute.FleetId;
        break;
      }
    }
  
    if (!fleetId) {
      throw new Error("No ACTIVE fleets found");
    }
  
    // Step 3: Get Active Game Sessions
    const describeGameSessionCommand = new DescribeGameSessionsCommand({
      FleetId: fleetId,
      Limit: 10,
      StatusFilter: "ACTIVE"
    });
    const describeGameSessionResponse = await gameLiftClient.send(describeGameSessionCommand);
  
    let gameSessions = [];
  
    // Step 4: Filter available game sessions
    for (const session of describeGameSessionResponse.GameSessions || []) {
      if (
        session.CurrentPlayerSessionCount < session.MaximumPlayerSessionCount &&
        session.Status === "ACTIVE" &&
        session.PlayerSessionCreationPolicy === "ACCEPT_ALL"
      ) {
        gameSessions.push(session);
      }
    }
  
    // Step 5: If no available game sessions, create one
    if (gameSessions.length === 0) {
      console.log("No available game sessions found. Creating a new one...");
  
      const createGameSessionCommand = new CreateGameSessionCommand({
        FleetId: fleetId,
        Name: generateRandomName(), 
        GameProperties: [{ Key: "player_type_available", Value: "tall" }],
        MaximumPlayerSessionCount: 3,
        Location: "custom-home-desk",
        PlayerSessionCreationPolicy: "ACCEPT_ALL"
      });
  
      const createGameSessionResponse = await gameLiftClient.send(createGameSessionCommand);
  
      if (!createGameSessionResponse.GameSession) {
        throw new Error("Game session creation failed");
      }
  
      gameSessions.push(createGameSessionResponse.GameSession);
    }
  
    return {
      GameSessions: gameSessions
    };
  } catch (e) {
    console.error("Error:", e);
    return { error: e.message };
  }

};
With this Lambda function set up alongside an API Gateway POST request, the next step was to develop the UI in Unreal. The goal was to replicate the design provided by the design team as closely as possible (See image below made by Sohail)
The temporary UI in Unreal consisted of three main components. First, an initial widget with a button to search for or create a game session. Second, a secondary UI featuring a scroll box that displayed all available game sessions, allowing players to select and join one. Lastly, a status message UI that kept players informed about the progress of joining the server, preventing them from repeatedly pressing the join button on multiple sessions.
The next step was to create a Lamda function that allowed to create a player session:
import { GameLiftClient, CreatePlayerSessionCommand } from "@aws-sdk/client-gamelift";

export const handler = async (event) => {
  try {
    const gameLiftClient = new GameLiftClient({region: process.env.REGION});
    const createPlayerSessionInput = { 
      GameSessionId: event.gameSessionId, // required
      PlayerId: event.playerId, // required
      PlayerData: event.playerData,
      Location: "custom-home-desk" // remove for EC2
    };
    const createPlayerSessionCommand = new CreatePlayerSessionCommand(createPlayerSessionInput);
    const createPlayerSessionresponse = await gameLiftClient.send(createPlayerSessionCommand);
    return createPlayerSessionresponse.PlayerSession;
    
  } catch (error) {
    return error;
  }
};
And then complete the code in C++ to call those functions when needed and display the correct UI during the process, These changes can be seen in this merge request.
The result of this implementation has been currently tested using an aniwhere fleets (Home Desktop PC used for the server) and can be seen in the video below: