123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- using UnityEngine;
- using Mirror;
- using System.Collections.Generic;
- using System.Collections;
- using CustomExtensions;
- public class Player : NetworkBehaviour
- {
- private float timer;
- private int currentTick;
- private float minTimeBetweenTicks;
- private const float SERVER_TICK_RATE = 30f;
- private const int BUFFER_SIZE = 1024;
- // Client specific
- private StatePayload[] client_stateBuffer;
- private InputPayload[] inputBuffer;
- private StatePayload latestServerState;
- private StatePayload lastProcessedState;
- private float horizontalInput;
- private float verticalInput;
- private StatePayload[] stateBuffer;
- private Queue<InputPayload> inputQueue;
- void Start()
- {
- minTimeBetweenTicks = 1f / SERVER_TICK_RATE;
- stateBuffer = new StatePayload[BUFFER_SIZE];
- inputBuffer = new InputPayload[BUFFER_SIZE];
- inputQueue = new Queue<InputPayload>();
- }
- void Update()
- {
- horizontalInput = Input.GetAxis("Horizontal");
- verticalInput = Input.GetAxis("Vertical");
- timer += Time.deltaTime;
- while (timer >= minTimeBetweenTicks)
- {
- timer -= minTimeBetweenTicks;
- if(isServer){
- HandleTick();
- }else if(isLocalPlayer){
- HandleTickClient();
- }
- currentTick++;
- }
- }
- public void OnClientInput(InputPayload inputPayload){
- if(isServer){
- m_OnClientInput(inputPayload);
- }else{
- CmdOnClientInput(inputPayload);
- }
- }
- [Command]
- public void CmdOnClientInput(InputPayload inputPayload){
- m_OnClientInput(inputPayload);
- }
- public void m_OnClientInput(InputPayload inputPayload)
- {
- inputQueue.Enqueue(inputPayload);
- }
- IEnumerator SendToClient(StatePayload statePayload)
- {
- yield return new WaitForSeconds(0.02f);
- OnServerMovementState(statePayload);
- }
- void HandleTick()
- {
- // Process the input queue
- int bufferIndex = -1;
- while(inputQueue.Count > 0)
- {
- InputPayload inputPayload = inputQueue.Dequeue();
- bufferIndex = inputPayload.tick % BUFFER_SIZE;
- StatePayload statePayload = ProcessMovement(inputPayload);
- stateBuffer[bufferIndex] = statePayload;
- }
- if (bufferIndex != -1)
- {
- StartCoroutine(SendToClient(stateBuffer[bufferIndex]));
- }
- }
- void HandleTickClient()
- {
- if (!latestServerState.Equals(default(StatePayload)) &&
- (lastProcessedState.Equals(default(StatePayload)) ||
- !latestServerState.Equals(lastProcessedState)))
- {
- HandleServerReconciliation();
- }
- int bufferIndex = currentTick % BUFFER_SIZE;
- // Add payload to inputBuffer
- InputPayload inputPayload = new InputPayload();
- inputPayload.tick = currentTick;
- inputPayload.inputVector = new Vector3(horizontalInput, 0, verticalInput);
- inputBuffer[bufferIndex] = inputPayload;
- // Add payload to stateBuffer
- stateBuffer[bufferIndex] = ProcessMovement(inputPayload);
- // Send input to server
- StartCoroutine(SendToServer(inputPayload));
- }
- void HandleServerReconciliation()
- {
- lastProcessedState = latestServerState;
- int serverStateBufferIndex = latestServerState.tick % BUFFER_SIZE;
- float positionError = Vector3.Distance(latestServerState.position, stateBuffer[serverStateBufferIndex].position);
- float rotationError = CustomExtensions.QuaternionExtensions.Difference(latestServerState.rotation, stateBuffer[serverStateBufferIndex].rotation);
- if (positionError > 0.001f || rotationError > 0.001f)
- {
- Debug.Log($"We have to reconcile bro, Errors\npos:{positionError}, rot:{rotationError}");
- // Rewind & Replay
- transform.position = latestServerState.position;
- transform.rotation = latestServerState.rotation;
- // Update buffer at index of latest server state
- stateBuffer[serverStateBufferIndex] = latestServerState;
- // Now re-simulate the rest of the ticks up to the current tick on the client
- int tickToProcess = latestServerState.tick + 1;
- while (tickToProcess < currentTick)
- {
- int bufferIndex = tickToProcess % BUFFER_SIZE;
- // Process new movement with reconciled state
- StatePayload statePayload = ProcessMovement(inputBuffer[bufferIndex]);
- // Update buffer with recalculated state
- stateBuffer[bufferIndex] = statePayload;
- tickToProcess++;
- }
- }
- }
- public void m_OnServerMovementState(StatePayload serverState)
- {
- latestServerState = serverState;
- }
- public void OnServerMovementState(StatePayload serverState){
- if(isServer){
- RpcOnServerMovementState(serverState);
- }else{
- m_OnServerMovementState(serverState);
- // CmdOnServerMovementState(serverState);
- }
- }
- [ClientRpc]
- public void RpcOnServerMovementState(StatePayload serverState){
- m_OnServerMovementState(serverState);
- Debug.Log(serverState.position + ":" + transform.position);
-
- }
- IEnumerator SendToServer(InputPayload inputPayload)
- {
- yield return new WaitForSeconds(0.02f);
- OnClientInput(inputPayload);
- }
- StatePayload ProcessMovement(InputPayload input)
- {
- // Should always be in sync with same function on Client
- transform.position += input.inputVector * 5f * minTimeBetweenTicks;
- transform.Rotate(input.inputVector);
- return new StatePayload()
- {
- tick = input.tick,
- position = transform.position,
- rotation = transform.rotation
- };
- }
- }
- public struct InputPayload
- {
- public int tick;
- public Vector3 inputVector;
- public override string ToString()
- {
- return JsonUtility.ToJson(this);
- }
- public static InputPayload Parse(string json){
- return JsonUtility.FromJson<InputPayload>(json);
- }
- }
- public struct StatePayload
- {
- public int tick;
- public Vector3 position;
- public Quaternion rotation;
- public override string ToString()
- {
- return JsonUtility.ToJson(this);
- }
- public static StatePayload Parse(string json){
- return JsonUtility.FromJson<StatePayload>(json);
- }
- }
|