HoangMinhNguyen

Position Sync

Jul 13th, 2025
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.07 KB | None | 0 0
  1. using UnityEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Net.WebSockets;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8.  
  9. public class UnityPositionSync : MonoBehaviour
  10. {
  11.     [Header("Network Settings")]
  12.     public string serverUrl = "ws://localhost:8386";
  13.     public string gameId = "test_game";
  14.     public string authToken = "your_auth_token_here";
  15.    
  16.     [Header("Position Settings")]
  17.     public float updateRate = 10f; // Server updates per second
  18.     public float interpolationDelay = 0.1f; // 100ms delay for interpolation
  19.     public float extrapolationTime = 0.05f; // 50ms extrapolation for smooth movement
  20.    
  21.     [Header("Interpolation Settings")]
  22.     public bool useInterpolation = true;
  23.     public bool useExtrapolation = true;
  24.     public float maxExtrapolationTime = 0.2f; // Maximum extrapolation time
  25.    
  26.     private ClientWebSocket webSocket;
  27.     private CancellationTokenSource cancellationTokenSource;
  28.     private bool isConnected = false;
  29.     private short mySlot = -1;
  30.    
  31.     // Position tracking
  32.     private Vector3 myPosition;
  33.     private Dictionary<short, PlayerData> otherPlayers = new Dictionary<short, PlayerData>();
  34.    
  35.     // Advanced interpolation
  36.     private Dictionary<short, InterpolationData> interpolationData = new Dictionary<short, InterpolationData>();
  37.     private Dictionary<short, ExtrapolationData> extrapolationData = new Dictionary<short, ExtrapolationData>();
  38.    
  39.     [System.Serializable]
  40.     public class PlayerData
  41.     {
  42.         public Vector3 position;
  43.         public Vector3 velocity;
  44.         public long timestamp;
  45.         public Vector3 targetPosition;
  46.         public float interpolationTime;
  47.         public bool isMoving;
  48.     }
  49.    
  50.     [System.Serializable]
  51.     public class InterpolationData
  52.     {
  53.         public Vector3 startPosition;
  54.         public Vector3 endPosition;
  55.         public Vector3 startVelocity;
  56.         public Vector3 endVelocity;
  57.         public float startTime;
  58.         public float duration;
  59.         public bool isActive;
  60.     }
  61.    
  62.     [System.Serializable]
  63.     public class ExtrapolationData
  64.     {
  65.         public Vector3 position;
  66.         public Vector3 velocity;
  67.         public float startTime;
  68.         public bool isActive;
  69.     }
  70.    
  71.     void Start()
  72.     {
  73.         ConnectToServer();
  74.     }
  75.    
  76.     void Update()
  77.     {
  78.         // Update interpolation and extrapolation for other players
  79.         if (useInterpolation)
  80.             UpdateInterpolation();
  81.        
  82.         if (useExtrapolation)
  83.             UpdateExtrapolation();
  84.        
  85.         // Send position updates
  86.         if (isConnected && mySlot != -1)
  87.         {
  88.             SendPositionUpdate();
  89.         }
  90.     }
  91.    
  92.     void OnDestroy()
  93.     {
  94.         DisconnectFromServer();
  95.     }
  96.    
  97.     async void ConnectToServer()
  98.     {
  99.         try
  100.         {
  101.             webSocket = new ClientWebSocket();
  102.             cancellationTokenSource = new CancellationTokenSource();
  103.            
  104.             var uri = new Uri($"{serverUrl}?token={authToken}&gameId={gameId}");
  105.             await webSocket.ConnectAsync(uri, cancellationTokenSource.Token);
  106.            
  107.             isConnected = true;
  108.             Debug.Log("Connected to game server");
  109.            
  110.             // Start receiving messages
  111.             _ = ReceiveMessages();
  112.            
  113.             // Start sending position updates
  114.             InvokeRepeating(nameof(SendPositionUpdate), 0f, 1f / updateRate);
  115.            
  116.         }
  117.         catch (Exception e)
  118.         {
  119.             Debug.LogError($"Failed to connect: {e.Message}");
  120.         }
  121.     }
  122.    
  123.     async void DisconnectFromServer()
  124.     {
  125.         if (webSocket != null)
  126.         {
  127.             isConnected = false;
  128.             cancellationTokenSource?.Cancel();
  129.            
  130.             try
  131.             {
  132.                 await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Disconnecting", CancellationToken.None);
  133.             }
  134.             catch (Exception e)
  135.             {
  136.                 Debug.LogError($"Error during disconnect: {e.Message}");
  137.             }
  138.         }
  139.     }
  140.    
  141.     async Task ReceiveMessages()
  142.     {
  143.         var buffer = new byte[4096];
  144.        
  145.         while (isConnected && webSocket.State == WebSocketState.Open)
  146.         {
  147.             try
  148.             {
  149.                 var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationTokenSource.Token);
  150.                
  151.                 if (result.MessageType == WebSocketMessageType.Binary)
  152.                 {
  153.                     ProcessMessage(buffer, result.Count);
  154.                 }
  155.                 else if (result.MessageType == WebSocketMessageType.Close)
  156.                 {
  157.                     Debug.Log("Server closed connection");
  158.                     break;
  159.                 }
  160.             }
  161.             catch (Exception e)
  162.             {
  163.                 Debug.LogError($"Error receiving message: {e.Message}");
  164.                 break;
  165.             }
  166.         }
  167.        
  168.         isConnected = false;
  169.     }
  170.    
  171.     void ProcessMessage(byte[] data, int count)
  172.     {
  173.         try
  174.         {
  175.             using (var stream = new MemoryStream(data, 0, count))
  176.             using (var reader = new BinaryReader(stream))
  177.             {
  178.                 // Read TLV header
  179.                 short messageType = reader.ReadInt16();
  180.                 int messageLength = reader.ReadInt32();
  181.                
  182.                 Debug.Log($"Received message type: {messageType}, length: {messageLength}");
  183.                
  184.                 switch (messageType)
  185.                 {
  186.                     case 2: // AUTHENTICATION_SEND
  187.                         ProcessAuthenticationResponse(reader);
  188.                         break;
  189.                        
  190.                     case 8: // POSITION_UPDATE_SEND
  191.                         ProcessPositionUpdate(reader);
  192.                         break;
  193.                        
  194.                     case 12: // INFO_PLAYERS_IN_ROOM_SEND
  195.                         ProcessPlayerInfo(reader);
  196.                         break;
  197.                        
  198.                     default:
  199.                         Debug.LogWarning($"Unknown message type: {messageType}");
  200.                         break;
  201.                 }
  202.             }
  203.         }
  204.         catch (Exception e)
  205.         {
  206.             Debug.LogError($"Error processing message: {e.Message}");
  207.         }
  208.     }
  209.    
  210.     void ProcessAuthenticationResponse(BinaryReader reader)
  211.     {
  212.         int statusCode = reader.ReadInt32();
  213.         int messageLength = reader.ReadInt32();
  214.         string message = System.Text.Encoding.UTF8.GetString(reader.ReadBytes(messageLength));
  215.        
  216.         Debug.Log($"Authentication response: {statusCode} - {message}");
  217.        
  218.         if (statusCode == 8386) // SUCCESS
  219.         {
  220.             Debug.Log("Authentication successful!");
  221.         }
  222.         else
  223.         {
  224.             Debug.LogError("Authentication failed!");
  225.         }
  226.     }
  227.    
  228.     void ProcessPlayerInfo(BinaryReader reader)
  229.     {
  230.         short playerCount = reader.ReadInt16();
  231.         Debug.Log($"Received player info for {playerCount} players");
  232.        
  233.         for (int i = 0; i < playerCount; i++)
  234.         {
  235.             int usernameLength = reader.ReadInt32();
  236.             string username = System.Text.Encoding.UTF8.GetString(reader.ReadBytes(usernameLength));
  237.             short slot = reader.ReadInt16();
  238.            
  239.             Debug.Log($"Player {username} assigned to slot {slot}");
  240.            
  241.             // If this is our username, remember our slot
  242.             if (username == "YourUsername") // Replace with actual username logic
  243.             {
  244.                 mySlot = slot;
  245.                 Debug.Log($"I am assigned to slot {mySlot}");
  246.             }
  247.         }
  248.     }
  249.    
  250.     void ProcessPositionUpdate(BinaryReader reader)
  251.     {
  252.         short playerCount = reader.ReadInt16();
  253.        
  254.         for (int i = 0; i < playerCount; i++)
  255.         {
  256.             short slot = reader.ReadInt16();
  257.             float x = reader.ReadSingle();
  258.             float y = reader.ReadSingle();
  259.            
  260.             // Skip our own position updates
  261.             if (slot == mySlot)
  262.                 continue;
  263.            
  264.             Vector3 newPosition = new Vector3(x, 0, y); // Assuming Y is up in Unity
  265.            
  266.             // Update player data
  267.             if (!otherPlayers.ContainsKey(slot))
  268.             {
  269.                 otherPlayers[slot] = new PlayerData();
  270.             }
  271.            
  272.             var playerData = otherPlayers[slot];
  273.             Vector3 oldPosition = playerData.position;
  274.            
  275.             // Calculate velocity based on position change
  276.             float deltaTime = (Time.time - playerData.interpolationTime);
  277.             if (deltaTime > 0)
  278.             {
  279.                 playerData.velocity = (newPosition - oldPosition) / deltaTime;
  280.             }
  281.            
  282.             playerData.targetPosition = newPosition;
  283.             playerData.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  284.             playerData.interpolationTime = Time.time;
  285.             playerData.isMoving = Vector3.Distance(oldPosition, newPosition) > 0.01f;
  286.            
  287.             // Setup interpolation
  288.             if (useInterpolation)
  289.             {
  290.                 SetupInterpolation(slot, oldPosition, newPosition, playerData.velocity);
  291.             }
  292.            
  293.             // Setup extrapolation
  294.             if (useExtrapolation && playerData.isMoving)
  295.             {
  296.                 SetupExtrapolation(slot, newPosition, playerData.velocity);
  297.             }
  298.            
  299.             Debug.Log($"Received position for slot {slot}: ({x}, {y})");
  300.         }
  301.        
  302.         // Read timestamp
  303.         long timestamp = reader.ReadInt64();
  304.     }
  305.    
  306.     void SetupInterpolation(short slot, Vector3 startPos, Vector3 endPos, Vector3 velocity)
  307.     {
  308.         if (!interpolationData.ContainsKey(slot))
  309.         {
  310.             interpolationData[slot] = new InterpolationData();
  311.         }
  312.        
  313.         var interp = interpolationData[slot];
  314.         interp.startPosition = startPos;
  315.         interp.endPosition = endPos;
  316.         interp.startVelocity = velocity;
  317.         interp.endVelocity = velocity; // Assume constant velocity for simplicity
  318.         interp.startTime = Time.time + interpolationDelay; // Delay interpolation
  319.         interp.duration = interpolationDelay;
  320.         interp.isActive = true;
  321.     }
  322.    
  323.     void SetupExtrapolation(short slot, Vector3 position, Vector3 velocity)
  324.     {
  325.         if (!extrapolationData.ContainsKey(slot))
  326.         {
  327.             extrapolationData[slot] = new ExtrapolationData();
  328.         }
  329.        
  330.         var extrap = extrapolationData[slot];
  331.         extrap.position = position;
  332.         extrap.velocity = velocity;
  333.         extrap.startTime = Time.time;
  334.         extrap.isActive = true;
  335.     }
  336.    
  337.     void UpdateInterpolation()
  338.     {
  339.         float currentTime = Time.time;
  340.        
  341.         foreach (var kvp in interpolationData)
  342.         {
  343.             short slot = kvp.Key;
  344.             var interp = kvp.Value;
  345.            
  346.             if (!interp.isActive || !otherPlayers.ContainsKey(slot))
  347.                 continue;
  348.            
  349.             var playerData = otherPlayers[slot];
  350.            
  351.             // Wait for interpolation delay
  352.             if (currentTime < interp.startTime)
  353.                 continue;
  354.            
  355.             float elapsed = currentTime - interp.startTime;
  356.             float t = Mathf.Clamp01(elapsed / interp.duration);
  357.            
  358.             // Smooth interpolation with easing
  359.             float smoothT = SmoothStep(t);
  360.            
  361.             // Interpolate position
  362.             playerData.position = Vector3.Lerp(interp.startPosition, interp.endPosition, smoothT);
  363.            
  364.             // Interpolate velocity (optional)
  365.             playerData.velocity = Vector3.Lerp(interp.startVelocity, interp.endVelocity, smoothT);
  366.            
  367.             // Remove interpolation data when complete
  368.             if (t >= 1f)
  369.             {
  370.                 interp.isActive = false;
  371.             }
  372.         }
  373.     }
  374.    
  375.     void UpdateExtrapolation()
  376.     {
  377.         float currentTime = Time.time;
  378.        
  379.         foreach (var kvp in extrapolationData)
  380.         {
  381.             short slot = kvp.Key;
  382.             var extrap = kvp.Value;
  383.            
  384.             if (!extrap.isActive || !otherPlayers.ContainsKey(slot))
  385.                 continue;
  386.            
  387.             var playerData = otherPlayers[slot];
  388.            
  389.             float elapsed = currentTime - extrap.startTime;
  390.            
  391.             // Limit extrapolation time
  392.             if (elapsed > maxExtrapolationTime)
  393.             {
  394.                 extrap.isActive = false;
  395.                 continue;
  396.             }
  397.            
  398.             // Only extrapolate if no active interpolation
  399.             if (!interpolationData.ContainsKey(slot) || !interpolationData[slot].isActive)
  400.             {
  401.                 // Extrapolate position based on velocity
  402.                 Vector3 extrapolatedPos = extrap.position + extrap.velocity * elapsed;
  403.                 playerData.position = extrapolatedPos;
  404.             }
  405.         }
  406.     }
  407.    
  408.     float SmoothStep(float t)
  409.     {
  410.         // Smooth step function for better interpolation
  411.         return t * t * (3f - 2f * t);
  412.     }
  413.    
  414.     void SendPositionUpdate()
  415.     {
  416.         if (!isConnected || mySlot == -1)
  417.             return;
  418.        
  419.         try
  420.         {
  421.             // Get current position (you can modify this based on your character controller)
  422.             Vector3 currentPos = transform.position;
  423.            
  424.             // Create position update message
  425.             using (var stream = new MemoryStream())
  426.             using (var writer = new BinaryWriter(stream))
  427.             {
  428.                 // TLV header
  429.                 writer.Write((short)7); // POSITION_UPDATE_RECEIVE
  430.                
  431.                 // Calculate message length (slot + x + y + timestamp)
  432.                 int messageLength = 2 + 4 + 4 + 8; // short + float + float + long
  433.                 writer.Write(messageLength);
  434.                
  435.                 // Message content
  436.                 writer.Write(mySlot);
  437.                 writer.Write(currentPos.x);
  438.                 writer.Write(currentPos.z); // Use Z as Y for 2D game
  439.                 writer.Write(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
  440.                
  441.                 // Send message
  442.                 var messageBytes = stream.ToArray();
  443.                 webSocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Binary, true, cancellationTokenSource.Token);
  444.             }
  445.         }
  446.         catch (Exception e)
  447.         {
  448.             Debug.LogError($"Error sending position update: {e.Message}");
  449.         }
  450.     }
  451.    
  452.     // Public method to get other player positions (for rendering)
  453.     public Dictionary<short, Vector3> GetOtherPlayerPositions()
  454.     {
  455.         var positions = new Dictionary<short, Vector3>();
  456.         foreach (var kvp in otherPlayers)
  457.         {
  458.             positions[kvp.Key] = kvp.Value.position;
  459.         }
  460.         return positions;
  461.     }
  462.    
  463.     // Public method to get other player velocities (for rendering)
  464.     public Dictionary<short, Vector3> GetOtherPlayerVelocities()
  465.     {
  466.         var velocities = new Dictionary<short, Vector3>();
  467.         foreach (var kvp in otherPlayers)
  468.         {
  469.             velocities[kvp.Key] = kvp.Value.velocity;
  470.         }
  471.         return velocities;
  472.     }
  473.    
  474.     // Public method to get our slot
  475.     public short GetMySlot()
  476.     {
  477.         return mySlot;
  478.     }
  479.    
  480.     // Public method to check connection status
  481.     public bool IsConnected()
  482.     {
  483.         return isConnected;
  484.     }
  485.    
  486.     // Public method to get interpolation status for debugging
  487.     public Dictionary<short, bool> GetInterpolationStatus()
  488.     {
  489.         var status = new Dictionary<short, bool>();
  490.         foreach (var kvp in interpolationData)
  491.         {
  492.             status[kvp.Key] = kvp.Value.isActive;
  493.         }
  494.         return status;
  495.     }
  496. }
Advertisement
Add Comment
Please, Sign In to add comment