mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
feat: NT-UR bit flag changed detection to lower bandwidth usage. (#3721)
* feat: NT-UR bit flag changed detection to lower bandwidth usage. Also major credits to our Ninja. * Tooltip updated * fix: NT-Unreliable Quaternion Compression Fix Credits to ninja of course :D * NT-U new improvements Credits to Ninja * Nothing to see here.. * Added comment to Quat Rotation Fix * Sensitivity check to improve value comparisons. Without this, X 0 and X -4.955753E-07 (0) would trigger as a change of value. Helps epsilon/floating point inaccuracies. * Moved around checks. rotationChanged not needed now for non-compressed bool, as we check individual rotation sensitivity changes. We can move this inside quat compress check. * Use Rot/All, not just RotX as a compress changed flag. * Set Just Rot. * Updated Reset to ResetState * Fixing PR 3571/3572/3572 in this new bitflag branch --------- Co-authored-by: ninjakickja <80569286+ninjakickja@users.noreply.github.com>
This commit is contained in:
parent
3e0a6ae64e
commit
fbd64dfb79
@ -1,4 +1,5 @@
|
||||
// NetworkTransform V2 by mischa (2021-07)
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
@ -12,6 +13,8 @@ public class NetworkTransformUnreliable : NetworkTransformBase
|
||||
// Testing under really bad network conditions, 2%-5% packet loss and 250-1200ms ping, 5 proved to eliminate any twitching, however this should not be the default as it is a rare case Developers may want to cover.
|
||||
[Tooltip("How much time, as a multiple of send interval, has passed before clearing buffers.\nA larger buffer means more delay, but results in smoother movement.\nExample: 1 for faster responses minimal smoothing, 5 covers bad pings but has noticable delay, 3 is recommended for balanced results,.")]
|
||||
public float bufferResetMultiplier = 3;
|
||||
[Tooltip("Detect and send only changed data, such as Position X and Z, not the full Vector3 of X Y Z. Lowers network data at cost of extra calculations.")]
|
||||
public bool changedDetection = true;
|
||||
|
||||
[Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")]
|
||||
public float positionSensitivity = 0.01f;
|
||||
@ -25,6 +28,7 @@ public class NetworkTransformUnreliable : NetworkTransformBase
|
||||
// Used to store last sent snapshots
|
||||
protected TransformSnapshot lastSnapshot;
|
||||
protected bool cachedSnapshotComparison;
|
||||
protected Changed cachedChangedComparison;
|
||||
protected bool hasSentUnchangedPosition;
|
||||
|
||||
// update //////////////////////////////////////////////////////////////
|
||||
@ -61,7 +65,9 @@ protected virtual void CheckLastSendTime()
|
||||
// This fixes previous issue of, if sendIntervalMultiplier = 1, we send every frame,
|
||||
// because intervalCounter is always = 1 in the previous version.
|
||||
|
||||
if (sendIntervalCounter == sendIntervalMultiplier)
|
||||
// Changing == to >= https://github.com/MirrorNetworking/Mirror/issues/3571
|
||||
|
||||
if (sendIntervalCounter >= sendIntervalMultiplier)
|
||||
sendIntervalCounter = 0;
|
||||
|
||||
// timeAsDouble not available in older Unity versions.
|
||||
@ -109,36 +115,68 @@ void UpdateServerBroadcast()
|
||||
// send snapshot without timestamp.
|
||||
// receiver gets it from batch timestamp to save bandwidth.
|
||||
TransformSnapshot snapshot = Construct();
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (compressRotation)
|
||||
if (changedDetection)
|
||||
{
|
||||
RpcServerToClientSyncCompressRotation(
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
RpcServerToClientSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (compressRotation)
|
||||
{
|
||||
RpcServerToClientSyncCompressRotation(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
RpcServerToClientSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
RpcServerToClientSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
lastSnapshot = snapshot;
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
|
||||
// Fixes https://github.com/MirrorNetworking/Mirror/issues/3572
|
||||
// This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573
|
||||
// with the exception of Quaternion.Angle sensitivity has to be > 0.16.
|
||||
// Unity issue, we are leaving it as is.
|
||||
|
||||
if (positionChanged) lastSnapshot.position = snapshot.position;
|
||||
if (rotationChanged) lastSnapshot.rotation = snapshot.rotation;
|
||||
if (positionChanged) lastSnapshot.scale = snapshot.scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,36 +243,67 @@ void UpdateClientBroadcast()
|
||||
// send snapshot without timestamp.
|
||||
// receiver gets it from batch timestamp to save bandwidth.
|
||||
TransformSnapshot snapshot = Construct();
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (compressRotation)
|
||||
if (changedDetection)
|
||||
{
|
||||
CmdClientToServerSyncCompressRotation(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
CmdClientToServerSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CmdClientToServerSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
lastSnapshot = snapshot;
|
||||
if (compressRotation)
|
||||
{
|
||||
CmdClientToServerSyncCompressRotation(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CmdClientToServerSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
|
||||
// Fixes https://github.com/MirrorNetworking/Mirror/issues/3572
|
||||
// This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573
|
||||
// with the exception of Quaternion.Angle sensitivity has to be > 0.16.
|
||||
// Unity issue, we are leaving it as is.
|
||||
if (positionChanged) lastSnapshot.position = snapshot.position;
|
||||
if (rotationChanged) lastSnapshot.rotation = snapshot.rotation;
|
||||
if (positionChanged) lastSnapshot.scale = snapshot.scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -406,5 +475,204 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
|
||||
|
||||
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
|
||||
}
|
||||
|
||||
protected virtual void UpdateLastSentSnapshot(Changed change, TransformSnapshot currentSnapshot)
|
||||
{
|
||||
if (change == Changed.None || change == Changed.CompressRot) return;
|
||||
|
||||
if ((change & Changed.PosX) > 0) lastSnapshot.position.x = currentSnapshot.position.x;
|
||||
if ((change & Changed.PosY) > 0) lastSnapshot.position.y = currentSnapshot.position.y;
|
||||
if ((change & Changed.PosZ) > 0) lastSnapshot.position.z = currentSnapshot.position.z;
|
||||
|
||||
if (compressRotation)
|
||||
{
|
||||
if ((change & Changed.Rot) > 0) lastSnapshot.rotation = currentSnapshot.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 newRotation;
|
||||
newRotation.x = (change & Changed.RotX) > 0 ? currentSnapshot.rotation.eulerAngles.x : lastSnapshot.rotation.eulerAngles.x;
|
||||
newRotation.y = (change & Changed.RotY) > 0 ? currentSnapshot.rotation.eulerAngles.y : lastSnapshot.rotation.eulerAngles.y;
|
||||
newRotation.z = (change & Changed.RotZ) > 0 ? currentSnapshot.rotation.eulerAngles.z : lastSnapshot.rotation.eulerAngles.z;
|
||||
|
||||
lastSnapshot.rotation = Quaternion.Euler(newRotation);
|
||||
}
|
||||
|
||||
if ((change & Changed.Scale) > 0) lastSnapshot.scale = currentSnapshot.scale;
|
||||
}
|
||||
|
||||
// Returns true if position, rotation AND scale are unchanged, within given sensitivity range.
|
||||
// Note the sensitivity comparison are different for pos, rot and scale.
|
||||
protected virtual Changed CompareChangedSnapshots(TransformSnapshot currentSnapshot)
|
||||
{
|
||||
Changed change = Changed.None;
|
||||
|
||||
if (syncPosition)
|
||||
{
|
||||
bool positionChanged = Vector3.SqrMagnitude(lastSnapshot.position - currentSnapshot.position) > positionSensitivity * positionSensitivity;
|
||||
if (positionChanged)
|
||||
{
|
||||
if (Mathf.Abs(lastSnapshot.position.x - currentSnapshot.position.x) > positionSensitivity) change |= Changed.PosX;
|
||||
if (Mathf.Abs(lastSnapshot.position.y - currentSnapshot.position.y) > positionSensitivity) change |= Changed.PosY;
|
||||
if (Mathf.Abs(lastSnapshot.position.z - currentSnapshot.position.z) > positionSensitivity) change |= Changed.PosZ;
|
||||
}
|
||||
}
|
||||
|
||||
if (syncRotation)
|
||||
{
|
||||
if (compressRotation)
|
||||
{
|
||||
bool rotationChanged = Quaternion.Angle(lastSnapshot.rotation, currentSnapshot.rotation) > rotationSensitivity;
|
||||
if (rotationChanged)
|
||||
{
|
||||
// Here we set all Rot enum flags, to tell us if there was a change in rotation
|
||||
// when using compression. If no change, we don't write the compressed Quat.
|
||||
change |= Changed.CompressRot;
|
||||
change |= Changed.Rot;
|
||||
}
|
||||
else
|
||||
{
|
||||
change |= Changed.CompressRot;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Mathf.Abs(lastSnapshot.rotation.eulerAngles.x - currentSnapshot.rotation.eulerAngles.x) > rotationSensitivity) change |= Changed.RotX;
|
||||
if (Mathf.Abs(lastSnapshot.rotation.eulerAngles.y - currentSnapshot.rotation.eulerAngles.y) > rotationSensitivity) change |= Changed.RotY;
|
||||
if (Mathf.Abs(lastSnapshot.rotation.eulerAngles.z - currentSnapshot.rotation.eulerAngles.z) > rotationSensitivity) change |= Changed.RotZ;
|
||||
}
|
||||
}
|
||||
|
||||
if (syncScale)
|
||||
{
|
||||
if (Vector3.SqrMagnitude(lastSnapshot.scale - currentSnapshot.scale) > scaleSensitivity * scaleSensitivity) change |= Changed.Scale;
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
[Command(channel = Channels.Unreliable)]
|
||||
void CmdClientToServerSync(SyncData syncData)
|
||||
{
|
||||
OnClientToServerSync(syncData);
|
||||
//For client authority, immediately pass on the client snapshot to all other
|
||||
//clients instead of waiting for server to send its snapshots.
|
||||
if (syncDirection == SyncDirection.ClientToServer)
|
||||
RpcServerToClientSync(syncData);
|
||||
}
|
||||
|
||||
protected virtual void OnClientToServerSync(SyncData syncData)
|
||||
{
|
||||
// only apply if in client authority mode
|
||||
if (syncDirection != SyncDirection.ClientToServer) return;
|
||||
|
||||
// protect against ever growing buffer size attacks
|
||||
if (serverSnapshots.Count >= connectionToClient.snapshotBufferSizeLimit) return;
|
||||
|
||||
// only player owned objects (with a connection) can send to
|
||||
// server. we can get the timestamp from the connection.
|
||||
double timestamp = connectionToClient.remoteTimeStamp;
|
||||
|
||||
if (onlySyncOnChange)
|
||||
{
|
||||
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkClient.sendInterval;
|
||||
|
||||
if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
|
||||
ResetState();
|
||||
}
|
||||
|
||||
UpdateSyncData(ref syncData, serverSnapshots);
|
||||
|
||||
AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + timeStampAdjustment + offset, syncData.position, syncData.quatRotation, syncData.scale);
|
||||
}
|
||||
|
||||
|
||||
[ClientRpc(channel = Channels.Unreliable)]
|
||||
void RpcServerToClientSync(SyncData syncData) =>
|
||||
OnServerToClientSync(syncData);
|
||||
|
||||
protected virtual void OnServerToClientSync(SyncData syncData)
|
||||
{
|
||||
// in host mode, the server sends rpcs to all clients.
|
||||
// the host client itself will receive them too.
|
||||
// -> host server is always the source of truth
|
||||
// -> we can ignore any rpc on the host client
|
||||
// => otherwise host objects would have ever growing clientBuffers
|
||||
// (rpc goes to clients. if isServer is true too then we are host)
|
||||
if (isServer) return;
|
||||
|
||||
// don't apply for local player with authority
|
||||
if (IsClientWithAuthority) return;
|
||||
|
||||
// on the client, we receive rpcs for all entities.
|
||||
// not all of them have a connectionToServer.
|
||||
// but all of them go through NetworkClient.connection.
|
||||
// we can get the timestamp from there.
|
||||
double timestamp = NetworkClient.connection.remoteTimeStamp;
|
||||
|
||||
if (onlySyncOnChange)
|
||||
{
|
||||
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkServer.sendInterval;
|
||||
|
||||
if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
|
||||
ResetState();
|
||||
}
|
||||
|
||||
UpdateSyncData(ref syncData, clientSnapshots);
|
||||
|
||||
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, syncData.position, syncData.quatRotation, syncData.scale);
|
||||
}
|
||||
|
||||
protected virtual void UpdateSyncData(ref SyncData syncData, SortedList<double, TransformSnapshot> snapshots)
|
||||
{
|
||||
if (syncData.changedDataByte == Changed.None || syncData.changedDataByte == Changed.CompressRot)
|
||||
{
|
||||
syncData.position = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].position : GetPosition();
|
||||
syncData.quatRotation = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation : GetRotation();
|
||||
syncData.scale = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].scale : GetScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just going to update these without checking if syncposition or not,
|
||||
// because if not syncing position, NT will not apply any position data
|
||||
// to the target during Apply().
|
||||
|
||||
syncData.position.x = (syncData.changedDataByte & Changed.PosX) > 0 ? syncData.position.x : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].position.x : GetPosition().x);
|
||||
syncData.position.y = (syncData.changedDataByte & Changed.PosY) > 0 ? syncData.position.y : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].position.y : GetPosition().y);
|
||||
syncData.position.z = (syncData.changedDataByte & Changed.PosZ) > 0 ? syncData.position.z : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].position.z : GetPosition().z);
|
||||
|
||||
// If compressRot is true, we already have the Quat in syncdata.
|
||||
if ((syncData.changedDataByte & Changed.CompressRot) == 0)
|
||||
{
|
||||
syncData.vecRotation.x = (syncData.changedDataByte & Changed.RotX) > 0 ? syncData.vecRotation.x : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation.eulerAngles.x : GetRotation().eulerAngles.x);
|
||||
syncData.vecRotation.y = (syncData.changedDataByte & Changed.RotY) > 0 ? syncData.vecRotation.y : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation.eulerAngles.y : GetRotation().eulerAngles.y); ;
|
||||
syncData.vecRotation.z = (syncData.changedDataByte & Changed.RotZ) > 0 ? syncData.vecRotation.z : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation.eulerAngles.z : GetRotation().eulerAngles.z);
|
||||
|
||||
syncData.quatRotation = Quaternion.Euler(syncData.vecRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncData.quatRotation = (syncData.changedDataByte & Changed.Rot) > 0 ? syncData.quatRotation : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation : GetRotation());
|
||||
}
|
||||
|
||||
syncData.scale = (syncData.changedDataByte & Changed.Scale) > 0 ? syncData.scale : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].scale : GetScale());
|
||||
}
|
||||
}
|
||||
|
||||
// This is to extract position/rotation/scale data from payload. Override
|
||||
// Construct and Deconstruct if you are implementing a different SyncData logic.
|
||||
// Note however that snapshot interpolation still requires the basic 3 data
|
||||
// position, rotation and scale, which are computed from here.
|
||||
protected virtual void DeconstructSyncData(System.ArraySegment<byte> receivedPayload, out byte? changedFlagData, out Vector3? position, out Quaternion? rotation, out Vector3? scale)
|
||||
{
|
||||
using (NetworkReaderPooled reader = NetworkReaderPool.Get(receivedPayload))
|
||||
{
|
||||
SyncData syncData = reader.Read<SyncData>();
|
||||
changedFlagData = (byte)syncData.changedDataByte;
|
||||
position = syncData.position;
|
||||
rotation = syncData.quatRotation;
|
||||
scale = syncData.scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
156
Assets/Mirror/Components/NetworkTransform/TransformSyncData.cs
Normal file
156
Assets/Mirror/Components/NetworkTransform/TransformSyncData.cs
Normal file
@ -0,0 +1,156 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using Mirror;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[Serializable]
|
||||
public struct SyncData
|
||||
{
|
||||
public Changed changedDataByte;
|
||||
public Vector3 position;
|
||||
public Quaternion quatRotation;
|
||||
public Vector3 vecRotation;
|
||||
public Vector3 scale;
|
||||
|
||||
public SyncData(Changed _dataChangedByte, Vector3 _position, Quaternion _rotation, Vector3 _scale)
|
||||
{
|
||||
this.changedDataByte = _dataChangedByte;
|
||||
this.position = _position;
|
||||
this.quatRotation = _rotation;
|
||||
this.vecRotation = quatRotation.eulerAngles;
|
||||
this.scale = _scale;
|
||||
}
|
||||
|
||||
public SyncData(Changed _dataChangedByte, TransformSnapshot _snapshot)
|
||||
{
|
||||
this.changedDataByte = _dataChangedByte;
|
||||
this.position = _snapshot.position;
|
||||
this.quatRotation = _snapshot.rotation;
|
||||
this.vecRotation = quatRotation.eulerAngles;
|
||||
this.scale = _snapshot.scale;
|
||||
}
|
||||
|
||||
public SyncData(Changed _dataChangedByte, Vector3 _position, Vector3 _vecRotation, Vector3 _scale)
|
||||
{
|
||||
this.changedDataByte = _dataChangedByte;
|
||||
this.position = _position;
|
||||
this.vecRotation = _vecRotation;
|
||||
this.quatRotation = Quaternion.Euler(vecRotation);
|
||||
this.scale = _scale;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Changed : byte
|
||||
{
|
||||
None = 0,
|
||||
PosX = 1 << 0,
|
||||
PosY = 1 << 1,
|
||||
PosZ = 1 << 2,
|
||||
CompressRot = 1 << 3,
|
||||
RotX = 1 << 4,
|
||||
RotY = 1 << 5,
|
||||
RotZ = 1 << 6,
|
||||
Scale = 1 << 7,
|
||||
|
||||
Pos = PosX | PosY | PosZ,
|
||||
Rot = RotX | RotY | RotZ
|
||||
}
|
||||
|
||||
|
||||
public static class SyncDataReaderWriter
|
||||
{
|
||||
public static void WriteSyncData(this NetworkWriter writer, SyncData syncData)
|
||||
{
|
||||
writer.WriteByte((byte)syncData.changedDataByte);
|
||||
|
||||
// Write position
|
||||
if ((syncData.changedDataByte & Changed.PosX) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.position.x);
|
||||
}
|
||||
|
||||
if ((syncData.changedDataByte & Changed.PosY) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.position.y);
|
||||
}
|
||||
|
||||
if ((syncData.changedDataByte & Changed.PosZ) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.position.z);
|
||||
}
|
||||
|
||||
// Write rotation
|
||||
if ((syncData.changedDataByte & Changed.CompressRot) > 0)
|
||||
{
|
||||
if((syncData.changedDataByte & Changed.Rot) > 0)
|
||||
{
|
||||
writer.WriteUInt(Compression.CompressQuaternion(syncData.quatRotation));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((syncData.changedDataByte & Changed.RotX) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.quatRotation.eulerAngles.x);
|
||||
}
|
||||
|
||||
if ((syncData.changedDataByte & Changed.RotY) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.quatRotation.eulerAngles.y);
|
||||
}
|
||||
|
||||
if ((syncData.changedDataByte & Changed.RotZ) > 0)
|
||||
{
|
||||
writer.WriteFloat(syncData.quatRotation.eulerAngles.z);
|
||||
}
|
||||
}
|
||||
|
||||
// Write scale
|
||||
if ((syncData.changedDataByte & Changed.Scale) > 0)
|
||||
{
|
||||
writer.WriteVector3(syncData.scale);
|
||||
}
|
||||
}
|
||||
|
||||
public static SyncData ReadSyncData(this NetworkReader reader)
|
||||
{
|
||||
Changed changedData = (Changed)reader.ReadByte();
|
||||
|
||||
// If we have nothing to read here, let's say because posX is unchanged, then we can write anything
|
||||
// for now, but in the NT, we will need to check changedData again, to put the right values of the axis
|
||||
// back. We don't have it here.
|
||||
|
||||
Vector3 position =
|
||||
new Vector3(
|
||||
(changedData & Changed.PosX) > 0 ? reader.ReadFloat() : 0,
|
||||
(changedData & Changed.PosY) > 0 ? reader.ReadFloat() : 0,
|
||||
(changedData & Changed.PosZ) > 0 ? reader.ReadFloat() : 0
|
||||
);
|
||||
|
||||
Vector3 vecRotation = new Vector3();
|
||||
Quaternion quatRotation = new Quaternion();
|
||||
|
||||
if ((changedData & Changed.CompressRot) > 0)
|
||||
{
|
||||
quatRotation = (changedData & Changed.RotX) > 0 ? Compression.DecompressQuaternion(reader.ReadUInt()) : new Quaternion();
|
||||
}
|
||||
else
|
||||
{
|
||||
vecRotation =
|
||||
new Vector3(
|
||||
(changedData & Changed.RotX) > 0 ? reader.ReadFloat() : 0,
|
||||
(changedData & Changed.RotY) > 0 ? reader.ReadFloat() : 0,
|
||||
(changedData & Changed.RotZ) > 0 ? reader.ReadFloat() : 0
|
||||
);
|
||||
}
|
||||
|
||||
Vector3 scale = (changedData & Changed.Scale) == Changed.Scale ? reader.ReadVector3() : new Vector3();
|
||||
|
||||
SyncData _syncData = (changedData & Changed.CompressRot) > 0 ? new SyncData(changedData, position, quatRotation, scale) : new SyncData(changedData, position, vecRotation, scale);
|
||||
|
||||
return _syncData;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1c0832ca88e749ff96fe04cebb617ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user