Player.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using UnityEngine;
  2. using Mirror;
  3. using System.Collections.Generic;
  4. using System.Collections;
  5. using CustomExtensions;
  6. public class Player : NetworkBehaviour
  7. {
  8. private float timer;
  9. private int currentTick;
  10. private float minTimeBetweenTicks;
  11. private const float SERVER_TICK_RATE = 30f;
  12. private const int BUFFER_SIZE = 1024;
  13. // Client specific
  14. private StatePayload[] client_stateBuffer;
  15. private InputPayload[] inputBuffer;
  16. private StatePayload latestServerState;
  17. private StatePayload lastProcessedState;
  18. private float horizontalInput;
  19. private float verticalInput;
  20. private StatePayload[] stateBuffer;
  21. private Queue<InputPayload> inputQueue;
  22. void Start()
  23. {
  24. minTimeBetweenTicks = 1f / SERVER_TICK_RATE;
  25. stateBuffer = new StatePayload[BUFFER_SIZE];
  26. inputBuffer = new InputPayload[BUFFER_SIZE];
  27. inputQueue = new Queue<InputPayload>();
  28. }
  29. void Update()
  30. {
  31. horizontalInput = Input.GetAxis("Horizontal");
  32. verticalInput = Input.GetAxis("Vertical");
  33. timer += Time.deltaTime;
  34. while (timer >= minTimeBetweenTicks)
  35. {
  36. timer -= minTimeBetweenTicks;
  37. if(isServer){
  38. HandleTick();
  39. }else if(isLocalPlayer){
  40. HandleTickClient();
  41. }
  42. currentTick++;
  43. }
  44. }
  45. public void OnClientInput(InputPayload inputPayload){
  46. if(isServer){
  47. m_OnClientInput(inputPayload);
  48. }else{
  49. CmdOnClientInput(inputPayload);
  50. }
  51. }
  52. [Command]
  53. public void CmdOnClientInput(InputPayload inputPayload){
  54. m_OnClientInput(inputPayload);
  55. }
  56. public void m_OnClientInput(InputPayload inputPayload)
  57. {
  58. inputQueue.Enqueue(inputPayload);
  59. }
  60. IEnumerator SendToClient(StatePayload statePayload)
  61. {
  62. yield return new WaitForSeconds(0.02f);
  63. OnServerMovementState(statePayload);
  64. }
  65. void HandleTick()
  66. {
  67. // Process the input queue
  68. int bufferIndex = -1;
  69. while(inputQueue.Count > 0)
  70. {
  71. InputPayload inputPayload = inputQueue.Dequeue();
  72. bufferIndex = inputPayload.tick % BUFFER_SIZE;
  73. StatePayload statePayload = ProcessMovement(inputPayload);
  74. stateBuffer[bufferIndex] = statePayload;
  75. }
  76. if (bufferIndex != -1)
  77. {
  78. StartCoroutine(SendToClient(stateBuffer[bufferIndex]));
  79. }
  80. }
  81. void HandleTickClient()
  82. {
  83. if (!latestServerState.Equals(default(StatePayload)) &&
  84. (lastProcessedState.Equals(default(StatePayload)) ||
  85. !latestServerState.Equals(lastProcessedState)))
  86. {
  87. HandleServerReconciliation();
  88. }
  89. int bufferIndex = currentTick % BUFFER_SIZE;
  90. // Add payload to inputBuffer
  91. InputPayload inputPayload = new InputPayload();
  92. inputPayload.tick = currentTick;
  93. inputPayload.inputVector = new Vector3(horizontalInput, 0, verticalInput);
  94. inputBuffer[bufferIndex] = inputPayload;
  95. // Add payload to stateBuffer
  96. stateBuffer[bufferIndex] = ProcessMovement(inputPayload);
  97. // Send input to server
  98. StartCoroutine(SendToServer(inputPayload));
  99. }
  100. void HandleServerReconciliation()
  101. {
  102. lastProcessedState = latestServerState;
  103. int serverStateBufferIndex = latestServerState.tick % BUFFER_SIZE;
  104. float positionError = Vector3.Distance(latestServerState.position, stateBuffer[serverStateBufferIndex].position);
  105. float rotationError = CustomExtensions.QuaternionExtensions.Difference(latestServerState.rotation, stateBuffer[serverStateBufferIndex].rotation);
  106. if (positionError > 0.001f || rotationError > 0.001f)
  107. {
  108. Debug.Log($"We have to reconcile bro, Errors\npos:{positionError}, rot:{rotationError}");
  109. // Rewind & Replay
  110. transform.position = latestServerState.position;
  111. transform.rotation = latestServerState.rotation;
  112. // Update buffer at index of latest server state
  113. stateBuffer[serverStateBufferIndex] = latestServerState;
  114. // Now re-simulate the rest of the ticks up to the current tick on the client
  115. int tickToProcess = latestServerState.tick + 1;
  116. while (tickToProcess < currentTick)
  117. {
  118. int bufferIndex = tickToProcess % BUFFER_SIZE;
  119. // Process new movement with reconciled state
  120. StatePayload statePayload = ProcessMovement(inputBuffer[bufferIndex]);
  121. // Update buffer with recalculated state
  122. stateBuffer[bufferIndex] = statePayload;
  123. tickToProcess++;
  124. }
  125. }
  126. }
  127. public void m_OnServerMovementState(StatePayload serverState)
  128. {
  129. latestServerState = serverState;
  130. }
  131. public void OnServerMovementState(StatePayload serverState){
  132. if(isServer){
  133. RpcOnServerMovementState(serverState);
  134. }else{
  135. m_OnServerMovementState(serverState);
  136. // CmdOnServerMovementState(serverState);
  137. }
  138. }
  139. [ClientRpc]
  140. public void RpcOnServerMovementState(StatePayload serverState){
  141. m_OnServerMovementState(serverState);
  142. Debug.Log(serverState.position + ":" + transform.position);
  143. }
  144. IEnumerator SendToServer(InputPayload inputPayload)
  145. {
  146. yield return new WaitForSeconds(0.02f);
  147. OnClientInput(inputPayload);
  148. }
  149. StatePayload ProcessMovement(InputPayload input)
  150. {
  151. // Should always be in sync with same function on Client
  152. transform.position += input.inputVector * 5f * minTimeBetweenTicks;
  153. transform.Rotate(input.inputVector);
  154. return new StatePayload()
  155. {
  156. tick = input.tick,
  157. position = transform.position,
  158. rotation = transform.rotation
  159. };
  160. }
  161. }
  162. public struct InputPayload
  163. {
  164. public int tick;
  165. public Vector3 inputVector;
  166. public override string ToString()
  167. {
  168. return JsonUtility.ToJson(this);
  169. }
  170. public static InputPayload Parse(string json){
  171. return JsonUtility.FromJson<InputPayload>(json);
  172. }
  173. }
  174. public struct StatePayload
  175. {
  176. public int tick;
  177. public Vector3 position;
  178. public Quaternion rotation;
  179. public override string ToString()
  180. {
  181. return JsonUtility.ToJson(this);
  182. }
  183. public static StatePayload Parse(string json){
  184. return JsonUtility.FromJson<StatePayload>(json);
  185. }
  186. }