init
This commit is contained in:
73
Assets/Scripts/Helpers.cs
Normal file
73
Assets/Scripts/Helpers.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
using CustomExtensions;
|
||||
using System;
|
||||
|
||||
public struct InputState{
|
||||
public int Tick;
|
||||
public Vector2 Input;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"tick: {Tick}, input: {Input}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
InputState _obj = (InputState) obj;
|
||||
if(_obj.Input == Input){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CloseEnough(InputState other, float threshold) => Mathf.Abs((other.Input-Input).magnitude) < threshold;
|
||||
}
|
||||
|
||||
public struct PlayerState{
|
||||
public int Tick;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"tick: {Tick},pos: {Position}, rot: {Rotation}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
PlayerState _obj = (PlayerState) obj;
|
||||
if(_obj.Position == Position && _obj.Rotation == Rotation){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CloseEnough(PlayerState other, float threshold) => Mathf.Abs((other.Position-Position).magnitude) < threshold && Rotation.Approximately(other.Rotation, threshold);
|
||||
|
||||
public float Difference(PlayerState other){
|
||||
return (Mathf.Abs((other.Position-Position).magnitude) + Rotation.Difference(other.Rotation))/2f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Helpers{
|
||||
public static int unixTimestamp = (int)System.DateTime.UtcNow.Subtract(new System.DateTime(1970, 1, 1)).TotalSeconds;
|
||||
|
||||
}
|
||||
|
||||
namespace CustomExtensions{
|
||||
public static class QuaternionExtensions{
|
||||
public static bool Approximately(this Quaternion quatA, Quaternion value, float acceptableRange)
|
||||
{
|
||||
return 1 - Mathf.Abs(Quaternion.Dot(quatA, value)) < acceptableRange;
|
||||
}
|
||||
public static float Difference(this Quaternion quatA, Quaternion value){
|
||||
return 1 - Mathf.Abs(Quaternion.Dot(quatA, value));
|
||||
}
|
||||
|
||||
public static long ToUnix(this DateTime time){
|
||||
return (long)time.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Helpers.cs.meta
Normal file
11
Assets/Scripts/Helpers.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7017886b6933ab24ebd07098772bf027
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/Scripts/Logger.cs
Normal file
67
Assets/Scripts/Logger.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
public class Logger
|
||||
{
|
||||
public static bool Enabled = true;
|
||||
private static Logger m_instance = null;
|
||||
private static string ApplicationDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
string path = Application.dataPath;
|
||||
if (Application.platform == RuntimePlatform.OSXPlayer)
|
||||
{
|
||||
path += "/../../";
|
||||
}
|
||||
else if (Application.platform == RuntimePlatform.WindowsPlayer)
|
||||
{
|
||||
path += "/../";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
public string LogFilePath {get; private set;}
|
||||
public static Logger instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_instance == null)
|
||||
{
|
||||
m_instance = new Logger();
|
||||
}
|
||||
|
||||
return m_instance;
|
||||
}
|
||||
}
|
||||
|
||||
public Logger()
|
||||
{
|
||||
if(!Enabled){return;}
|
||||
Debug.Log("Starting logger @ " + ApplicationDirectory);
|
||||
if(LogFilePath == null){
|
||||
LogFilePath = ApplicationDirectory + "Log.txt";
|
||||
}
|
||||
File.WriteAllText(LogFilePath, "Logger initiated at " + DateTime.Now + "\n\n");
|
||||
}
|
||||
|
||||
public void log(string message){
|
||||
if(!Enabled){return;}
|
||||
|
||||
File.AppendAllText(LogFilePath,$"[{DateTime.Now}] {message}\n");
|
||||
Debug.Log(message);
|
||||
}
|
||||
|
||||
public static void Log(string message){
|
||||
instance.log(message);
|
||||
}
|
||||
|
||||
public static void SetFileName(string fileName){
|
||||
instance.LogFilePath = ApplicationDirectory+fileName + ".txt";
|
||||
}
|
||||
|
||||
public static void SetFilePath(string path){
|
||||
instance.LogFilePath= path;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Logger.cs.meta
Normal file
11
Assets/Scripts/Logger.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67433c855e8eda8439e0a3ed85f45d60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
231
Assets/Scripts/Player.cs
Normal file
231
Assets/Scripts/Player.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Player.cs.meta
Normal file
11
Assets/Scripts/Player.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8682d5131ce872b40a46bbf0d8e273c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user