mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
no inheritance from ntbase
This commit is contained in:
parent
e29ab56a20
commit
c4f49ec5d4
@ -7,8 +7,215 @@
|
|||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
[AddComponentMenu("Network/Network Transform (Unreliable Compressed)")]
|
[AddComponentMenu("Network/Network Transform (Unreliable Compressed)")]
|
||||||
public class NetworkTransformUnreliableCompressed : NetworkTransformBase
|
public class NetworkTransformUnreliableCompressed : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
// NT BASE /////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// target transform to sync. can be on a child.
|
||||||
|
// TODO this field is kind of unnecessary since we now support child NetworkBehaviours
|
||||||
|
[Header("Target")]
|
||||||
|
[Tooltip("The Transform component to sync. May be on on this GameObject, or on a child.")]
|
||||||
|
public Transform target;
|
||||||
|
|
||||||
|
// Is this a client with authority over this transform?
|
||||||
|
// This component could be on the player object or any object that has been assigned authority to this client.
|
||||||
|
protected bool IsClientWithAuthority => isClient && authority;
|
||||||
|
|
||||||
|
// snapshots with initial capacity to avoid early resizing & allocations: see NetworkRigidbodyBenchmark example.
|
||||||
|
public readonly SortedList<double, TransformSnapshot> clientSnapshots = new SortedList<double, TransformSnapshot>(16);
|
||||||
|
public readonly SortedList<double, TransformSnapshot> serverSnapshots = new SortedList<double, TransformSnapshot>(16);
|
||||||
|
|
||||||
|
// CoordinateSpace ///////////////////////////////////////////////////////////
|
||||||
|
[Header("Coordinate Space")]
|
||||||
|
[Tooltip("Local by default. World may be better when changing hierarchy, or non-NetworkTransforms root position/rotation/scale values.")]
|
||||||
|
public CoordinateSpace coordinateSpace = CoordinateSpace.Local;
|
||||||
|
|
||||||
|
// convert syncInterval to sendIntervalMultiplier.
|
||||||
|
// in the future this can be moved into core to support tick aligned Sync,
|
||||||
|
public uint sendIntervalMultiplier
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (syncInterval > 0)
|
||||||
|
{
|
||||||
|
// if syncInterval is > 0, calculate how many multiples of NetworkManager.sendRate it is
|
||||||
|
//
|
||||||
|
// for example:
|
||||||
|
// NetworkServer.sendInterval is 1/60 = 0.16
|
||||||
|
// NetworkTransform.syncInterval is 0.5 (500ms).
|
||||||
|
// 0.5 / 0.16 = 3.125
|
||||||
|
// in other words: 3.125 x sendInterval
|
||||||
|
//
|
||||||
|
// note that NetworkServer.sendInterval is usually set on start.
|
||||||
|
// to make this work in Edit mode, make sure that NetworkManager
|
||||||
|
// OnValidate sets NetworkServer.sendInterval immediately.
|
||||||
|
float multiples = syncInterval / NetworkServer.sendInterval;
|
||||||
|
|
||||||
|
// syncInterval is always supposed to sync at a minimum of 1 x sendInterval.
|
||||||
|
// that's what we do for every other NetworkBehaviour since
|
||||||
|
// we only sync in Broadcast() which is called @ sendInterval.
|
||||||
|
return multiples > 1 ? (uint)Mathf.RoundToInt(multiples) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if syncInterval is 0, use NetworkManager.sendRate (x1)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Header("Timeline Offset")]
|
||||||
|
[Tooltip("Add a small timeline offset to account for decoupled arrival of NetworkTime and NetworkTransform snapshots.\nfixes: https://github.com/MirrorNetworking/Mirror/issues/3427")]
|
||||||
|
public bool timelineOffset = false;
|
||||||
|
|
||||||
|
// Ninja's Notes on offset & mulitplier:
|
||||||
|
//
|
||||||
|
// In a no multiplier scenario:
|
||||||
|
// 1. Snapshots are sent every frame (frame being 1 NM send interval).
|
||||||
|
// 2. Time Interpolation is set to be 'behind' by 2 frames times.
|
||||||
|
// In theory where everything works, we probably have around 2 snapshots before we need to interpolate snapshots. From NT perspective, we should always have around 2 snapshots ready, so no stutter.
|
||||||
|
//
|
||||||
|
// In a multiplier scenario:
|
||||||
|
// 1. Snapshots are sent every 10 frames.
|
||||||
|
// 2. Time Interpolation remains 'behind by 2 frames'.
|
||||||
|
// When everything works, we are receiving NT snapshots every 10 frames, but start interpolating after 2.
|
||||||
|
// Even if I assume we had 2 snapshots to begin with to start interpolating (which we don't), by the time we reach 13th frame, we are out of snapshots, and have to wait 7 frames for next snapshot to come. This is the reason why we absolutely need the timestamp adjustment. We are starting way too early to interpolate.
|
||||||
|
//
|
||||||
|
protected double timeStampAdjustment => NetworkServer.sendInterval * (sendIntervalMultiplier - 1);
|
||||||
|
protected double offset => timelineOffset ? NetworkServer.sendInterval * sendIntervalMultiplier : 0;
|
||||||
|
|
||||||
|
// debugging ///////////////////////////////////////////////////////////
|
||||||
|
protected override void OnValidate()
|
||||||
|
{
|
||||||
|
// Skip if Editor is in Play mode
|
||||||
|
if (Application.isPlaying) return;
|
||||||
|
|
||||||
|
base.OnValidate();
|
||||||
|
|
||||||
|
// configure in awake
|
||||||
|
Configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure to call this when inheriting too!
|
||||||
|
protected virtual void Awake()
|
||||||
|
{
|
||||||
|
// sometimes OnValidate() doesn't run before launching a project.
|
||||||
|
// need to guarantee configuration runs.
|
||||||
|
Configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot functions //////////////////////////////////////////////////
|
||||||
|
// get local/world position
|
||||||
|
protected virtual Vector3 GetPosition() =>
|
||||||
|
coordinateSpace == CoordinateSpace.Local ? target.localPosition : target.position;
|
||||||
|
|
||||||
|
// get local/world rotation
|
||||||
|
protected virtual Quaternion GetRotation() =>
|
||||||
|
coordinateSpace == CoordinateSpace.Local ? target.localRotation : target.rotation;
|
||||||
|
|
||||||
|
// get local/world scale
|
||||||
|
protected virtual Vector3 GetScale() =>
|
||||||
|
coordinateSpace == CoordinateSpace.Local ? target.localScale : target.lossyScale;
|
||||||
|
|
||||||
|
// set local/world position
|
||||||
|
protected virtual void SetPosition(Vector3 position)
|
||||||
|
{
|
||||||
|
if (coordinateSpace == CoordinateSpace.Local)
|
||||||
|
target.localPosition = position;
|
||||||
|
else
|
||||||
|
target.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set local/world rotation
|
||||||
|
protected virtual void SetRotation(Quaternion rotation)
|
||||||
|
{
|
||||||
|
if (coordinateSpace == CoordinateSpace.Local)
|
||||||
|
target.localRotation = rotation;
|
||||||
|
else
|
||||||
|
target.rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set local/world position
|
||||||
|
protected virtual void SetScale(Vector3 scale)
|
||||||
|
{
|
||||||
|
if (coordinateSpace == CoordinateSpace.Local)
|
||||||
|
target.localScale = scale;
|
||||||
|
// Unity doesn't support setting world scale.
|
||||||
|
// OnValidate disables syncScale in world mode.
|
||||||
|
// else
|
||||||
|
// target.lossyScale = scale; // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct a snapshot of the current state
|
||||||
|
// => internal for testing
|
||||||
|
protected virtual TransformSnapshot Construct()
|
||||||
|
{
|
||||||
|
// NetworkTime.localTime for double precision until Unity has it too
|
||||||
|
return new TransformSnapshot(
|
||||||
|
// our local time is what the other end uses as remote time
|
||||||
|
NetworkTime.localTime, // Unity 2019 doesn't have timeAsDouble yet
|
||||||
|
0, // the other end fills out local time itself
|
||||||
|
GetPosition(),
|
||||||
|
GetRotation(),
|
||||||
|
GetScale()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddSnapshot(SortedList<double, TransformSnapshot> snapshots, double timeStamp, Vector3? position, Quaternion? rotation, Vector3? scale)
|
||||||
|
{
|
||||||
|
// position, rotation, scale can have no value if same as last time.
|
||||||
|
// saves bandwidth.
|
||||||
|
// but we still need to feed it to snapshot interpolation. we can't
|
||||||
|
// just have gaps in there if nothing has changed. for example, if
|
||||||
|
// client sends snapshot at t=0
|
||||||
|
// client sends nothing for 10s because not moved
|
||||||
|
// client sends snapshot at t=10
|
||||||
|
// then the server would assume that it's one super slow move and
|
||||||
|
// replay it for 10 seconds.
|
||||||
|
|
||||||
|
if (!position.HasValue) position = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].position : GetPosition();
|
||||||
|
if (!rotation.HasValue) rotation = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].rotation : GetRotation();
|
||||||
|
if (!scale.HasValue) scale = snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].scale : GetScale();
|
||||||
|
|
||||||
|
// insert transform snapshot
|
||||||
|
SnapshotInterpolation.InsertIfNotExists(
|
||||||
|
snapshots,
|
||||||
|
NetworkClient.snapshotSettings.bufferLimit,
|
||||||
|
new TransformSnapshot(
|
||||||
|
timeStamp, // arrival remote timestamp. NOT remote time.
|
||||||
|
NetworkTime.localTime, // Unity 2019 doesn't have timeAsDouble yet
|
||||||
|
position.Value,
|
||||||
|
rotation.Value,
|
||||||
|
scale.Value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply a snapshot to the Transform.
|
||||||
|
// -> start, end, interpolated are all passed in caes they are needed
|
||||||
|
// -> a regular game would apply the 'interpolated' snapshot
|
||||||
|
// -> a board game might want to jump to 'goal' directly
|
||||||
|
// (it's easier to always interpolate and then apply selectively,
|
||||||
|
// instead of manually interpolating x, y, z, ... depending on flags)
|
||||||
|
// => internal for testing
|
||||||
|
//
|
||||||
|
// NOTE: stuck detection is unnecessary here.
|
||||||
|
// we always set transform.position anyway, we can't get stuck.
|
||||||
|
protected virtual void Apply(TransformSnapshot interpolated, TransformSnapshot endGoal)
|
||||||
|
{
|
||||||
|
// local position/rotation for VR support
|
||||||
|
//
|
||||||
|
// if syncPosition/Rotation/Scale is disabled then we received nulls
|
||||||
|
// -> current position/rotation/scale would've been added as snapshot
|
||||||
|
// -> we still interpolated
|
||||||
|
// -> but simply don't apply it. if the user doesn't want to sync
|
||||||
|
// scale, then we should not touch scale etc.
|
||||||
|
|
||||||
|
// interpolate parts
|
||||||
|
SetPosition(interpolated.position);
|
||||||
|
SetRotation(interpolated.rotation);
|
||||||
|
SetScale(interpolated.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NT COMPRESSED ///////////////////////////////////////////////////////
|
||||||
[Header("Debug")]
|
[Header("Debug")]
|
||||||
public bool debugDraw = false;
|
public bool debugDraw = false;
|
||||||
|
|
||||||
@ -17,9 +224,10 @@ public class NetworkTransformUnreliableCompressed : NetworkTransformBase
|
|||||||
|
|
||||||
// validation //////////////////////////////////////////////////////////
|
// validation //////////////////////////////////////////////////////////
|
||||||
// Configure is called from OnValidate and Awake
|
// Configure is called from OnValidate and Awake
|
||||||
protected override void Configure()
|
protected void Configure()
|
||||||
{
|
{
|
||||||
base.Configure();
|
// set target to self if none yet
|
||||||
|
if (target == null) target = transform;
|
||||||
|
|
||||||
// force syncMethod to unreliable
|
// force syncMethod to unreliable
|
||||||
syncMethod = SyncMethod.Unreliable;
|
syncMethod = SyncMethod.Unreliable;
|
||||||
@ -47,8 +255,7 @@ void LateUpdate()
|
|||||||
// instead.
|
// instead.
|
||||||
if (isServer || (IsClientWithAuthority && NetworkClient.ready))
|
if (isServer || (IsClientWithAuthority && NetworkClient.ready))
|
||||||
{
|
{
|
||||||
if (!onlySyncOnChange)
|
SetDirty();
|
||||||
SetDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,18 +358,9 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
|
|||||||
|
|
||||||
int startPosition = writer.Position;
|
int startPosition = writer.Position;
|
||||||
|
|
||||||
if (syncPosition) writer.WriteVector3(snapshot.position);
|
writer.WriteVector3(snapshot.position);
|
||||||
if (syncRotation)
|
writer.WriteQuaternion(snapshot.rotation);
|
||||||
{
|
writer.WriteVector3(snapshot.scale);
|
||||||
// if smallest-three quaternion compression is enabled,
|
|
||||||
// then we don't need baseline rotation since delta always
|
|
||||||
// sends an absolute value.
|
|
||||||
if (!compressRotation)
|
|
||||||
{
|
|
||||||
writer.WriteQuaternion(snapshot.rotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (syncScale) writer.WriteVector3(snapshot.scale);
|
|
||||||
|
|
||||||
// set 'last'
|
// set 'last'
|
||||||
last = snapshot;
|
last = snapshot;
|
||||||
@ -172,17 +370,9 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
|
|||||||
{
|
{
|
||||||
int startPosition = writer.Position;
|
int startPosition = writer.Position;
|
||||||
|
|
||||||
if (syncPosition) writer.WriteVector3(snapshot.position);
|
writer.WriteVector3(snapshot.position);
|
||||||
if (syncRotation)
|
writer.WriteQuaternion(snapshot.rotation);
|
||||||
{
|
writer.WriteVector3(snapshot.scale);
|
||||||
// (optional) smallest three compression for now. no delta.
|
|
||||||
if (compressRotation)
|
|
||||||
{
|
|
||||||
writer.WriteUInt(Compression.CompressQuaternion(snapshot.rotation));
|
|
||||||
}
|
|
||||||
else writer.WriteQuaternion(snapshot.rotation);
|
|
||||||
}
|
|
||||||
if (syncScale) writer.WriteVector3(snapshot.scale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,50 +388,20 @@ public override void OnDeserialize(NetworkReader reader, bool initialState)
|
|||||||
// reliable full state
|
// reliable full state
|
||||||
if (initialState)
|
if (initialState)
|
||||||
{
|
{
|
||||||
if (syncPosition)
|
position = reader.ReadVector3();
|
||||||
{
|
rotation = reader.ReadQuaternion();
|
||||||
position = reader.ReadVector3();
|
scale = reader.ReadVector3();
|
||||||
|
|
||||||
if (debugDraw) Debug.DrawLine(position.Value, position.Value + Vector3.up , Color.green, 10.0f);
|
if (debugDraw) Debug.DrawLine(position.Value, position.Value + Vector3.up , Color.green, 10.0f);
|
||||||
}
|
|
||||||
if (syncRotation)
|
|
||||||
{
|
|
||||||
// if smallest-three quaternion compression is enabled,
|
|
||||||
// then we don't need baseline rotation since delta always
|
|
||||||
// sends an absolute value.
|
|
||||||
if (!compressRotation)
|
|
||||||
{
|
|
||||||
rotation = reader.ReadQuaternion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (syncScale) scale = reader.ReadVector3();
|
|
||||||
}
|
}
|
||||||
// unreliable delta: decompress against last full reliable state
|
// unreliable delta: decompress against last full reliable state
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// varint -> delta -> quantize
|
position = reader.ReadVector3();
|
||||||
if (syncPosition)
|
rotation = reader.ReadQuaternion();
|
||||||
{
|
scale = reader.ReadVector3();
|
||||||
position = reader.ReadVector3();
|
|
||||||
|
|
||||||
if (debugDraw) Debug.DrawLine(position.Value, position.Value + Vector3.up , Color.yellow, 10.0f);
|
if (debugDraw) Debug.DrawLine(position.Value, position.Value + Vector3.up , Color.yellow, 10.0f);
|
||||||
}
|
|
||||||
if (syncRotation)
|
|
||||||
{
|
|
||||||
// (optional) smallest three compression for now. no delta.
|
|
||||||
if (compressRotation)
|
|
||||||
{
|
|
||||||
rotation = Compression.DecompressQuaternion(reader.ReadUInt());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rotation = reader.ReadQuaternion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (syncScale)
|
|
||||||
{
|
|
||||||
scale = reader.ReadVector3();
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle depending on server / client / host.
|
// handle depending on server / client / host.
|
||||||
// server has priority for host mode.
|
// server has priority for host mode.
|
||||||
@ -292,10 +452,8 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
|
|||||||
// reset state for next session.
|
// reset state for next session.
|
||||||
// do not ever call this during a session (i.e. after teleport).
|
// do not ever call this during a session (i.e. after teleport).
|
||||||
// calling this will break delta compression.
|
// calling this will break delta compression.
|
||||||
public override void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
base.ResetState();
|
|
||||||
|
|
||||||
// reset 'last' for delta too
|
// reset 'last' for delta too
|
||||||
last = new TransformSnapshot(0, 0, Vector3.zero, Quaternion.identity, Vector3.zero);
|
last = new TransformSnapshot(0, 0, Vector3.zero, Quaternion.identity, Vector3.zero);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user