diff --git a/Assets/Scenes/Game.unity b/Assets/Scenes/Game.unity index ec5b089..9d06407 100644 --- a/Assets/Scenes/Game.unity +++ b/Assets/Scenes/Game.unity @@ -1208,7 +1208,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 559a535836e3be843a23b9fc3cc5a0cf, type: 3} m_Name: m_EditorClassIdentifier: - start_on_dummy: 1 + start_on_dummy: 0 loadingScreen: {fileID: 3571457582356105321} helperScreen: {fileID: 0} txtLoadingStatus: {fileID: 3586273892319143354} @@ -3460,6 +3460,8 @@ MonoBehaviour: syncMode: 0 syncInterval: 0 statusText: {fileID: 1110060053} + scoreText_me: {fileID: 517343105} + scoreText_enemy: {fileID: 1914728796} gameOverScreen: {fileID: 400480993} gameStarted: 0 winner: 1 @@ -3686,7 +3688,7 @@ MonoBehaviour: sendTimeout: 5000 receiveTimeout: 20000 noDelay: 1 - sslEnabled: 0 + sslEnabled: 1 sslProtocols: 3072 sslCertJson: ./cert.json port: 27777 @@ -3704,7 +3706,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2131586989} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3} m_Name: diff --git a/Assets/Scripts/NetGameManager.cs b/Assets/Scripts/NetGameManager.cs index e93fa73..2ff0d28 100644 --- a/Assets/Scripts/NetGameManager.cs +++ b/Assets/Scripts/NetGameManager.cs @@ -9,6 +9,8 @@ public class NetGameManager : NetworkBehaviour public const int WAITING_TIME = 120; public static NetGameManager instance; public TMP_Text statusText; + public TMP_Text scoreText_me; + public TMP_Text scoreText_enemy; public GameOverScreen gameOverScreen; public bool gameStarted = false; float waitingTimer = WAITING_TIME; @@ -31,6 +33,13 @@ public class NetGameManager : NetworkBehaviour Application.targetFrameRate = 60; } + public void UpdateScore(bool isLocalPlayer, int score){ + if(isLocalPlayer){ + scoreText_me.text = score.ToString(); + }else{ + scoreText_enemy.text = score.ToString(); + } + } public bool playerSpawned = false; // Update is called once per frame @@ -47,18 +56,20 @@ public class NetGameManager : NetworkBehaviour StartCoroutine(StartGame()); }else{ if(players.Length == 1){ - waitingTimer -= Time.deltaTime; - SetStatusText("Waiting for another player...\nRemaining time: " + waitingTimer.ToString("F0") + " seconds"); - }else{ - SetStatusText("Opponent abandoned the game"); - //TODO: GAME OVER + if(waitingTimer >0){ + waitingTimer -= Time.deltaTime; + SetStatusText("Waiting for another player...\nRemaining time: " + waitingTimer.ToString("F0") + " seconds"); + }else{ + SetStatusText("Opponent abandoned the game"); + GameOver(players[0].playerType); + } } } }else{ if(players.Length == 1){ if(playerSpawned){ SetStatusText("Opponent left the game"); - //TODO: GAME OVER + GameOver(players[0].playerType); }else{ gameStarted = false; SetStatusText("Waiting for another player..."); @@ -69,6 +80,7 @@ public class NetGameManager : NetworkBehaviour } } + [ClientRpc] void RpcSetGameStarted(bool started){ gameStarted = started; @@ -86,12 +98,17 @@ public class NetGameManager : NetworkBehaviour spawnedFruits.Add(fruit); } public void DestroyFruit(Vector2 position){ - foreach(GameObject fruit in spawnedFruits){ - if(fruit.transform.position == (Vector3)position){ - NetworkServer.Destroy(fruit); - spawnedFruits.Remove(fruit); + int index = -1; + for(int i=0; i < spawnedFruits.Count; i++){ + if(spawnedFruits[i].transform.position == (Vector3)position){ + index = i; + break; } } + if(index != -1){ + NetworkServer.Destroy(spawnedFruits[index]); + spawnedFruits.RemoveAt(index); + } } Vector2 GetRandomPoint(){ @@ -185,7 +202,10 @@ public class NetGameManager : NetworkBehaviour } queryString = queryString.TrimEnd('&'); - WWW www = new WWW(Constants.VALIDATOR_URL + "finalize" + queryString); + string validator_url = GameData.isDev ? Constants.VALIDATOR_URL_DEV : Constants.VALIDATOR_URL_PROD; + string url = validator_url + "finalize" + queryString; + Debug.Log("Finalizing game on " + url); + WWW www = new WWW(url); yield return www; if(www.error == null){ diff --git a/Assets/Scripts/SnakeController.cs b/Assets/Scripts/SnakeController.cs index 66a7072..a2d6c32 100644 --- a/Assets/Scripts/SnakeController.cs +++ b/Assets/Scripts/SnakeController.cs @@ -8,6 +8,8 @@ public class SnakeController : NetworkBehaviour { [SyncVar] public string userId; + [SyncVar(hook = nameof(OnScoreChange))] + public int Score=0; public Vector2 startOffset = new Vector3(-10, -10); public float movingInterval = 0.2f; public Vector2 curDirection = Vector3.right; @@ -24,29 +26,40 @@ public class SnakeController : NetworkBehaviour public float leftEdge => transform.position.x - fieldSize.x / 2f; public float rightEdge => transform.position.x + fieldSize.x / 2f; + // Add new fields for client prediction + private List predictedPositions = new List(); + private List predictedDirections = new List(); + private float lastServerUpdateTime; + private const float MAX_PREDICTION_TIME = 0.5f; // Maximum time to keep predictions + void Start() { - - ButtonSet.OnUp += OnUp; ButtonSet.OnDown += OnDown; ButtonSet.OnLeft += OnLeft; ButtonSet.OnRight += OnRight; - playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client; - if(FindObjectsOfType().Length >1){ - startOffset+=new Vector2(0, 10); - } - if(!isServer&& isLocalPlayer){ CmdSetUserId(GameData.user_id); } + + if (isLocalPlayer) + { + lastServerUpdateTime = Time.time; + playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client; + + } } [Command] void CmdSetUserId(string userId){ + + if(userId == GameData.betData.owner_id){ + startOffset.y += 10; + } this.userId = userId; + playerType = userId == GameData.betData.owner_id ? PlayerType.Master : PlayerType.Client; } private void OnDestroy() @@ -77,22 +90,34 @@ public class SnakeController : NetworkBehaviour public bool controlling = false; void Update() { - if (NetGameManager.instance.gameOver) { return; } - if (!isKickstarted) { return; } - if (isLocalPlayer) { ProcessInput(); } + if (NetGameManager.instance.gameOver) { return; } + if (!isKickstarted) { return; } - if (!isServer) { return; } - - CheckHead(); - if (isDead) { return; } - Move(); + if (isServer) + { + CheckHead(); + if (isDead) { return; } + Move(); + } + else if (isLocalPlayer) + { + // Client-side prediction + if (moveTimer > 0) + { + moveTimer -= Time.deltaTime; + } + else + { + moveTimer = movingInterval; + PredictMove(); + } + } } - void ProcessInput() { if (Input.GetKeyDown(KeyCode.RightArrow)) @@ -133,12 +158,26 @@ public class SnakeController : NetworkBehaviour ChangeDir(-Vector2.up); } - - public int Score; public void AddScore(int amount = 1) { Score += amount; + if(isServer){ + RpcOnScore(); + }else{ + OnScore(); + } + } + [ClientRpc] + void RpcOnScore(){ + OnScore(); + } + + void OnScoreChange(int oldScore, int newScore){ + NetGameManager.instance.UpdateScore(isLocalPlayer, newScore); + } + + void OnScore(){ SFXManager.instance.PlayCrunch(); } @@ -172,6 +211,90 @@ public class SnakeController : NetworkBehaviour } bool queueNewPiece = false; + void PredictMove() + { + if (moveInputQueue.Count > 0) + { + Vector2 dir = Vector2.zero; + if (curDirection.x != 0) + { + dir = new Vector2(-curDirection.x, moveInputQueue[0].y); + } + else + { + dir = new Vector2(moveInputQueue[0].x, -curDirection.y); + } + curDirection = moveInputQueue[0]; + moveInputQueue.RemoveAt(0); + + bends.Add(new BendData() + { + position = snakePieces[0].transform.position, + direction = dir + }); + } + + // Store current state for prediction + Vector2 curPosition = snakePieces[0].transform.position; + Vector2 lastPosition = snakePieces[snakePieces.Count - 1].transform.position; + + // Update snake pieces + for (int i = snakePieces.Count - 1; i > 0; i--) + { + Vector3 dir = (snakePieces[i - 1].transform.position - snakePieces[i].transform.position).normalized; + snakePieces[i].direction = dir; + snakePieces[i].isHead = false; + snakePieces[i].transform.position = snakePieces[i - 1].transform.position; + + BendData bendToRemove = null; + foreach (BendData bend in bends) + { + if (bend.position == snakePieces[i].transform.position) + { + snakePieces[i].direction = bend.direction; + if (i == snakePieces.Count - 1) + { + bendToRemove = bend; + } + break; + } + } + + if (bendToRemove != null) + { + bends.Remove(bendToRemove); + } + } + + // Move head + snakePieces[0].transform.position = curPosition + curDirection; + snakePieces[0].direction = curDirection; + snakePieces[0].isHead = true; + + // Handle wrapping + if (snakePieces[0].transform.position.x > rightEdge) + { + snakePieces[0].transform.position = new Vector2(leftEdge, snakePieces[0].transform.position.y); + } + else if (snakePieces[0].transform.position.x < leftEdge) + { + snakePieces[0].transform.position = new Vector2(rightEdge, snakePieces[0].transform.position.y); + } + + if (snakePieces[0].transform.position.y > topEdge) + { + snakePieces[0].transform.position = new Vector2(snakePieces[0].transform.position.x, botEdge); + } + else if (snakePieces[0].transform.position.y < botEdge) + { + snakePieces[0].transform.position = new Vector2(snakePieces[0].transform.position.x, topEdge); + } + + // Store prediction + predictedPositions.Add(snakePieces[0].transform.position); + predictedDirections.Add(curDirection); + } + void Move() { if (moveTimer > 0) @@ -201,18 +324,17 @@ public class SnakeController : NetworkBehaviour direction = dir }); } + Vector2 curPosition = snakePieces[0].transform.position; Vector2 lastPosition = snakePieces[snakePieces.Count - 1].transform.position; for (int i = snakePieces.Count - 1; i > 0; i--) { Vector3 dir = (snakePieces[i - 1].transform.position - snakePieces[i].transform.position).normalized; - snakePieces[i].direction = dir; - snakePieces[i].isHead = false; snakePieces[i].transform.position = snakePieces[i - 1].transform.position; - snakePieces[i].transform.position = snakePieces[i].transform.position; + BendData bendToRemove = null; foreach (BendData bend in bends) { @@ -267,8 +389,63 @@ public class SnakeController : NetworkBehaviour { snakePieces[0].transform.position = new Vector2(snakePieces[0].transform.position.x, topEdge); } + + // After server movement, send state to clients + if (isServer) + { + Vector2[] positions = new Vector2[snakePieces.Count]; + Vector2[] directions = new Vector2[snakePieces.Count]; + + for (int i = 0; i < snakePieces.Count; i++) + { + positions[i] = snakePieces[i].transform.position; + directions[i] = snakePieces[i].direction; + } + + RpcReconcileState(positions, directions); + } } } + + [ClientRpc] + void RpcReconcileState(Vector2[] serverPositions, Vector2[] serverDirections) + { + if (!isLocalPlayer) return; + + // Find the oldest prediction that matches the server state + int matchIndex = -1; + for (int i = 0; i < predictedPositions.Count; i++) + { + if (Vector2.Distance(predictedPositions[i], serverPositions[0]) < 0.1f) + { + matchIndex = i; + break; + } + } + + if (matchIndex >= 0) + { + // Remove all predictions up to the matching one + predictedPositions.RemoveRange(0, matchIndex + 1); + predictedDirections.RemoveRange(0, matchIndex + 1); + } + else + { + // No match found, reset to server state + predictedPositions.Clear(); + predictedDirections.Clear(); + + // Update snake pieces to match server state + for (int i = 0; i < snakePieces.Count; i++) + { + snakePieces[i].transform.position = serverPositions[i]; + snakePieces[i].direction = serverDirections[i]; + } + } + + lastServerUpdateTime = Time.time; + } + bool isDead = false; public PlayerType playerType; void CheckHead() @@ -286,7 +463,7 @@ public class SnakeController : NetworkBehaviour { Debug.Log($"Crashed into {obstacle.name}", gameObject); isDead = true; - NetGameManager.instance.GameOver(playerType); + NetGameManager.instance.GameOver(playerType == PlayerType.Master ? PlayerType.Client : PlayerType.Master); SFXManager.instance.PlayDeath(); } } @@ -296,7 +473,6 @@ public class SnakeController : NetworkBehaviour queueNewPiece = true; NetGameManager.instance.DestroyFruit(obstacle.transform.position); - gameObject.SetActive(false); } } diff --git a/Assets/Scripts/Utils/ClientKickstarter.cs b/Assets/Scripts/Utils/ClientKickstarter.cs index a8ccaf0..23a1351 100644 --- a/Assets/Scripts/Utils/ClientKickstarter.cs +++ b/Assets/Scripts/Utils/ClientKickstarter.cs @@ -97,7 +97,7 @@ public class ClientKickstarter : MonoBehaviour IEnumerator startTest(int user_id = 1) { - WWW www = new WWW(Constants.MATCHMAKER_URL + "getGamePort?address=" + GameData.address + "&gameName=" + GAME_NAME); + WWW www = new WWW(Constants.MATCHMAKER_URL + "getGamePort?address=" + GameData.address + "&gameName=" + GAME_NAME + "&isDev=" + (GameData.isDev ? "true" : "false")); yield return www; Debug.Log("test port: " + www.text); string port = www.text; @@ -108,7 +108,7 @@ public class ClientKickstarter : MonoBehaviour IEnumerator CoroutineKickstarterWatcher() { - yield return new WaitForSeconds(6f); + yield return new WaitForSeconds(10f); SnakeController[] players = FindObjectsOfType(); if(players.Length == 0){ txtLoadingStatus.text = "Connection failed. Retrying..."; @@ -127,7 +127,7 @@ public class ClientKickstarter : MonoBehaviour while (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { - WWW www = new WWW(Constants.MATCHMAKER_URL + "getGamePort?address=" + GameData.address + "&gameName=" + GAME_NAME); + WWW www = new WWW(Constants.MATCHMAKER_URL + "getGamePort?address=" + GameData.address + "&gameName=" + GAME_NAME + "&isDev=" + (GameData.isDev ? "true" : "false")); yield return www; if (string.IsNullOrEmpty(www.error)) diff --git a/Assets/Scripts/Utils/DuelFi.cs b/Assets/Scripts/Utils/DuelFi.cs index 402afd0..d520c35 100644 --- a/Assets/Scripts/Utils/DuelFi.cs +++ b/Assets/Scripts/Utils/DuelFi.cs @@ -1,7 +1,8 @@ public static class Constants{ - public static string VALIDATOR_URL = "https://validator.duelfi.io/"; + public static string VALIDATOR_URL_PROD = "https://validator.duelfi.io/"; + public static string VALIDATOR_URL_DEV = "https://validatordev.duelfi.io/"; public static string MATCHMAKER_URL = "https://matchmaker.duelfi.io/"; public static string API_URL = "https://api.duelfi.io/v1/"; diff --git a/Assets/Scripts/Utils/ServerKickstarter.cs b/Assets/Scripts/Utils/ServerKickstarter.cs index cffba66..45c61b9 100644 --- a/Assets/Scripts/Utils/ServerKickstarter.cs +++ b/Assets/Scripts/Utils/ServerKickstarter.cs @@ -28,12 +28,17 @@ public class ServerKickstarter : MonoBehaviour { arguments["port"] = "5000"; arguments["address"] = "2h6hptZhts119MhaY39SyxSCYp966Y9EkKubEReb7wbT"; + arguments["isDev"] = "true"; } try { NetworkManager.singleton.GetComponent().port = (ushort)int.Parse(arguments["port"]); GameData.port = int.Parse(arguments["port"]); + GameData.isDev = arguments["isDev"] == "true"; + if(GameData.isDev){ + Debug.Log("Starting server in dev mode"); + } StartCoroutine(CoroutineUpdateGameData(arguments["address"])); } catch (Exception e) @@ -52,10 +57,11 @@ public class ServerKickstarter : MonoBehaviour IEnumerator CoroutineUpdateGameData(string address) { GameData.address = address; - - WWW www = new WWW(Constants.VALIDATOR_URL + "fetchBet?address=" + address); + string validator_url = GameData.isDev ? Constants.VALIDATOR_URL_DEV : Constants.VALIDATOR_URL_PROD; + string url = validator_url + "fetchBet?address=" + address; + WWW www = new WWW(url); yield return www; - + Debug.Log(url); Debug.Log("bet: " + www.text); try {