// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using UnityEngine; using System.Collections.Generic; using System.Collections; using Amazon.CognitoIdentity; using System.Net.Http; using System.Threading.Tasks; using System; using UnityEngine.UI; using UnityEngine.SceneManagement; using Amazon.CognitoIdentity.Model; // *** MAIN CLIENT CLASS FOR MANAGING CLIENT CONNECTIONS AND MESSAGES *** public class Client : MonoBehaviour { // Prefabs for the player and enemy objects referenced from the scene object public GameObject characterPrefab; public GameObject enemyPrefab; // Reference to the Start Game and restart Buttons public Button startGameButton; private bool gameStartRequested = false; public Button restartButton; // NOTE: DON'T EDIT THESE HERE, as they are overwritten by values in the Client GameObject. Set in Inspector instead public string apiEndpoint = "https:// latencies = new Dictionary(); // List of enemy players private List enemyPlayers = new List(); //We get events back from the NetworkServer through this static list public static List messagesToProcess = new List(); private float updateCounter = 0.0f; //Cognito credentials for sending signed requests to the API public static Amazon.Runtime.ImmutableCredentials cognitoCredentials = null; public static string cognitoID = null; // Helper function check if an enemy exists in the enemy list already private bool EnemyPlayerExists(int clientId) { foreach(NetworkPlayer player in enemyPlayers) { if(player.GetPlayerId() == clientId) { return true; } } return false; } // Helper function to find and enemy from the enemy list private NetworkPlayer GetEnemyPlayer(int clientId) { foreach (NetworkPlayer player in enemyPlayers) { if (player.GetPlayerId() == clientId) { return player; } } return null; } async Task SendHTTPSPingRequest(string requestUrl) { try { //First request to establish the connection var request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(requestUrl), }; // Execute the request var client = new HttpClient(); var resp = await client.SendAsync(request); // We measure the average of second and third requests to get the TCP latency without HTTPS handshake var startTime = DateTime.Now; request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(requestUrl), }; resp = await client.SendAsync(request); request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(requestUrl), }; resp = await client.SendAsync(request); // Total time var totalTime = (DateTime.Now - startTime).TotalMilliseconds / 2.0; return totalTime; } catch(Exception e) { print("Error reaching the endpoint " + requestUrl + ", setting latency to 1 second"); return 1000.0; } } void MeasureLatencies() { // We'll ping the two Regions we are using, you can extend to any amount var region1 = this.regionString; var region2 = this.secondaryLocationRegionString; // Check latencies to Regions by pinging DynamoDB endpoints (they just report health but we use them here for latency) var response = Task.Run(() => this.SendHTTPSPingRequest("https://dynamodb."+ region1 + ".amazonaws.com")); response.Wait(1000); // We'll expect a response in 1 second string latency1 = "Latency " + region1 + ": " + response.Result + " ms"; print(latency1); this.latencies.Add(region1, response.Result); response = Task.Run(() => this.SendHTTPSPingRequest("https://dynamodb." + region2 + ".amazonaws.com")); response.Wait(1000); // We'll expect a response in 1 second string latency2 = "Latency " + region2 + ": " + response.Result + " ms"; print(latency2); this.latencies.Add(region2, response.Result); // Set the UI text FindObjectOfType().SetLatencyInfo(latency1, latency2); } // Called when restart button is clicked public void Restart() { this.networkClient.Disconnect(); SceneManager.LoadScene(0); } // Called by Unity when the Gameobject is created void Start() { this.startGameButton.onClick.AddListener(StartGame); this.restartButton.onClick.AddListener(Restart); #if BOTCLIENT // Bots will start automatically System.Console.WriteLine("BOT: Start connecting immediately"); this.StartGame(); #endif } // Called when Start game button is clicked void StartGame() { if (!this.gameStartRequested) { this.startGameButton.gameObject.SetActive(false); this.gameStartRequested = true; FindObjectOfType().SetTextBox("Setting up Client.."); // Get the Region enum from the string value this.region = Amazon.RegionEndpoint.GetBySystemName(regionString); Debug.Log("My Region endpoint: " + this.region); // Check if we have stored an identity and request credentials for that existing identity Client.cognitoID = PlayerPrefs.GetString("CognitoID", null); if (Client.cognitoID != null && Client.cognitoID != "") { Debug.Log("Requesting credentials for existing identity: " + Client.cognitoID); var response = Task.Run(() => GetCredentialsForExistingIdentity(Client.cognitoID)); response.Wait(5000); Client.cognitoID = response.Result.IdentityId; Client.cognitoCredentials = new Amazon.Runtime.ImmutableCredentials(response.Result.Credentials.AccessKeyId, response.Result.Credentials.SecretKey, response.Result.Credentials.SessionToken); } // Else get a new identity else { Debug.Log("Requesting a new playeridentity as none stored yet."); CognitoAWSCredentials credentials = new CognitoAWSCredentials( this.identityPoolID, this.region); Client.cognitoCredentials = credentials.GetCredentials(); Client.cognitoID = credentials.GetIdentityId(); Debug.Log("Got Cognito ID: " + credentials.GetIdentityId()); // Store to player prefs and save for future games PlayerPrefs.SetString("CognitoID", Client.cognitoID); PlayerPrefs.Save(); } // Get latencies to regions this.MeasureLatencies(); // Connect to the server now that we have our identity, credentials and latencies StartCoroutine(ConnectToServer()); } } // Retrieves credentials for existing identities async Task GetCredentialsForExistingIdentity(string identity) { // As this is a public API, we call it with fake access keys AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient("A","B",this.region); var resp = await cognitoClient.GetCredentialsForIdentityAsync(identity); return resp; } // Update is called once per frame void Update() { if (this.localPlayer != null) { #if BOTCLIENT this.BotUpdate(); #endif // Process any messages we have received over the network this.ProcessMessages(); // Only send updates 20 times per second to avoid flooding server with messages this.updateCounter += Time.deltaTime; if (updateCounter < 0.05f) { return; } this.updateCounter = 0.0f; // Send current move command for server to process this.SendMove(); // Receive new messages this.networkClient.Update(); } } private void BotUpdate() { #if BOTCLIENT this.botSessionTimer -= Time.deltaTime; if (this.botSessionTimer <= 0.0f) { System.Console.WriteLine("BOT: Restarting session."); this.Restart(); } #endif } // Do matchmaking and connect to the server endpoint received // This is a coroutine to simplify the logic and keep our UI updated throughout the process IEnumerator ConnectToServer() { FindObjectOfType().SetTextBox("Connecting to backend.."); yield return null; // Start network client and connect to server this.networkClient = new NetworkClient(); // We will wait for the matchmaking and connection coroutine to end before creating the player yield return StartCoroutine(this.networkClient.DoMatchMakingAndConnect(this.latencies)); if (this.networkClient.ConnectionSucceeded()) { // Create character this.localPlayer = new NetworkPlayer(0); this.localPlayer.Initialize(characterPrefab, new Vector3(UnityEngine.Random.Range(-5,5), 1, UnityEngine.Random.Range(-5, 5))); this.localPlayer.ResetTarget(); this.networkClient.SendMessage(this.localPlayer.GetSpawnMessage()); } yield return null; } // Process messages received from server void ProcessMessages() { List justLeftClients = new List(); List clientsMoved = new List(); // Go through any messages to process foreach (SimpleMessage msg in messagesToProcess) { // Own position if (msg.messageType == MessageType.PositionOwn) { this.localPlayer.ReceivePosition(msg, this.characterPrefab); } // players spawn and position messages else if (msg.messageType == MessageType.Spawn || msg.messageType == MessageType.Position || msg.messageType == MessageType.PlayerLeft) { if (msg.messageType == MessageType.Spawn && this.EnemyPlayerExists(msg.clientId) == false) { //Debug.Log("Enemy spawned: " + msg.float1 + "," + msg.float2 + "," + msg.float3 + " ID: " + msg.clientId); NetworkPlayer enemyPlayer = new NetworkPlayer(msg.clientId); this.enemyPlayers.Add(enemyPlayer); enemyPlayer.Spawn(msg, this.enemyPrefab); } else if (msg.messageType == MessageType.Position && justLeftClients.Contains(msg.clientId) == false) { //Debug.Log("Enemy pos received: " + msg.float1 + "," + msg.float2 + "," + msg.float3); //Setup enemycharacter if not done yet if (this.EnemyPlayerExists(msg.clientId) == false) { Debug.Log("Creating new enemy with ID: " + msg.clientId); NetworkPlayer newPlayer = new NetworkPlayer(msg.clientId); this.enemyPlayers.Add(newPlayer); newPlayer.Spawn(msg, this.enemyPrefab); } // We pass the prefab with the position message as it might be the enemy is not spawned yet NetworkPlayer enemyPlayer = this.GetEnemyPlayer(msg.clientId); enemyPlayer.ReceivePosition(msg, this.enemyPrefab); clientsMoved.Add(msg.clientId); } else if (msg.messageType == MessageType.PlayerLeft) { Debug.Log("Player left " + msg.clientId); // A player left, remove from list and delete gameobject NetworkPlayer enemyPlayer = this.GetEnemyPlayer(msg.clientId); if (enemyPlayer != null) { //Debug.Log("Found enemy player"); enemyPlayer.DeleteGameObject(); this.enemyPlayers.Remove(enemyPlayer); justLeftClients.Add(msg.clientId); } } } } messagesToProcess.Clear(); // Interpolate all enemy players towards their current target foreach (var enemyPlayer in this.enemyPlayers) { enemyPlayer.InterpolateToTarget(); } // Interpolate player towards his/her current target this.localPlayer.InterpolateToTarget(); } void SendMove() { // Get movement input var newPosMessage = this.localPlayer.GetMoveMessage(); // Bots will have randomized movement that slowly changes #if BOTCLIENT if(this.botMovementChangeCount <= 0) { this.currentBotMovementX = UnityEngine.Random.Range(-1.0f, 1.0f); this.currentBotMovementZ = UnityEngine.Random.Range(-1.0f, 1.0f); this.botMovementChangeCount = 30; } this.botMovementChangeCount -= 1; newPosMessage.float1 = this.currentBotMovementX; newPosMessage.float2 = this.currentBotMovementZ; #endif // Send if not null if (newPosMessage != null) this.networkClient.SendMessage(newPosMessage); } #endif }