// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // GameLift server configuration and callbacks using UnityEngine; using Aws.GameLift.Server; using System.Collections.Generic; using Aws.GameLift.Server.Model; public class GameLift : MonoBehaviour { #if SERVER //Set the port that your game service is listening on for incoming player connections public int listeningPort = -1; // Game preparation state private bool gameSessionInfoReceived = false; private float waitingForPlayerTime = 0.0f; // Game state private bool gameStarted = false; private string gameSessionId; public string GetGameSessionID() { return gameSessionId; } // Matchmaker data MatchmakerData matchmakerData; // StatsD client for sending custom metrics to CloudWatch through the local StatsD agent private SimpleStatsdClient statsdClient; public SimpleStatsdClient GetStatsdClient() { return statsdClient; } // Backfill ticket ID (received and updated on game session updates) string backfillTicketID = null; // Game session timer, we don't want to run over 20 minutes private float gameSessionTimer = 0.0f; // Get the port to host the server from the command line arguments private int GetPortFromArgs() { int defaultPort = 1935; int port = defaultPort; //Use default is arg not provided string[] args = System.Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length; i++) { Debug.Log("ARG " + i + ": " + args[i]); if (args[i] == "-port") { port = int.Parse(args[i + 1]); } } return port; } //Respond to new game session activation request. GameLift sends activation request //to the game server along with a game session object containing game properties //and other settings. void OnStartGameSessionDelegate(GameSession gameSession) { //Start waiting for players this.gameSessionInfoReceived = true; this.gameSessionId = gameSession.GameSessionId; //Set the game session tag (CloudWatch dimension) for custom metrics string justSessionId = this.gameSessionId.Split('/')[2]; this.statsdClient.SetCommonTagString("#gamesession:" + justSessionId); //Send session started to CloudWatch just for testing this.statsdClient.SendCounter("game.SessionStarted", 1); //Log the session ID System.Console.WriteLine("Game Session ID: " + justSessionId); System.Console.WriteLine("Matchmaker data New session:" + gameSession.MatchmakerData); this.matchmakerData = MatchmakerData.FromJson(gameSession.MatchmakerData); this.backfillTicketID = this.matchmakerData.AutoBackfillTicketId; // Activate the session GameLiftServerAPI.ActivateGameSession(); } //Respond to game session updates void OnUpdateGameSessionDelegate ( UpdateGameSession updateGameSession ) { System.Console.WriteLine("backfill ticked ID update session:" + updateGameSession.BackfillTicketId); if (updateGameSession.BackfillTicketId != null) { System.Console.WriteLine("Updating backfill ticked ID: " + updateGameSession.BackfillTicketId); this.backfillTicketID = updateGameSession.BackfillTicketId; } } //OnProcessTerminate callback. GameLift invokes this callback before shutting down //an instance hosting this game server. It gives this game server a chance to save //its state, communicate with services, etc., before being shut down. void OnProcessTerminateDelegate() { //In this case, we simply tell GameLift we are indeed going to shut down. GameLiftServerAPI.ProcessEnding(); Application.Quit(); } //This is the HealthCheck callback. //GameLift invokes this callback every 60 seconds or so. //Here, a game server might want to check the health of dependencies and such. //Simply return true if healthy, false otherwise. //The game server has 60 seconds to respond with its health status. //GameLift will default to 'false' if the game server doesn't respond in time. bool OnHealthCheckDelegate() { //In this case, we're always healthy as long as the process is running return true; } // Called when the monobehaviour is created public void Awake() { //Initiate the simple statsD client this.statsdClient = new SimpleStatsdClient("localhost", 8125); //Get the port from command line args listeningPort = this.GetPortFromArgs(); System.Console.WriteLine("Will be running in port: " + this.listeningPort); //InitSDK establishes a local connection with the Amazon GameLift agent to enable //further communication. var initSDKOutcome = GameLiftServerAPI.InitSDK(); if (initSDKOutcome.Success) { ProcessParameters processParameters = new ProcessParameters( this.OnStartGameSessionDelegate, this.OnUpdateGameSessionDelegate, this.OnProcessTerminateDelegate, this.OnHealthCheckDelegate, //Here, the game server tells GameLift what port it is listening on for incoming player //connections. We will use the port received from command line arguments listeningPort, new LogParameters(new List<string>() { //Let GameLift know where our logs are stored. We are expecting the command line args to specify the server with the port in log file "/local/game/logs/myserver"+listeningPort+".log" })); //Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParameters); if (processReadyOutcome.Success) { print("ProcessReady success."); } else { print("ProcessReady failure : " + processReadyOutcome.Error.ToString()); } } else { print("InitSDK failure : " + initSDKOutcome.Error.ToString()); } } // Ends the game session for all and disconnects the players public void TerminateGameSession() { System.Console.WriteLine("Terminating Game Session"); //Cleanup (not currently relevant as we just terminate the process) GameObject.FindObjectOfType<Server>().DisconnectAll(); this.gameStarted = false; // Stop the backfilling if (this.backfillTicketID != null) { System.Console.WriteLine("Stopping backfill"); var stopBackfill = new StopMatchBackfillRequest(); stopBackfill.TicketId = this.backfillTicketID; stopBackfill.MatchmakingConfigurationArn = this.matchmakerData.MatchmakingConfigurationArn; stopBackfill.GameSessionArn = GameLiftServerAPI.GetGameSessionId().Result; GameLiftServerAPI.StopMatchBackfill(stopBackfill); } // Terminate the process following GameLift best practices. A new one will be started automatically System.Console.WriteLine("Terminating process"); GameLiftServerAPI.ProcessEnding(); Application.Quit(); } public void StartGame() { System.Console.WriteLine("Starting game"); this.gameStarted = true; } public bool GameStarted() { return this.gameStarted; } // Called by Unity once a frame public void Update() { // Wait for players to join for 5 seconds max if(this.gameSessionInfoReceived && !this.gameStarted) { this.waitingForPlayerTime += Time.deltaTime; if(this.waitingForPlayerTime > 5.0f) { System.Console.WriteLine("No players in 5 seconds from starting the game, terminate game session"); this.waitingForPlayerTime = 0.0f; this.gameSessionInfoReceived = false; this.TerminateGameSession(); } } // Backup mechanism: Terminate any sessions that have run over 20 minutes as our game typically lasts for 5 minutes max if(this.gameStarted) { this.gameSessionTimer += Time.deltaTime; if (this.gameSessionTimer > 1200.0f) { System.Console.WriteLine("Reached max game session length (20 minutes). Terminate session."); this.waitingForPlayerTime = 0.0f; this.gameSessionTimer = 0.0f; this.gameSessionInfoReceived = false; this.TerminateGameSession(); } } } void OnApplicationQuit() { //Make sure to call GameLiftServerAPI.Destroy() when the application quits. //This resets the local connection with GameLift's agent. GameLiftServerAPI.Destroy(); } #endif }