Hi,
I could not really find a specific answer for my question in any forum which is why I list my issue here now. Basically, I want to use Unity Netcode and want to integrate my game on an AWS GameLift fleet. For testing purposes I created an ANYWHERE fleet and used the Bootstrap sample scene from unity netcode 2.1 to start with. Here, I added only one gameobject which has the following script attached:
using UnityEngine;
using System.Collections.Generic;
using Aws.GameLift.Server;
using Aws.GameLift.Server.Model;
using System;
using Unity.Netcode;
using System.IO;
using System.Threading.Tasks;
using Amazon.Lambda;
using Amazon.Lambda.Model;
using Amazon;
using System.Net;
using System.Net.Sockets;
using Amazon.GameLift;
using Amazon.Runtime;
using Amazon.GameLift.Model;
namespace Assets.scripts.Multiplayer
{
/// <summary>
/// THis is called by the server. The server must know whihc methods to call when the client wants to create aserver...
/// </summary>
public class GameLiftServerInitializer : MonoBehaviour
{
private string testToken = "MYTESTTOKENFORTHECORRESPONDINGFLEET";
private string testFleet= "MYTESTFLEETID"; // hidden here so
public bool isTest;
public LaunchGameServer launchGameServer;
private void Start()
{
Initiate();
}
public async Task Initiate()
{
// ConfigureAppSettings();
AWSConfigs.LoggingConfig.LogTo = LoggingOptions.None;
AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.Never;
AWSConfigs.LoggingConfig.LogMetrics = false;
if (Application.platform == RuntimePlatform.WindowsEditor)
{
// await GetAuthToken();
UnityEngine.Debug.Log("platform is windows and you are running the test server");
UnityEngine.Debug.Log("platform is windows Editor. This code should terminate here.");
this.enabled = false;
return;
}
else if (Application.platform == RuntimePlatform.WindowsPlayer)
{
string randomProcessId = CreateRandomProcessID();
// ConfigureLog4Net();
UnityEngine.Debug.Log("platform is windows and you are running the test server");
// Define the server parameters
string webSocketUrl = "wss://eu-central-1.api.amazongamelift.com";
string processId = randomProcessId;
string fleetId = testFleet;
string hostId = "NilsCompute"; // Replace with your registered compute name
string authToken = testToken; // Retrieve this dynamically
ServerParameters serverParameters = new ServerParameters(webSocketUrl, processId, hostId, fleetId, authToken);
var localOutcome = GameLiftServerAPI.InitSDK(serverParameters);
if (localOutcome.Success)
{
Debug.Log("GameLift SDK initialized successfully.");
}
else
{
Debug.LogError("Failed to initialize GameLift SDK.");
Application.Quit();
}
}
else if (Application.platform == RuntimePlatform.LinuxServer || Application.platform == RuntimePlatform.LinuxPlayer )
{
// Use extracted credentials
string randomProcessId = CreateRandomProcessID();
// ConfigureLog4Net();
UnityEngine.Debug.Log("platform is linux and you are running the test server");
// Define the server parameters
string webSocketUrl = "wss://eu-central-1.api.amazongamelift.com";
string processId = randomProcessId;
string fleetId = testFleet;
string hostId = "NilsCompute"; // Replace with your registered compute name
string authToken = testToken;
ServerParameters serverParameters = new ServerParameters(webSocketUrl, processId, hostId, fleetId, authToken);
var localOutcome = GameLiftServerAPI.InitSDK(serverParameters);
if (localOutcome.Success)
{
Debug.Log("GameLift SDK initialized successfully.");
}
else
{
Debug.LogError("Failed to initialize GameLift SDK.");
Application.Quit();
}
}
else if (Application.platform == RuntimePlatform.Android)
{
UnityEngine.Debug.Log("You are on android. This code is part of the server and thus it will not run here. Start disabling class");
this.enabled = false;
return;
}
else
{
Debug.LogError("Unsupported platform for GameLift server.");
this.enabled = false;
Application.Quit();
return;
}
UnityEngine.Debug.Log("Initializing gamelift");
InitializeGameLift();
}
string CreateRandomProcessID()
{
int length = 10;
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
System.Random random = new System.Random();
char[] stringChars = new char[length];
for (int i = 0; i < length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
return new String(stringChars);
}
private void InitializeGameLift()
{
UnityEngine.Debug.Log("Initializing gamleift with execution methods");
int availablePort = FindAvailablePort();
if (!Directory.Exists("logs"))
{
Directory.CreateDirectory("logs");
}
ProcessParameters processParams = new ProcessParameters(
onStartGameSession: OnStartGameSession, // gets the parsed attributes by the host and sets them on the server side. Calls setup server which launches netcode server
onProcessTerminate: OnProcessTerminate,
onHealthCheck: OnHealthCheck,
onUpdateGameSession: UpdateGameSession,
port: availablePort,
logParameters: new LogParameters(new List<string>()
{
"logs/server.log",
"logs/error.log"
})
);
var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParams);
if (processReadyOutcome.Success)
{
Debug.Log("Gamelift setup successful.");
}
else
{
Debug.LogError("Failed to initialize GameLift SDK.");
Application.Quit();
}
}
private int FindAvailablePort()
{
const int maxAllowedPort = 60000; // GameLift's maximum allowed port
const int minPort = 1027; // Minimum port to avoid conflicts with reserved system ports
int port = 0;
TcpListener listener = null;
try
{
for (int testPort = minPort; testPort <= maxAllowedPort; testPort++)
{
listener = new TcpListener(IPAddress.Loopback, testPort);
// Attempt to bind to the test port
listener.Start();
port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
Debug.Log($"Found available port: {port}");
return port;
}
}
catch (SocketException ex)
{
Debug.LogError($"Error while searching for available port: {ex.Message}");
}
finally
{
listener?.Stop();
}
throw new Exception($"Failed to find an available port in the range {minPort}-{maxAllowedPort}");
}
// this is called initially...
private async void OnStartGameSession(Aws.GameLift.Server.Model.GameSession gameSession)
{
Debug.Log($"Starting game session: {gameSession.GameSessionId}");
// Retrieve GameProperties
Dictionary<string, string> gameProperties = new Dictionary<string, string>();
foreach (var prop in gameSession.GameProperties)
{
gameProperties[prop.Key] = prop.Value;
}
// Signal that the server is ready - after that: response.GameSession.Status == GameSessionStatus.ACTIVE
GameLiftServerAPI.ActivateGameSession();
UnityEngine.Debug.Log("session is activated");
if (gameProperties.ContainsKey("Group"))
{
string group = gameProperties["Group"];
string sessionId = gameSession.GameSessionId;
await UpdateGameSessionInDynamoDB(gameSession.GameSessionId, group); // this tells the server aws that the corresponding gamesession for the corresponding group is active.
}
else
{
UnityEngine.Debug.Log("Could not find group in game properties. Automatic matching not possible");
}
if (NetworkManager.Singleton == null)
{
Debug.LogError("Network Manager is null");
return;
}
// Start Unity Netcode server
Debug.Log("Starting Unity Netcode server...");
if (!NetworkManager.Singleton.IsServer)
{
NetworkManager.Singleton.StartServer();
Debug.Log("Unity Netcode server started successfully.");
}
else
{
Debug.Log("Unity Netcode server is already running.");
}
}
private void OnProcessTerminate()
{
//h here disconnect lambda function...
if (NetworkManager.Singleton.IsServer)
{
NetworkManager.Singleton.Shutdown();
}
Debug.Log("Process termination requested by GameLift.");
var ProcessEndingOutcome = GameLiftServerAPI.ProcessEnding();
Application.Quit();
}
private bool OnHealthCheck()
{
Debug.Log("Health check received.");
// GameLiftServerAPI.ProcessEnding();
return true;
}
void UpdateGameSession(UpdateGameSession updateGameSession)
{
UnityEngine.Debug.Log("OnUpdateGameSession called");
return;
}
public async Task UpdateGameSessionInDynamoDB(string sessionId, string group, string status = "ACTIVE", string LambdaFunctionName = "headset_addSessionToTable")
{
// Initialize the Lambda client
/// MAKE SURE TO ATTACH IAM ROLE TO GAMELIFT
var lambdaClient = new AmazonLambdaClient(); // since this runs on aws the general environment variables should be set...
// Create the payload
var payload = new
{
sessionId = sessionId,
group = group,
status = status,
creationTime = DateTime.UtcNow.ToString("o") // ISO 8601 format
};
// Invoke the Lambda function
var request = new InvokeRequest
{
FunctionName = LambdaFunctionName,
Payload = System.Text.Json.JsonSerializer.Serialize(payload)
};
try
{
var response = await lambdaClient.InvokeAsync(request);
// Read the response
using var reader = new System.IO.StreamReader(response.Payload);
var responseBody = await reader.ReadToEndAsync();
Console.WriteLine($"Lambda response: {responseBody}");
}
catch (Exception ex)
{
Console.WriteLine($"Error invoking Lambda function: {ex.Message}");
throw;
}
}
}
}
When I build the sccene as linux project I can see that the gamelift SDK is successfully initialized and that it also calls ProcessReady successfully. Thus, I have a lambda function on aws gamelift which just creates a gamesession on this fleet. As soon as I invoke it the method OnStartGameSession is called which Activates the session and there I integrated the logic for starting the Netcode server with : NetworkManager.Singleton.StartServer() . However, then I immediately get about 50 repeating error messages of this kind:
NullReferenceException: Object reference not set to an instance of an object
at Unity.Netcode.NetworkManager.NetworkUpdate (Unity.Netcode.NetworkUpdateStage updateStage) [0x000aa] in :0
at Unity.Netcode.NetworkUpdateLoop.RunNetworkUpdateStage (Unity.Netcode.NetworkUpdateStage updateStage) [0x00021] in :0
at Unity.Netcode.NetworkUpdateLoop+NetworkPostLateUpdate+<>c.b__0_0 () [0x00000] in :0
I am not exactly sure why because if i just ignore this class and call StartServer separately in a linux build it of course just starts it. So, I wanted to ask what is your take on it. I guess I just do something wrong, but I could also not find any kind of sample project on GitHub where someone tried to do that.