Created
January 31, 2019 05:01
-
-
Save Lachee/0d5ca7a4f4e9a19a35d878b3fc815834 to your computer and use it in GitHub Desktop.
Conversion of a script I use to use for all my rigidbodies in legacy networking.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
namespace Mirror | |
{ | |
[DisallowMultipleComponent] | |
[AddComponentMenu("Network/NetworkRigidbody2D")] | |
[RequireComponent(typeof(Rigidbody2D))] | |
public class NetworkRigidbody2D : NetworkBehaviour | |
{ | |
private const int BUFFER_SIZE = 50; | |
public double interpolationBackTime = 0.1; | |
public double extrapolationLimit = 0.5; | |
[Tooltip("Compresses 16 Byte Quaternion into None=12, Some=6, Much=3, Lots=2 Byte")] | |
[SerializeField] Compression compressRotation = Compression.Much; | |
public enum Compression { None, Much, Lots }; // easily understandable and funny | |
[Header("Debug")] | |
private new Rigidbody2D rigidbody; | |
private bool _kinematic = false; | |
private State[] _bufferedStates = new State[BUFFER_SIZE]; // We store twenty states with "playback" information | |
private int _bufferCount; // Keep track of what slots are used | |
private float _lastClientSyncTime = 0; | |
[System.Serializable] | |
private struct State | |
{ | |
public double timestamp; | |
public Vector3 position; | |
public Vector3 velocity; | |
public float rotation; | |
public float angularVelocity; | |
} | |
private void Start() | |
{ | |
//Fetch the rigidbody | |
rigidbody = GetComponent<Rigidbody2D>(); | |
} | |
[Command] | |
private void CmdClientToServerSync(byte[] payload) | |
{ | |
// local authority client sends sync message to server for broadcasting | |
// deserialize payload | |
NetworkReader reader = new NetworkReader(payload); | |
Deserialize(reader); | |
// set dirty so that OnSerialize broadcasts it | |
SetDirtyBit(1UL); | |
} | |
public override bool OnSerialize(NetworkWriter writer, bool initialState) | |
{ | |
Serialize(writer); | |
return true; | |
} | |
public override void OnDeserialize(NetworkReader reader, bool initialState) | |
{ | |
Deserialize(reader); | |
} | |
public void Serialize(NetworkWriter writer) { | |
//Update the rigidbody if its null. | |
if (rigidbody == null) | |
rigidbody = GetComponent<Rigidbody2D>(); | |
try | |
{ | |
//Position / Velocity | |
writer.Write(rigidbody.position); | |
writer.Write(rigidbody.velocity); | |
//Rotation / Angular Velocity | |
switch (compressRotation) | |
{ | |
case Compression.None: | |
writer.Write(rigidbody.rotation); | |
writer.Write(rigidbody.angularVelocity); | |
break; | |
case Compression.Much: | |
writer.Write(Utils.ScaleFloatToByte(rigidbody.rotation, 0, 360, byte.MinValue, byte.MaxValue)); | |
writer.Write(Utils.ScaleFloatToByte(rigidbody.angularVelocity, 0, 360, byte.MinValue, byte.MaxValue)); | |
break; | |
case Compression.Lots: | |
writer.Write(Utils.PackThreeFloatsIntoUShort(rigidbody.rotation, rigidbody.angularVelocity, 0, 0, 360)); | |
break; | |
} | |
} | |
catch (System.Exception e) | |
{ | |
Debug.LogError("Exception while serializing!"); | |
Debug.LogException(e); | |
} | |
} | |
public void Deserialize(NetworkReader reader) | |
{ | |
//Update the rigidbody if its null. | |
if (rigidbody == null) | |
rigidbody = GetComponent<Rigidbody2D>(); | |
try | |
{ | |
//Prepare the state | |
State state = new State(); | |
state.timestamp = NetworkTime.time; | |
//Validate the kinematic | |
//_kinematic = rigidbody.isKinematic = reader.ReadBoolean(); | |
//Position / Velocity | |
state.position = reader.ReadVector2(); | |
state.velocity = reader.ReadVector2(); | |
//Rotation / Angular Velocity | |
switch(compressRotation) | |
{ | |
case Compression.None: | |
state.rotation = reader.ReadSingle(); | |
state.angularVelocity = reader.ReadSingle(); | |
break; | |
case Compression.Much: | |
state.rotation = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); | |
state.angularVelocity = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); | |
break; | |
case Compression.Lots: | |
float[] pack = Utils.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360); | |
state.rotation = pack[0]; | |
state.angularVelocity = pack[1]; | |
break; | |
} | |
// Shift the buffer sideways, deleting state 20 | |
for (int i = _bufferedStates.Length - 1; i >= 1; i--) | |
{ | |
_bufferedStates[i] = _bufferedStates[i - 1]; | |
} | |
//Update the first buffer | |
_bufferedStates[0] = state; | |
// Update used slot count, however never exceed the buffer size | |
// Slots aren't actually freed so this just makes sure the buffer is | |
// filled up and that uninitalized slots aren't used. | |
_bufferCount = Mathf.Min(_bufferCount + 1, _bufferedStates.Length); | |
// Check if states are in order, if it is inconsistent you could reshuffel or | |
// drop the out-of-order state. Nothing is done here | |
for (int i = 0; i < _bufferCount - 1; i++) | |
{ | |
if (_bufferedStates[i].timestamp < _bufferedStates[i + 1].timestamp) | |
Debug.LogWarning("State inconsistent"); | |
} | |
} | |
catch (System.Exception e) | |
{ | |
Debug.LogError("Exception while deserializing!"); | |
Debug.LogException(e); | |
} | |
} | |
/// <summary> | |
/// Should the rigidbody be synced? | |
/// </summary> | |
/// <returns></returns> | |
public bool ShouldSync() { return rigidbody.IsAwake(); } | |
void Update() | |
{ | |
if (isServer) | |
{ | |
//If we are the server and the rigidbody isnt sleeping, then say we are dirty. | |
SetDirtyBit(ShouldSync() ? 1UL : 0UL); | |
} | |
if (isClient) | |
{ | |
if (!isServer && hasAuthority) | |
{ | |
// check only each 'syncInterval' | |
if (Time.time - _lastClientSyncTime >= syncInterval) | |
{ | |
if (ShouldSync()) | |
{ | |
// serialize | |
NetworkWriter writer = new NetworkWriter(); | |
Serialize(writer); | |
// send to server | |
CmdClientToServerSync(writer.ToArray()); | |
} | |
_lastClientSyncTime = Time.time; | |
} | |
} | |
} | |
if (!hasAuthority) | |
{ | |
// This is the target playback time of the rigid body | |
double interpolationTime = NetworkTime.time - interpolationBackTime; | |
// Use interpolation if the target playback time is present in the buffer | |
if (_bufferedStates[0].timestamp > interpolationTime) | |
{ | |
// Go through buffer and find correct state to play back | |
for (int i = 0; i < _bufferCount; i++) | |
{ | |
if (_bufferedStates[i].timestamp <= interpolationTime || i == _bufferCount - 1) | |
{ | |
// The state one slot newer (<100ms) than the best playback state | |
State rhs = _bufferedStates[Mathf.Max(i - 1, 0)]; | |
// The best playback state (closest to 100 ms old (default time)) | |
State lhs = _bufferedStates[i]; | |
// Use the time between the two slots to determine if interpolation is necessary | |
double length = rhs.timestamp - lhs.timestamp; | |
float t = 0.0F; | |
// As the time difference gets closer to 100 ms t gets closer to 1 in | |
// which case rhs is only used | |
// Example: | |
// Time is 10.000, so sampleTime is 9.900 | |
// lhs.time is 9.910 rhs.time is 9.980 length is 0.070 | |
// t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs | |
if (length > 0.0001) | |
t = (float)((interpolationTime - lhs.timestamp) / length); | |
// if t=0 => lhs is used directly | |
rigidbody.position = Vector3.Lerp(lhs.position, rhs.position, t); | |
rigidbody.rotation = Mathf.Lerp(lhs.rotation, rhs.rotation, t); | |
rigidbody.velocity = Vector3.Lerp(lhs.velocity, rhs.velocity, t); | |
rigidbody.angularVelocity = Mathf.Lerp(lhs.angularVelocity, rhs.angularVelocity, t); | |
return; | |
} | |
} | |
} | |
// Use extrapolation | |
else | |
{ | |
State latest = _bufferedStates[0]; | |
float extrapolationLength = (float)(interpolationTime - latest.timestamp); | |
// Don't extrapolation for more than 500 ms, you would need to do that carefully | |
if (extrapolationLength < extrapolationLimit && GetComponent<Rigidbody>() != null && !GetComponent<Rigidbody>().isKinematic) | |
{ | |
rigidbody.position = latest.position + latest.velocity * extrapolationLength; | |
rigidbody.rotation = latest.rotation + latest.angularVelocity * extrapolationLength; | |
rigidbody.velocity = latest.velocity; | |
rigidbody.angularVelocity = latest.angularVelocity; | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment