prod ready

This commit is contained in:
Sewmina 2025-05-27 23:50:42 +05:30
parent d09b48c01a
commit 71354308de
6 changed files with 249 additions and 44 deletions

View File

@ -1208,7 +1208,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 559a535836e3be843a23b9fc3cc5a0cf, type: 3} m_Script: {fileID: 11500000, guid: 559a535836e3be843a23b9fc3cc5a0cf, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
start_on_dummy: 1 start_on_dummy: 0
loadingScreen: {fileID: 3571457582356105321} loadingScreen: {fileID: 3571457582356105321}
helperScreen: {fileID: 0} helperScreen: {fileID: 0}
txtLoadingStatus: {fileID: 3586273892319143354} txtLoadingStatus: {fileID: 3586273892319143354}
@ -3460,6 +3460,8 @@ MonoBehaviour:
syncMode: 0 syncMode: 0
syncInterval: 0 syncInterval: 0
statusText: {fileID: 1110060053} statusText: {fileID: 1110060053}
scoreText_me: {fileID: 517343105}
scoreText_enemy: {fileID: 1914728796}
gameOverScreen: {fileID: 400480993} gameOverScreen: {fileID: 400480993}
gameStarted: 0 gameStarted: 0
winner: 1 winner: 1
@ -3686,7 +3688,7 @@ MonoBehaviour:
sendTimeout: 5000 sendTimeout: 5000
receiveTimeout: 20000 receiveTimeout: 20000
noDelay: 1 noDelay: 1
sslEnabled: 0 sslEnabled: 1
sslProtocols: 3072 sslProtocols: 3072
sslCertJson: ./cert.json sslCertJson: ./cert.json
port: 27777 port: 27777
@ -3704,7 +3706,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2131586989} m_GameObject: {fileID: 2131586989}
m_Enabled: 1 m_Enabled: 0
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3} m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3}
m_Name: m_Name:

View File

@ -9,6 +9,8 @@ public class NetGameManager : NetworkBehaviour
public const int WAITING_TIME = 120; public const int WAITING_TIME = 120;
public static NetGameManager instance; public static NetGameManager instance;
public TMP_Text statusText; public TMP_Text statusText;
public TMP_Text scoreText_me;
public TMP_Text scoreText_enemy;
public GameOverScreen gameOverScreen; public GameOverScreen gameOverScreen;
public bool gameStarted = false; public bool gameStarted = false;
float waitingTimer = WAITING_TIME; float waitingTimer = WAITING_TIME;
@ -31,6 +33,13 @@ public class NetGameManager : NetworkBehaviour
Application.targetFrameRate = 60; 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; public bool playerSpawned = false;
// Update is called once per frame // Update is called once per frame
@ -47,18 +56,20 @@ public class NetGameManager : NetworkBehaviour
StartCoroutine(StartGame()); StartCoroutine(StartGame());
}else{ }else{
if(players.Length == 1){ if(players.Length == 1){
waitingTimer -= Time.deltaTime; if(waitingTimer >0){
SetStatusText("Waiting for another player...\nRemaining time: " + waitingTimer.ToString("F0") + " seconds"); waitingTimer -= Time.deltaTime;
}else{ SetStatusText("Waiting for another player...\nRemaining time: " + waitingTimer.ToString("F0") + " seconds");
SetStatusText("Opponent abandoned the game"); }else{
//TODO: GAME OVER SetStatusText("Opponent abandoned the game");
GameOver(players[0].playerType);
}
} }
} }
}else{ }else{
if(players.Length == 1){ if(players.Length == 1){
if(playerSpawned){ if(playerSpawned){
SetStatusText("Opponent left the game"); SetStatusText("Opponent left the game");
//TODO: GAME OVER GameOver(players[0].playerType);
}else{ }else{
gameStarted = false; gameStarted = false;
SetStatusText("Waiting for another player..."); SetStatusText("Waiting for another player...");
@ -69,6 +80,7 @@ public class NetGameManager : NetworkBehaviour
} }
} }
[ClientRpc] [ClientRpc]
void RpcSetGameStarted(bool started){ void RpcSetGameStarted(bool started){
gameStarted = started; gameStarted = started;
@ -86,12 +98,17 @@ public class NetGameManager : NetworkBehaviour
spawnedFruits.Add(fruit); spawnedFruits.Add(fruit);
} }
public void DestroyFruit(Vector2 position){ public void DestroyFruit(Vector2 position){
foreach(GameObject fruit in spawnedFruits){ int index = -1;
if(fruit.transform.position == (Vector3)position){ for(int i=0; i < spawnedFruits.Count; i++){
NetworkServer.Destroy(fruit); if(spawnedFruits[i].transform.position == (Vector3)position){
spawnedFruits.Remove(fruit); index = i;
break;
} }
} }
if(index != -1){
NetworkServer.Destroy(spawnedFruits[index]);
spawnedFruits.RemoveAt(index);
}
} }
Vector2 GetRandomPoint(){ Vector2 GetRandomPoint(){
@ -185,7 +202,10 @@ public class NetGameManager : NetworkBehaviour
} }
queryString = queryString.TrimEnd('&'); 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; yield return www;
if(www.error == null){ if(www.error == null){

View File

@ -8,6 +8,8 @@ public class SnakeController : NetworkBehaviour
{ {
[SyncVar] [SyncVar]
public string userId; public string userId;
[SyncVar(hook = nameof(OnScoreChange))]
public int Score=0;
public Vector2 startOffset = new Vector3(-10, -10); public Vector2 startOffset = new Vector3(-10, -10);
public float movingInterval = 0.2f; public float movingInterval = 0.2f;
public Vector2 curDirection = Vector3.right; public Vector2 curDirection = Vector3.right;
@ -24,29 +26,40 @@ public class SnakeController : NetworkBehaviour
public float leftEdge => transform.position.x - fieldSize.x / 2f; public float leftEdge => transform.position.x - fieldSize.x / 2f;
public float rightEdge => transform.position.x + fieldSize.x / 2f; public float rightEdge => transform.position.x + fieldSize.x / 2f;
// Add new fields for client prediction
private List<Vector2> predictedPositions = new List<Vector2>();
private List<Vector2> predictedDirections = new List<Vector2>();
private float lastServerUpdateTime;
private const float MAX_PREDICTION_TIME = 0.5f; // Maximum time to keep predictions
void Start() void Start()
{ {
ButtonSet.OnUp += OnUp; ButtonSet.OnUp += OnUp;
ButtonSet.OnDown += OnDown; ButtonSet.OnDown += OnDown;
ButtonSet.OnLeft += OnLeft; ButtonSet.OnLeft += OnLeft;
ButtonSet.OnRight += OnRight; ButtonSet.OnRight += OnRight;
playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client;
if(FindObjectsOfType<SnakeController>().Length >1){
startOffset+=new Vector2(0, 10);
}
if(!isServer&& isLocalPlayer){ if(!isServer&& isLocalPlayer){
CmdSetUserId(GameData.user_id); CmdSetUserId(GameData.user_id);
} }
if (isLocalPlayer)
{
lastServerUpdateTime = Time.time;
playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client;
}
} }
[Command] [Command]
void CmdSetUserId(string userId){ void CmdSetUserId(string userId){
if(userId == GameData.betData.owner_id){
startOffset.y += 10;
}
this.userId = userId; this.userId = userId;
playerType = userId == GameData.betData.owner_id ? PlayerType.Master : PlayerType.Client;
} }
private void OnDestroy() private void OnDestroy()
@ -77,22 +90,34 @@ public class SnakeController : NetworkBehaviour
public bool controlling = false; public bool controlling = false;
void Update() void Update()
{ {
if (NetGameManager.instance.gameOver) { return; }
if (!isKickstarted) { return; }
if (isLocalPlayer) if (isLocalPlayer)
{ {
ProcessInput(); ProcessInput();
} }
if (NetGameManager.instance.gameOver) { return; }
if (!isKickstarted) { return; }
if (!isServer) { return; } if (isServer)
{
CheckHead(); CheckHead();
if (isDead) { return; } if (isDead) { return; }
Move(); Move();
}
else if (isLocalPlayer)
{
// Client-side prediction
if (moveTimer > 0)
{
moveTimer -= Time.deltaTime;
}
else
{
moveTimer = movingInterval;
PredictMove();
}
}
} }
void ProcessInput() void ProcessInput()
{ {
if (Input.GetKeyDown(KeyCode.RightArrow)) if (Input.GetKeyDown(KeyCode.RightArrow))
@ -133,12 +158,26 @@ public class SnakeController : NetworkBehaviour
ChangeDir(-Vector2.up); ChangeDir(-Vector2.up);
} }
public int Score;
public void AddScore(int amount = 1) public void AddScore(int amount = 1)
{ {
Score += amount; 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(); SFXManager.instance.PlayCrunch();
} }
@ -172,6 +211,90 @@ public class SnakeController : NetworkBehaviour
} }
bool queueNewPiece = false; 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() void Move()
{ {
if (moveTimer > 0) if (moveTimer > 0)
@ -201,18 +324,17 @@ public class SnakeController : NetworkBehaviour
direction = dir direction = dir
}); });
} }
Vector2 curPosition = snakePieces[0].transform.position; Vector2 curPosition = snakePieces[0].transform.position;
Vector2 lastPosition = snakePieces[snakePieces.Count - 1].transform.position; Vector2 lastPosition = snakePieces[snakePieces.Count - 1].transform.position;
for (int i = snakePieces.Count - 1; i > 0; i--) for (int i = snakePieces.Count - 1; i > 0; i--)
{ {
Vector3 dir = (snakePieces[i - 1].transform.position - snakePieces[i].transform.position).normalized; Vector3 dir = (snakePieces[i - 1].transform.position - snakePieces[i].transform.position).normalized;
snakePieces[i].direction = dir; snakePieces[i].direction = dir;
snakePieces[i].isHead = false; snakePieces[i].isHead = false;
snakePieces[i].transform.position = snakePieces[i - 1].transform.position; snakePieces[i].transform.position = snakePieces[i - 1].transform.position;
snakePieces[i].transform.position = snakePieces[i].transform.position;
BendData bendToRemove = null; BendData bendToRemove = null;
foreach (BendData bend in bends) 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); 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; bool isDead = false;
public PlayerType playerType; public PlayerType playerType;
void CheckHead() void CheckHead()
@ -286,7 +463,7 @@ public class SnakeController : NetworkBehaviour
{ {
Debug.Log($"Crashed into {obstacle.name}", gameObject); Debug.Log($"Crashed into {obstacle.name}", gameObject);
isDead = true; isDead = true;
NetGameManager.instance.GameOver(playerType); NetGameManager.instance.GameOver(playerType == PlayerType.Master ? PlayerType.Client : PlayerType.Master);
SFXManager.instance.PlayDeath(); SFXManager.instance.PlayDeath();
} }
} }
@ -296,7 +473,6 @@ public class SnakeController : NetworkBehaviour
queueNewPiece = true; queueNewPiece = true;
NetGameManager.instance.DestroyFruit(obstacle.transform.position); NetGameManager.instance.DestroyFruit(obstacle.transform.position);
gameObject.SetActive(false);
} }
} }

View File

@ -97,7 +97,7 @@ public class ClientKickstarter : MonoBehaviour
IEnumerator startTest(int user_id = 1) 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; yield return www;
Debug.Log("test port: " + www.text); Debug.Log("test port: " + www.text);
string port = www.text; string port = www.text;
@ -108,7 +108,7 @@ public class ClientKickstarter : MonoBehaviour
IEnumerator CoroutineKickstarterWatcher() IEnumerator CoroutineKickstarterWatcher()
{ {
yield return new WaitForSeconds(6f); yield return new WaitForSeconds(10f);
SnakeController[] players = FindObjectsOfType<SnakeController>(); SnakeController[] players = FindObjectsOfType<SnakeController>();
if(players.Length == 0){ if(players.Length == 0){
txtLoadingStatus.text = "Connection failed. Retrying..."; txtLoadingStatus.text = "Connection failed. Retrying...";
@ -127,7 +127,7 @@ public class ClientKickstarter : MonoBehaviour
while (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) 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; yield return www;
if (string.IsNullOrEmpty(www.error)) if (string.IsNullOrEmpty(www.error))

View File

@ -1,7 +1,8 @@
public static class Constants{ 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 MATCHMAKER_URL = "https://matchmaker.duelfi.io/";
public static string API_URL = "https://api.duelfi.io/v1/"; public static string API_URL = "https://api.duelfi.io/v1/";

View File

@ -28,12 +28,17 @@ public class ServerKickstarter : MonoBehaviour
{ {
arguments["port"] = "5000"; arguments["port"] = "5000";
arguments["address"] = "2h6hptZhts119MhaY39SyxSCYp966Y9EkKubEReb7wbT"; arguments["address"] = "2h6hptZhts119MhaY39SyxSCYp966Y9EkKubEReb7wbT";
arguments["isDev"] = "true";
} }
try try
{ {
NetworkManager.singleton.GetComponent<SimpleWebTransport>().port = (ushort)int.Parse(arguments["port"]); NetworkManager.singleton.GetComponent<SimpleWebTransport>().port = (ushort)int.Parse(arguments["port"]);
GameData.port = 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"])); StartCoroutine(CoroutineUpdateGameData(arguments["address"]));
} }
catch (Exception e) catch (Exception e)
@ -52,10 +57,11 @@ public class ServerKickstarter : MonoBehaviour
IEnumerator CoroutineUpdateGameData(string address) IEnumerator CoroutineUpdateGameData(string address)
{ {
GameData.address = address; GameData.address = address;
string validator_url = GameData.isDev ? Constants.VALIDATOR_URL_DEV : Constants.VALIDATOR_URL_PROD;
WWW www = new WWW(Constants.VALIDATOR_URL + "fetchBet?address=" + address); string url = validator_url + "fetchBet?address=" + address;
WWW www = new WWW(url);
yield return www; yield return www;
Debug.Log(url);
Debug.Log("bet: " + www.text); Debug.Log("bet: " + www.text);
try try
{ {