This commit is contained in:
Sewmina 2025-05-30 21:24:50 +05:30
parent 71354308de
commit d4ba295f07
5 changed files with 628 additions and 154 deletions

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1f27e187933cc174ea8af8e815961672
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -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:

View File

@ -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<SnakePiece> snakePieces = new List<SnakePiece>();
public List<BendData> bends = new List<BendData>();
[SyncVar]
private List<BendData> serverBends = new List<BendData>();
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<Vector2> predictedDirections = new List<Vector2>();
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<NetworkIdentity> oldPieces, List<NetworkIdentity> 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<SnakePiece>();
if (piece != null)
{
snakePieces.Add(piece);
}
}
}
}
bool isKickstarted = false;
public void StartGame()
{
if (!isServer) return;
snakePieces = new List<SnakePiece>();
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<SnakePiece>();
piece.userId = userId;
snakePieces.Add(piece);
RpcAddSnakePiece(piece.netIdentity);
}
isKickstarted = true;
isDead = false;
}
[ClientRpc]
void RpcAddSnakePiece(NetworkIdentity netId)
{
var piece = netId.GetComponent<SnakePiece>();
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<SnakePiece>();
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<SnakePiece>();
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]