Skip to content

Commit 18cf2f0

Browse files
committed
Merge branch 'sam/feature/client-network-transform' into sam/feature/transform-teleport
* sam/feature/client-network-transform: fixing issue with tests where we had tickrate set to 0. Removed singleton reference for this, since multiinstance tests don't like them :( delegate to allow server to override client changes work on setDelta feat: NetworkAnimator and ClientNetworkAnimator (#1191) # Conflicts: # com.unity.netcode.gameobjects/Components/NetworkTransform.cs
2 parents 2e8848b + 17d17ee commit 18cf2f0

File tree

14 files changed

+429
-257
lines changed

14 files changed

+429
-257
lines changed

com.unity.netcode.gameobjects/Components/NetworkAnimator.cs

Lines changed: 284 additions & 116 deletions
Large diffs are not rendered by default.

com.unity.netcode.gameobjects/Components/NetworkTransform.cs

Lines changed: 64 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ namespace Unity.Netcode.Components
1414
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
1515
public class NetworkTransform : NetworkBehaviour
1616
{
17+
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
18+
public OnClientRequestChangeDelegate OnClientRequestChange;
19+
1720
internal struct NetworkTransformState : INetworkSerializable
1821
{
1922
private const int k_InLocalSpaceBit = 0;
@@ -503,6 +506,8 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
503506
m_PrevNetworkState = networkState;
504507

505508
var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position;
509+
510+
// todo: we should store network state w/ quats vs. euler angles
506511
var interpolatedRotAngles = InLocalSpace ? transformToUpdate.localEulerAngles : transformToUpdate.eulerAngles;
507512
var interpolatedScale = InLocalSpace ? transformToUpdate.localScale : transformToUpdate.lossyScale;
508513

@@ -524,19 +529,24 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
524529
interpolatedPosition.z = networkState.IsTeleporting || !Interpolate ? networkState.Position.z : m_PositionZInterpolator.GetInterpolatedValue();
525530
}
526531

527-
if (SyncRotAngleX)
532+
// again, we should be using quats here
533+
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
528534
{
529-
interpolatedRotAngles.x = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.x : m_RotationInterpolator.GetInterpolatedValue().eulerAngles.x;
530-
}
535+
var eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles;
536+
if (SyncRotAngleX)
537+
{
538+
interpolatedRotAngles.x = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.x : eulerAngles.x;
539+
}
531540

532-
if (SyncRotAngleY)
533-
{
534-
interpolatedRotAngles.y = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.y : m_RotationInterpolator.GetInterpolatedValue().eulerAngles.y;
535-
}
541+
if (SyncRotAngleY)
542+
{
543+
interpolatedRotAngles.y = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.y : eulerAngles.y;
544+
}
536545

537-
if (SyncRotAngleZ)
538-
{
539-
interpolatedRotAngles.z = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.z : m_RotationInterpolator.GetInterpolatedValue().eulerAngles.z;
546+
if (SyncRotAngleZ)
547+
{
548+
interpolatedRotAngles.z = networkState.IsTeleporting || !Interpolate ? networkState.Rotation.z : eulerAngles.z;
549+
}
540550
}
541551

542552
// Scale Read
@@ -602,12 +612,11 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
602612

603613
m_PrevNetworkState.Scale = interpolatedScale;
604614
}
605-
Debug.DrawLine(transformToUpdate.position, transformToUpdate.position + Vector3.up, Color.magenta, 10, false);
606615
}
607616

608617
private void AddInterpolatedState(NetworkTransformState newState)
609618
{
610-
var sentTime = new NetworkTime(NetworkManager.Singleton.ServerTime.TickRate, newState.SentTime);
619+
var sentTime = new NetworkTime(NetworkManager.ServerTime.TickRate, newState.SentTime);
611620

612621
if (newState.HasPositionX)
613622
{
@@ -660,7 +669,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
660669

661670
AddInterpolatedState(newState);
662671

663-
if (NetworkManager.Singleton.LogLevel == LogLevel.Developer)
672+
if (NetworkManager.LogLevel == LogLevel.Developer)
664673
{
665674
var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ);
666675
Debug.DrawLine(pos, pos + Vector3.up + Vector3.left * Random.Range(0.5f, 2f), Color.green, k_DebugDrawLineTime, false);
@@ -734,110 +743,20 @@ private void OnDestroy()
734743
m_ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged;
735744
}
736745

737-
#region delta input
738-
739-
private Vector3 m_CurrentPositionDirection;
740-
private Vector3 m_PreviousPositionDirection;
741-
742-
private Vector3 m_CurrentRotationDirection;
743-
private Vector3 m_PreviousRotationDirection;
744-
745-
private Vector3 m_CurrentScaleDirection;
746-
private Vector3 m_PreviousScaleDirection;
747-
748-
/// <summary>
749-
/// Simple way to affect your transform server side from clients, while still keeping a server authoritative transform. For more custom logic,
750-
/// you can implement an RPC that does the same as this method, with custom movement logic.
751-
/// It's not recommened to use this on non-kinematic or FixedUpdate based objects. Physics movements will need custom code to be synced with physics items.
752-
/// To stop movements, set delta back to 0
753-
/// </summary>
754-
/// <param name="deltaPos"></param>
755-
/// <param name="deltaRot"></param>
756-
/// <param name="deltaScale"></param>
757-
/// <exception cref="Exception"></exception>
758-
public void CommitDeltaValues(Vector3 deltaPos, Vector3 deltaRot, Vector3 deltaScale)
759-
{
760-
if (!IsOwner)
761-
{
762-
throw new Exception("Trying to send a delta to a not owned transform");
763-
}
764-
765-
if (m_PreviousPositionDirection == deltaPos && m_PreviousRotationDirection == deltaRot && m_PreviousScaleDirection == deltaScale)
766-
{
767-
return;
768-
}
769-
770-
if (NetworkManager != null && !(NetworkManager.IsConnectedClient || NetworkManager.IsListening))
771-
{
772-
return;
773-
}
774-
775-
if (!CanCommitToTransform)
776-
{
777-
if (!IsServer)
778-
{
779-
SendDeltaServerRpc(deltaPos, deltaRot, deltaScale);
780-
}
781-
}
782-
else
783-
{
784-
m_CurrentPositionDirection = deltaPos;
785-
m_CurrentRotationDirection = deltaRot;
786-
m_CurrentScaleDirection = deltaScale;
787-
}
788-
789-
m_PreviousPositionDirection = deltaPos;
790-
m_PreviousRotationDirection = deltaRot;
791-
m_PreviousScaleDirection = deltaScale;
792-
}
793-
794-
[ServerRpc]
795-
private void SendDeltaServerRpc(Vector3 deltaPos, Vector3 deltaRot, Vector3 deltaScale)
796-
{
797-
m_CurrentPositionDirection = deltaPos;
798-
m_CurrentRotationDirection = deltaRot;
799-
m_CurrentScaleDirection = deltaScale;
800-
}
801-
802-
private void UpdateWithDelta()
803-
{
804-
if (CanCommitToTransform)
805-
{
806-
// Custom logic for position update. You can also create your own RPC to have your own custom movement logic
807-
// this is resistant to jitter, since the current direction is cached. This way, if we receive jittery inputs, this update still knows what to do
808-
// An improvement could be to do input decay, and slowly decrease that direction over time if no new inputs. This is useful for when a client disconnects for example, so we don't
809-
// have objects moving forever.
810-
// This doesn't "impose" a position on the server from clients (which makes that client have authority), we’re making the client “suggest”
811-
// a pos change, but the server could also do what it wants with that transform in between inputs
812-
if (InLocalSpace)
813-
{
814-
m_Transform.localPosition += m_CurrentPositionDirection * Time.deltaTime;
815-
m_Transform.localRotation = Quaternion.Euler(m_Transform.localRotation.eulerAngles + m_CurrentRotationDirection * Time.deltaTime);
816-
m_Transform.localScale += m_CurrentScaleDirection * Time.deltaTime;
817-
}
818-
else
819-
{
820-
m_Transform.position += m_CurrentPositionDirection * Time.deltaTime;
821-
m_Transform.rotation = Quaternion.Euler(m_Transform.rotation.eulerAngles + m_CurrentRotationDirection * Time.deltaTime);
822-
m_Transform.localScale += m_CurrentScaleDirection * Time.deltaTime;
823-
}
824-
}
825-
}
826-
827-
#endregion
828-
829746
#region state set
830747

831748
/// <summary>
832749
/// Directly sets a state on the authoritative transform.
833750
/// This will override any changes made previously to the transform
834751
/// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated.
752+
/// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb
753+
/// just the desired one(s)
835754
/// </summary>
836-
/// <param name="pos"></param>
837-
/// <param name="rot"></param>
838-
/// <param name="scale"></param>
755+
/// <param name="posIn"></param> new position to move to. Can be null
756+
/// <param name="rotIn"></param> new rotation to rotate to. Can be null
757+
/// <param name="scaleIn"></param> new scale to scale to. Can be null
839758
/// <exception cref="Exception"></exception>
840-
public void SetState(Vector3 pos, Vector3 rot, Vector3 scale)
759+
public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null)
841760
{
842761
if (!IsOwner)
843762
{
@@ -849,6 +768,10 @@ public void SetState(Vector3 pos, Vector3 rot, Vector3 scale)
849768
return;
850769
}
851770

771+
Vector3 pos = posIn == null ? transform.position : (Vector3)posIn;
772+
Quaternion rot = rotIn == null ? transform.rotation : (Quaternion)rotIn;
773+
Vector3 scale = scaleIn == null ? transform.localScale : (Vector3)scaleIn;
774+
852775
if (!CanCommitToTransform)
853776
{
854777
if (!IsServer)
@@ -859,16 +782,41 @@ public void SetState(Vector3 pos, Vector3 rot, Vector3 scale)
859782
else
860783
{
861784
transform.position = pos;
862-
transform.rotation = Quaternion.Euler(rot);
785+
transform.rotation = rot;
863786
transform.localScale = scale;
864787
}
865788
}
866789

790+
/// <summary>
791+
/// Simple way to affect your transform server side from clients, while still keeping a server authoritative transform. For more custom logic,
792+
/// you can implement an RPC that does the same as this method, with custom movement logic.
793+
/// It's not recommended to use this on non-kinematic or FixedUpdate based objects. Physics movements will need custom code to be synced with physics items.
794+
/// To stop movements, set delta back to 0
795+
/// </summary>
796+
/// <param name="deltaPos"></param>
797+
/// <param name="deltaRot"></param>
798+
/// <param name="deltaScale"></param>
799+
/// <exception cref="Exception"></exception>
800+
public void ApplyDelta(Vector3? deltaPosIn = null, Quaternion? deltaRotIn = null, Vector3? deltaScaleIn = null)
801+
{
802+
Vector3 deltaPos = (deltaPosIn == null ? Vector3.zero : (Vector3)deltaPosIn);
803+
Quaternion deltaRot = (deltaRotIn == null ? Quaternion.identity : (Quaternion)deltaRotIn);
804+
Vector3 deltaScale = (deltaScaleIn == null ? Vector3.zero : (Vector3)deltaScaleIn);
805+
806+
SetState(transform.position + deltaPos, transform.rotation * deltaRot, transform.localScale + deltaScale);
807+
}
808+
867809
[ServerRpc]
868-
private void SetStateServerRpc(Vector3 pos, Vector3 rot, Vector3 scale)
810+
private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale)
869811
{
812+
// server has received this RPC request to move change transform. Give the server a chance to modify or
813+
// even reject the move
814+
if (OnClientRequestChange != null)
815+
{
816+
(pos, rot, scale) = OnClientRequestChange(pos, rot, scale);
817+
}
870818
transform.position = pos;
871-
transform.rotation = Quaternion.Euler(rot);
819+
transform.rotation = rot;
872820
transform.localScale = scale;
873821
}
874822
#endregion
@@ -882,8 +830,6 @@ protected virtual void Update()
882830
return;
883831
}
884832

885-
UpdateWithDelta();
886-
887833
if (CanCommitToTransform)
888834
{
889835
if (IsServer)
@@ -895,7 +841,7 @@ protected virtual void Update()
895841
}
896842

897843
// apply interpolated value
898-
if ((NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
844+
if (NetworkManager.IsConnectedClient || NetworkManager.IsListening)
899845
{
900846
foreach (var interpolator in m_AllFloatInterpolators)
901847
{
@@ -906,7 +852,7 @@ protected virtual void Update()
906852

907853
if (!CanCommitToTransform)
908854
{
909-
if (NetworkManager.Singleton.LogLevel == LogLevel.Developer)
855+
if (NetworkManager.LogLevel == LogLevel.Developer)
910856
{
911857
var interpolatedPosition = new Vector3(m_PositionXInterpolator.GetInterpolatedValue(), m_PositionYInterpolator.GetInterpolatedValue(), m_PositionZInterpolator.GetInterpolatedValue());
912858
Debug.DrawLine(interpolatedPosition, interpolatedPosition + Vector3.up, Color.magenta, k_DebugDrawLineTime, false);
@@ -916,6 +862,8 @@ protected virtual void Update()
916862
// if we have any changes, that means made some updates locally
917863
// we apply the latest ReplNetworkState again to revert our changes
918864
var oldStateDirtyInfo = ApplyTransformToNetworkStateWithInfo(ref m_PrevNetworkState, 0, m_Transform);
865+
866+
// there is a bug in this code, as we the message is dumped out under odd circumstances
919867
if (oldStateDirtyInfo.isPositionDirty || oldStateDirtyInfo.isScaleDirty || (oldStateDirtyInfo.isRotationDirty && SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ))
920868
{
921869
// ignoring rotation dirty since quaternions will mess with euler angles, making this impossible to determine if the change to a single axis comes

com.unity.netcode.gameobjects/Samples/ClientNetworkAnimator.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Samples/ClientNetworkAnimator/Scripts.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "ClientNetworkAnimator",
3+
"rootNamespace": "",
4+
"references": [
5+
"GUID:1491147abca9d7d4bb7105af628b223e",
6+
"GUID:3b8ed52f1b5c64994af4c4e0aa4b6c4b"
7+
],
8+
"includePlatforms": [],
9+
"excludePlatforms": [],
10+
"allowUnsafeCode": false,
11+
"overrideReferences": false,
12+
"precompiledReferences": [],
13+
"autoReferenced": true,
14+
"defineConstraints": [],
15+
"versionDefines": [],
16+
"noEngineReferences": false
17+
}

com.unity.netcode.gameobjects/Samples/ClientNetworkAnimator/Scripts/ClientNetworkAnimator.asmdef.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using UnityEngine;
2+
3+
namespace Unity.Netcode.Components
4+
{
5+
/// <summary>
6+
/// A prototype component for syncing Mecanim Animator state in a client-driven manner
7+
/// </summary>
8+
[AddComponentMenu("Netcode/" + nameof(ClientNetworkAnimator))]
9+
public class ClientNetworkAnimator : NetworkAnimator
10+
{
11+
public override bool CanCommitToAnimator => IsClient && IsOwner;
12+
}
13+
}

com.unity.netcode.gameobjects/Samples/ClientNetworkAnimator/Scripts/ClientNetworkAnimator.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testproject/Assets/Scripts/MoveInCircle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private void Tick(bool isFixed)
5151
if (IsServer)
5252
{
5353
var deltaTime = isFixed ? Time.fixedDeltaTime : Time.deltaTime;
54-
GetComponent<NetworkTransform>().CommitDeltaValues(transform.forward * m_MoveSpeed, new Vector3(0, m_RotationSpeed, 0), Vector3.one);
54+
GetComponent<NetworkTransform>().ApplyDelta(transform.forward * m_MoveSpeed, Quaternion.Euler(0, m_RotationSpeed, 0), Vector3.one);
5555
// transform.position = transform.position + transform.forward * (m_MoveSpeed * deltaTime);
5656
// transform.Rotate(0, m_RotationSpeed * deltaTime, 0);
5757
// transform.localScale = ((Mathf.Sin(isFixed ? Time.fixedTime : Time.time) + 1) * Vector3.one);

testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ public override void OnNetworkSpawn()
2424
{
2525
enabled = false;
2626
}
27+
28+
transform.position += Vector3.up * NetworkObject.NetworkObjectId * 1.1f;
2729
}
2830

2931
private void Update()
3032
{
31-
if (m_NetworkAnimator.IsAuthorityOverAnimator)
33+
if (m_NetworkAnimator.CanCommitToAnimator)
3234
{
3335
if (Input.GetKeyDown(KeyCode.C))
3436
{

0 commit comments

Comments
 (0)