cpmirror/Assets/Scripts/Player.cs
2023-11-28 11:41:03 +05:30

232 lines
6.5 KiB
C#

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);
}
}