diff --git a/Assets/Resources/SqrBkp.prefab b/Assets/Resources/SqrBkp.prefab new file mode 100644 index 0000000..081f61a --- /dev/null +++ b/Assets/Resources/SqrBkp.prefab @@ -0,0 +1,327 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &295599691903699673 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5406650049798896926} + - component: {fileID: 6874579508060478959} + - component: {fileID: 3459790770394357766} + - component: {fileID: 4735354123453469529} + - component: {fileID: 4952255795263612673} + m_Layer: 0 + m_Name: SqrBkp + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5406650049798896926 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295599691903699673} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3342671657833137417} + - {fileID: 2214332984883492026} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!61 &6874579508060478959 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295599691903699673} + m_Enabled: 1 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_UsedByComposite: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + serializedVersion: 2 + m_Size: {x: 0.8, y: 0.8} + m_EdgeRadius: 0 +--- !u!114 &3459790770394357766 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295599691903699673} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1cb327dda248e37489acbec5dfa62bbc, type: 3} + m_Name: + m_EditorClassIdentifier: + syncDirection: 0 + syncMode: 0 + syncInterval: 0 + skin: {fileID: 11400000, guid: 1296c645b80104448914605af84d2822, type: 2} + direction: {x: 1, y: 0} + isLocal: 0 + spriteRenderer: {fileID: 6309712304440343176} + myColor: {r: 0.3137255, g: 0.90588236, b: 0.36862746, a: 1} + enemyColor: {r: 1, g: 0, b: 0.059296608, a: 1} + myBodyColor: {r: 0.16078432, g: 0.5254902, b: 0.20000002, a: 1} + enemyBodyColor: {r: 0.5764706, g: 0.098039225, b: 0.10588236, a: 1} + isHead: 0 +--- !u!114 &4735354123453469529 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295599691903699673} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 0 + _assetId: 3639326877 + serverOnly: 0 + visibility: 0 + hasSpawned: 0 +--- !u!114 &4952255795263612673 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295599691903699673} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8ff3ba0becae47b8b9381191598957c8, type: 3} + m_Name: + m_EditorClassIdentifier: + syncDirection: 1 + syncMode: 0 + syncInterval: 0 + target: {fileID: 5406650049798896926} + syncPosition: 1 + syncRotation: 1 + syncScale: 0 + onlySyncOnChange: 1 + compressRotation: 1 + interpolatePosition: 0 + interpolateRotation: 0 + interpolateScale: 0 + coordinateSpace: 0 + timelineOffset: 1 + showGizmos: 0 + showOverlay: 0 + overlayColor: {r: 0, g: 0, b: 0, a: 0.5} + onlySyncOnChangeCorrectionMultiplier: 2 + useFixedUpdate: 0 + rotationSensitivity: 0.01 + positionPrecision: 0.01 + scalePrecision: 0.01 +--- !u!1 &2340789509869100348 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2214332984883492026} + - component: {fileID: 7163281116187786196} + m_Layer: 0 + m_Name: block (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &2214332984883492026 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2340789509869100348} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5406650049798896926} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &7163281116187786196 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2340789509869100348} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 5 + m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!1 &5408880743873525811 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3342671657833137417} + - component: {fileID: 6309712304440343176} + m_Layer: 0 + m_Name: block + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3342671657833137417 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5408880743873525811} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.0177, y: 0.0072, z: 0} + m_LocalScale: {x: 1, y: 1, z: 0.9} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5406650049798896926} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &6309712304440343176 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5408880743873525811} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 5 + m_Sprite: {fileID: 21300000, guid: 867695b809d154343add975191c8ac2f, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 diff --git a/Assets/Resources/SqrBkp.prefab.meta b/Assets/Resources/SqrBkp.prefab.meta new file mode 100644 index 0000000..0cd160f --- /dev/null +++ b/Assets/Resources/SqrBkp.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1f27e187933cc174ea8af8e815961672 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Square.prefab b/Assets/Resources/Square.prefab index acc60ec..ad45641 100644 --- a/Assets/Resources/Square.prefab +++ b/Assets/Resources/Square.prefab @@ -12,7 +12,6 @@ GameObject: - component: {fileID: 6874579508060478959} - component: {fileID: 3459790770394357766} - component: {fileID: 4735354123453469529} - - component: {fileID: 4952255795263612673} m_Layer: 0 m_Name: Square m_TagString: Untagged @@ -99,7 +98,7 @@ MonoBehaviour: syncInterval: 0 skin: {fileID: 11400000, guid: 1296c645b80104448914605af84d2822, type: 2} direction: {x: 1, y: 0} - isLocal: 0 + userId: spriteRenderer: {fileID: 6309712304440343176} myColor: {r: 0.3137255, g: 0.90588236, b: 0.36862746, a: 1} enemyColor: {r: 1, g: 0, b: 0.059296608, a: 1} @@ -123,40 +122,6 @@ MonoBehaviour: serverOnly: 0 visibility: 0 hasSpawned: 0 ---- !u!114 &4952255795263612673 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 295599691903699673} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 8ff3ba0becae47b8b9381191598957c8, type: 3} - m_Name: - m_EditorClassIdentifier: - syncDirection: 1 - syncMode: 0 - syncInterval: 0 - target: {fileID: 5406650049798896926} - syncPosition: 1 - syncRotation: 1 - syncScale: 0 - onlySyncOnChange: 1 - compressRotation: 1 - interpolatePosition: 0 - interpolateRotation: 0 - interpolateScale: 0 - coordinateSpace: 0 - timelineOffset: 1 - showGizmos: 0 - showOverlay: 0 - overlayColor: {r: 0, g: 0, b: 0, a: 0.5} - onlySyncOnChangeCorrectionMultiplier: 2 - useFixedUpdate: 0 - rotationSensitivity: 0.01 - positionPrecision: 0.01 - scalePrecision: 0.01 --- !u!1 &2340789509869100348 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/Game.unity b/Assets/Scenes/Game.unity index 9d06407..99999c6 100644 --- a/Assets/Scenes/Game.unity +++ b/Assets/Scenes/Game.unity @@ -3688,7 +3688,7 @@ MonoBehaviour: sendTimeout: 5000 receiveTimeout: 20000 noDelay: 1 - sslEnabled: 1 + sslEnabled: 0 sslProtocols: 3072 sslCertJson: ./cert.json port: 27777 @@ -3706,7 +3706,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2131586989} - m_Enabled: 0 + m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3} m_Name: diff --git a/Assets/Scripts/SnakeController.cs b/Assets/Scripts/SnakeController.cs index a2d6c32..dfd383b 100644 --- a/Assets/Scripts/SnakeController.cs +++ b/Assets/Scripts/SnakeController.cs @@ -9,17 +9,22 @@ public class SnakeController : NetworkBehaviour [SyncVar] public string userId; [SyncVar(hook = nameof(OnScoreChange))] - public int Score=0; + public int Score = 0; public Vector2 startOffset = new Vector3(-10, -10); public float movingInterval = 0.2f; + [SyncVar] public Vector2 curDirection = Vector3.right; public List snakePieces = new List(); public List bends = new List(); + [SyncVar] + private List serverBends = new List(); public GameObject snakePiecePrefab; public int startingLength = 3; public Vector2 fieldSize = new Vector2(160, 90); float moveTimer; + [SyncVar] + private float serverMoveTimer; public float topEdge => transform.position.y + fieldSize.y / 2f; public float botEdge => transform.position.y - fieldSize.y / 2f; @@ -31,6 +36,8 @@ public class SnakeController : NetworkBehaviour private List predictedDirections = new List(); private float lastServerUpdateTime; private const float MAX_PREDICTION_TIME = 0.5f; // Maximum time to keep predictions + private const float PREDICTION_TOLERANCE = 0.1f; // Tolerance for position matching + private const float SYNC_THRESHOLD = 1f; // Distance threshold for considering out of sync void Start() { @@ -39,27 +46,42 @@ public class SnakeController : NetworkBehaviour ButtonSet.OnLeft += OnLeft; ButtonSet.OnRight += OnRight; +#if UNITY_EDITOR + GameData.user_id = "user" + Random.Range(0, 100); +#endif - if(!isServer&& isLocalPlayer){ + if (!isServer && isLocalPlayer) + { CmdSetUserId(GameData.user_id); } if (isLocalPlayer) { lastServerUpdateTime = Time.time; - playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client; + playerType = GameData.isMaster ? PlayerType.Master : PlayerType.Client; } } [Command] - void CmdSetUserId(string userId){ + void CmdSetUserId(string userId) + { + if (GameData.betData == null) + { + startOffset.y += Random.Range(0, 10); + this.userId = userId; - if(userId == GameData.betData.owner_id){ - startOffset.y += 10; } - this.userId = userId; - playerType = userId == GameData.betData.owner_id ? PlayerType.Master : PlayerType.Client; + else + { + 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() @@ -69,11 +91,43 @@ public class SnakeController : NetworkBehaviour ButtonSet.OnLeft -= OnLeft; ButtonSet.OnRight -= OnRight; } + [SyncVar] + public bool isKickstarted = false; + void OnSnakePiecesChanged(List oldPieces, List newPieces) + { + if (!isClient) return; + Debug.Log("Server added a new piece with id: " + newPieces[newPieces.Count - 1].netId); + + // Clear existing pieces + foreach (var piece in snakePieces) + { + if (piece != null) + { + Destroy(piece.gameObject); + } + } + snakePieces.Clear(); + + // Add new pieces + foreach (var netId in newPieces) + { + if (netId != null) + { + var piece = netId.GetComponent(); + if (piece != null) + { + snakePieces.Add(piece); + } + } + } + } - bool isKickstarted = false; public void StartGame() { + if (!isServer) return; + snakePieces = new List(); + for (int i = 0; i < startingLength; i++) { Vector3 pos = ((Vector2)transform.position - startOffset) - (Vector2.right * i); @@ -82,11 +136,22 @@ public class SnakeController : NetworkBehaviour SnakePiece piece = newPiece.GetComponent(); piece.userId = userId; snakePieces.Add(piece); + RpcAddSnakePiece(piece.netIdentity); } isKickstarted = true; isDead = false; } + [ClientRpc] + void RpcAddSnakePiece(NetworkIdentity netId) + { + var piece = netId.GetComponent(); + if (piece != null) + { + snakePieces.Add(piece); + } + } + public bool controlling = false; void Update() { @@ -105,7 +170,7 @@ public class SnakeController : NetworkBehaviour } else if (isLocalPlayer) { - // Client-side prediction + // Client-side prediction for local player if (moveTimer > 0) { moveTimer -= Time.deltaTime; @@ -161,23 +226,29 @@ public class SnakeController : NetworkBehaviour public void AddScore(int amount = 1) { Score += amount; - if(isServer){ + if (isServer) + { RpcOnScore(); - }else{ + } + else + { OnScore(); } } [ClientRpc] - void RpcOnScore(){ + void RpcOnScore() + { OnScore(); } - void OnScoreChange(int oldScore, int newScore){ + void OnScoreChange(int oldScore, int newScore) + { NetGameManager.instance.UpdateScore(isLocalPlayer, newScore); } - void OnScore(){ + void OnScore() + { SFXManager.instance.PlayCrunch(); } @@ -227,11 +298,14 @@ public class SnakeController : NetworkBehaviour curDirection = moveInputQueue[0]; moveInputQueue.RemoveAt(0); - bends.Add(new BendData() + // Add bend to both local and server lists + BendData newBend = new BendData() { position = snakePieces[0].transform.position, direction = dir - }); + }; + bends.Add(newBend); + CmdAddBend(newBend); } // Store current state for prediction @@ -263,11 +337,12 @@ public class SnakeController : NetworkBehaviour if (bendToRemove != null) { bends.Remove(bendToRemove); + CmdRemoveBend(bendToRemove); } } // Move head - snakePieces[0].transform.position = curPosition + curDirection; + snakePieces[0].transform.position = (Vector3)curPosition + (Vector3)curDirection; snakePieces[0].direction = curDirection; snakePieces[0].isHead = true; @@ -293,6 +368,12 @@ public class SnakeController : NetworkBehaviour // Store prediction predictedPositions.Add(snakePieces[0].transform.position); predictedDirections.Add(curDirection); + + // Request server sync periodically + if (Time.time - lastServerUpdateTime > 0.1f) // Request sync every 100ms + { + CmdRequestSync(); + } } void Move() @@ -300,123 +381,163 @@ public class SnakeController : NetworkBehaviour if (moveTimer > 0) { moveTimer -= Time.deltaTime; + return; } - else + + moveTimer = movingInterval; + if (moveInputQueue.Count > 0) { - moveTimer = movingInterval; - if (moveInputQueue.Count > 0) + Vector2 dir = Vector2.zero; + if (curDirection.x != 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 - }); + dir = new Vector2(-curDirection.x, moveInputQueue[0].y); } - - Vector2 curPosition = snakePieces[0].transform.position; - Vector2 lastPosition = snakePieces[snakePieces.Count - 1].transform.position; - - for (int i = snakePieces.Count - 1; i > 0; i--) + else { - 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; + dir = new Vector2(moveInputQueue[0].x, -curDirection.y); + } + curDirection = moveInputQueue[0]; + moveInputQueue.RemoveAt(0); - BendData bendToRemove = null; - foreach (BendData bend in bends) + // Add bend to both local and server lists + BendData newBend = new BendData() + { + position = snakePieces[0].transform.position, + direction = dir + }; + bends.Add(newBend); + CmdAddBend(newBend); + } + + 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) { - if (bend.position == snakePieces[i].transform.position) + snakePieces[i].direction = bend.direction; + if (i == snakePieces.Count - 1) { - snakePieces[i].direction = bend.direction; - if (i == snakePieces.Count - 1) - { - bendToRemove = bend; - } - break; + bendToRemove = bend; } - } - - if (bendToRemove != null) - { - bends.Remove(bendToRemove); + break; } } - if (queueNewPiece) + if (bendToRemove != null) { - GameObject newPiece = Instantiate(snakePiecePrefab, lastPosition, Quaternion.identity); - NetworkServer.Spawn(newPiece); - SnakePiece piece = newPiece.GetComponent(); - piece.userId = userId; - snakePieces.Add(piece); - SFXManager.instance.PlayCrunch(); - AddScore(); + bends.Remove(bendToRemove); + CmdRemoveBend(bendToRemove); + } + } - queueNewPiece = false; + if (queueNewPiece) + { + GameObject newPiece = Instantiate(snakePiecePrefab, lastPosition, Quaternion.identity); + NetworkServer.Spawn(newPiece); + SnakePiece piece = newPiece.GetComponent(); + piece.userId = userId; + snakePieces.Add(piece); + RpcAddSnakePiece(piece.netIdentity); + SFXManager.instance.PlayCrunch(); + AddScore(); + + queueNewPiece = false; + } + + // Move head + snakePieces[0].transform.position = (Vector3)curPosition + (Vector3)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); + } + + // 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; } - snakePieces[0].transform.position = curPosition + curDirection; - snakePieces[0].direction = curDirection; - snakePieces[0].isHead = true; - - 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); - } - - // 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); - } + RpcReconcileState(positions, directions, serverMoveTimer); } } - [ClientRpc] - void RpcReconcileState(Vector2[] serverPositions, Vector2[] serverDirections) + [Command] + void CmdRequestSync() { + 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, serverMoveTimer); + } + + [ClientRpc] + void RpcReconcileState(Vector2[] serverPositions, Vector2[] serverDirections, float serverTimer) + { + if (!isLocalPlayer && !isServer) + { + // For non-local players, directly update positions from server + for (int i = 0; i < snakePieces.Count; i++) + { + if (i < serverPositions.Length) + { + snakePieces[i].transform.position = serverPositions[i]; + snakePieces[i].direction = serverDirections[i]; + snakePieces[i].isHead = (i == 0); + } + } + return; + } + if (!isLocalPlayer) return; + // Sync move timer with server + moveTimer = serverTimer; + // 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) + if (Vector2.Distance(predictedPositions[i], serverPositions[0]) < PREDICTION_TOLERANCE) { matchIndex = i; break; @@ -431,15 +552,38 @@ public class SnakeController : NetworkBehaviour } else { - // No match found, reset to server state - predictedPositions.Clear(); - predictedDirections.Clear(); - - // Update snake pieces to match server state + // Check if we're significantly out of sync + bool needsSync = false; + float maxDist = 0; for (int i = 0; i < snakePieces.Count; i++) { - snakePieces[i].transform.position = serverPositions[i]; - snakePieces[i].direction = serverDirections[i]; + if (i < serverPositions.Length) + { + float distance = Vector2.Distance(snakePieces[i].transform.position, serverPositions[i]); + if (distance > SYNC_THRESHOLD) + { + maxDist = Mathf.Max(maxDist, distance); + needsSync = true; + } + } + } + + if (needsSync) + { + Debug.Log("Syncing local player, max dist: " + maxDist); + // Only update positions if significantly out of sync + for (int i = 0; i < snakePieces.Count; i++) + { + if (i < serverPositions.Length) + { + snakePieces[i].transform.position = serverPositions[i]; + snakePieces[i].direction = serverDirections[i]; + snakePieces[i].isHead = (i == 0); + } + } + // Clear predictions after sync + predictedPositions.Clear(); + predictedDirections.Clear(); } } @@ -487,6 +631,37 @@ public class SnakeController : NetworkBehaviour } } + [Command] + void CmdAddBend(BendData bend) + { + serverBends.Add(bend); + RpcAddBend(bend); + } + + [Command] + void CmdRemoveBend(BendData bend) + { + serverBends.Remove(bend); + RpcRemoveBend(bend); + } + + [ClientRpc] + void RpcAddBend(BendData bend) + { + if (!isLocalPlayer) + { + bends.Add(bend); + } + } + + [ClientRpc] + void RpcRemoveBend(BendData bend) + { + if (!isLocalPlayer) + { + bends.Remove(bend); + } + } } [System.Serializable]