@@ -14,6 +14,9 @@ namespace Unity.Netcode.Components
14
14
[ DefaultExecutionOrder ( 100000 ) ] // this is needed to catch the update time after the transform was updated by user scripts
15
15
public class NetworkTransform : NetworkBehaviour
16
16
{
17
+ public delegate ( Vector3 pos , Quaternion rotOut , Vector3 scale ) OnClientRequestChangeDelegate ( Vector3 pos , Quaternion rot , Vector3 scale ) ;
18
+ public OnClientRequestChangeDelegate OnClientRequestChange ;
19
+
17
20
internal struct NetworkTransformState : INetworkSerializable
18
21
{
19
22
private const int k_InLocalSpaceBit = 0 ;
@@ -503,6 +506,8 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
503
506
m_PrevNetworkState = networkState ;
504
507
505
508
var interpolatedPosition = InLocalSpace ? transformToUpdate . localPosition : transformToUpdate . position ;
509
+
510
+ // todo: we should store network state w/ quats vs. euler angles
506
511
var interpolatedRotAngles = InLocalSpace ? transformToUpdate . localEulerAngles : transformToUpdate . eulerAngles ;
507
512
var interpolatedScale = InLocalSpace ? transformToUpdate . localScale : transformToUpdate . lossyScale ;
508
513
@@ -524,19 +529,24 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
524
529
interpolatedPosition . z = networkState . IsTeleporting || ! Interpolate ? networkState . Position . z : m_PositionZInterpolator . GetInterpolatedValue ( ) ;
525
530
}
526
531
527
- if ( SyncRotAngleX )
532
+ // again, we should be using quats here
533
+ if ( SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ )
528
534
{
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
+ }
531
540
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
+ }
536
545
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
+ }
540
550
}
541
551
542
552
// Scale Read
@@ -602,12 +612,11 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw
602
612
603
613
m_PrevNetworkState . Scale = interpolatedScale ;
604
614
}
605
- Debug . DrawLine ( transformToUpdate . position , transformToUpdate . position + Vector3 . up , Color . magenta , 10 , false ) ;
606
615
}
607
616
608
617
private void AddInterpolatedState ( NetworkTransformState newState )
609
618
{
610
- var sentTime = new NetworkTime ( NetworkManager . Singleton . ServerTime . TickRate , newState . SentTime ) ;
619
+ var sentTime = new NetworkTime ( NetworkManager . ServerTime . TickRate , newState . SentTime ) ;
611
620
612
621
if ( newState . HasPositionX )
613
622
{
@@ -660,7 +669,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
660
669
661
670
AddInterpolatedState ( newState ) ;
662
671
663
- if ( NetworkManager . Singleton . LogLevel == LogLevel . Developer )
672
+ if ( NetworkManager . LogLevel == LogLevel . Developer )
664
673
{
665
674
var pos = new Vector3 ( newState . PositionX , newState . PositionY , newState . PositionZ ) ;
666
675
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()
734
743
m_ReplicatedNetworkState . OnValueChanged -= OnNetworkStateChanged ;
735
744
}
736
745
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
-
829
746
#region state set
830
747
831
748
/// <summary>
832
749
/// Directly sets a state on the authoritative transform.
833
750
/// This will override any changes made previously to the transform
834
751
/// 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)
835
754
/// </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
839
758
/// <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 )
841
760
{
842
761
if ( ! IsOwner )
843
762
{
@@ -849,6 +768,10 @@ public void SetState(Vector3 pos, Vector3 rot, Vector3 scale)
849
768
return ;
850
769
}
851
770
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
+
852
775
if ( ! CanCommitToTransform )
853
776
{
854
777
if ( ! IsServer )
@@ -859,16 +782,41 @@ public void SetState(Vector3 pos, Vector3 rot, Vector3 scale)
859
782
else
860
783
{
861
784
transform . position = pos ;
862
- transform . rotation = Quaternion . Euler ( rot ) ;
785
+ transform . rotation = rot ;
863
786
transform . localScale = scale ;
864
787
}
865
788
}
866
789
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
+
867
809
[ ServerRpc ]
868
- private void SetStateServerRpc ( Vector3 pos , Vector3 rot , Vector3 scale )
810
+ private void SetStateServerRpc ( Vector3 pos , Quaternion rot , Vector3 scale )
869
811
{
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
+ }
870
818
transform . position = pos ;
871
- transform . rotation = Quaternion . Euler ( rot ) ;
819
+ transform . rotation = rot ;
872
820
transform . localScale = scale ;
873
821
}
874
822
#endregion
@@ -882,8 +830,6 @@ protected virtual void Update()
882
830
return ;
883
831
}
884
832
885
- UpdateWithDelta ( ) ;
886
-
887
833
if ( CanCommitToTransform )
888
834
{
889
835
if ( IsServer )
@@ -895,7 +841,7 @@ protected virtual void Update()
895
841
}
896
842
897
843
// apply interpolated value
898
- if ( ( NetworkManager . Singleton . IsConnectedClient || NetworkManager . Singleton . IsListening ) )
844
+ if ( NetworkManager . IsConnectedClient || NetworkManager . IsListening )
899
845
{
900
846
foreach ( var interpolator in m_AllFloatInterpolators )
901
847
{
@@ -906,7 +852,7 @@ protected virtual void Update()
906
852
907
853
if ( ! CanCommitToTransform )
908
854
{
909
- if ( NetworkManager . Singleton . LogLevel == LogLevel . Developer )
855
+ if ( NetworkManager . LogLevel == LogLevel . Developer )
910
856
{
911
857
var interpolatedPosition = new Vector3 ( m_PositionXInterpolator . GetInterpolatedValue ( ) , m_PositionYInterpolator . GetInterpolatedValue ( ) , m_PositionZInterpolator . GetInterpolatedValue ( ) ) ;
912
858
Debug . DrawLine ( interpolatedPosition , interpolatedPosition + Vector3 . up , Color . magenta , k_DebugDrawLineTime , false ) ;
@@ -916,6 +862,8 @@ protected virtual void Update()
916
862
// if we have any changes, that means made some updates locally
917
863
// we apply the latest ReplNetworkState again to revert our changes
918
864
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
919
867
if ( oldStateDirtyInfo . isPositionDirty || oldStateDirtyInfo . isScaleDirty || ( oldStateDirtyInfo . isRotationDirty && SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ ) )
920
868
{
921
869
// ignoring rotation dirty since quaternions will mess with euler angles, making this impossible to determine if the change to a single axis comes
0 commit comments