Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
441 changes: 308 additions & 133 deletions com.unity.netcode.gameobjects/Components/NetworkTransform.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections;
#if NGO_TRANSFORM_DEBUG
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -57,7 +56,7 @@ protected override void OnCreatePlayerPrefab()
else
{
var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransformTestComponent>();
networkTransform.Interpolate = false;
networkTransform.Interpolate = true;
}
}

Expand All @@ -74,8 +73,8 @@ protected override void OnServerAndClientsCreated()
protected override IEnumerator OnServerAndClientsConnected()
{
// Get the client player representation on both the server and the client side
m_ServerSideClientPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId];
m_ClientSideClientPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId];
m_ServerSideClientPlayer = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject;
m_ClientSideClientPlayer = m_ClientNetworkManagers[0].LocalClient.PlayerObject;

// Get the NetworkTransformTestComponent to make sure the client side is ready before starting test
var otherSideNetworkTransformComponent = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
Expand Down Expand Up @@ -118,38 +117,38 @@ public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool test

authPlayerTransform.position = new Vector3(10, 20, 30);

yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.position.x > approximation);
yield return WaitForConditionOrTimeOut(ServerClientPositionMatches);

Assert.False(s_GlobalTimeoutHelper.TimedOut, $"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}");
AssertOnTimeout($"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}");

Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with ==
//Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with ==

// test rotation
authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter
Assert.AreEqual(Quaternion.identity, otherSideNetworkTransform.transform.rotation, "wrong initial value for rotation"); // sanity check

yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation);
yield return WaitForConditionOrTimeOut(ServerClientRotationMatches);

Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for rotation change");
AssertOnTimeout("timeout while waiting for rotation change");

// approximation needed here since eulerAngles isn't super precise.
Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}");
Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}");
Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}");
//// approximation needed here since eulerAngles isn't super precise.
//Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}");
//Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}");
//Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}");

// test scale
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check
authPlayerTransform.localScale = new Vector3(2, 3, 4);

yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation);
yield return WaitForConditionOrTimeOut(ServerClientScaleMatches);

Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for scale change");
AssertOnTimeout("timeout while waiting for scale change");

UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost");
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost");
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost");
//UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost");

// todo reparent and test
// todo test all public API
Expand Down Expand Up @@ -251,6 +250,93 @@ public IEnumerator TestRotationThresholdDeltaCheck()
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
}

private bool ValidateBitSetValues(NetworkTransform.NetworkTransformState serverState, NetworkTransform.NetworkTransformState clientState)
{
if (serverState.HasPositionX == clientState.HasPositionX && serverState.HasPositionY == clientState.HasPositionY && serverState.HasPositionZ == clientState.HasPositionZ &&
serverState.HasRotAngleX == clientState.HasRotAngleX && serverState.HasRotAngleY == clientState.HasRotAngleY && serverState.HasRotAngleZ == clientState.HasRotAngleZ &&
serverState.HasScaleX == clientState.HasScaleX && serverState.HasScaleY == clientState.HasScaleY && serverState.HasScaleZ == clientState.HasScaleZ)
{
return true;
}
return false;
}

/// <summary>
/// </summary>
[UnityTest]
public IEnumerator TestBitsetValue()
{
// Get the client player's NetworkTransform for both instances
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
otherSideNetworkTransform.RotAngleThreshold = authoritativeNetworkTransform.RotAngleThreshold = 0.1f;
yield return s_DefaultWaitForTick;

authoritativeNetworkTransform.Interpolate = true;
otherSideNetworkTransform.Interpolate = true;

yield return s_DefaultWaitForTick;

authoritativeNetworkTransform.transform.rotation = Quaternion.Euler(1, 2, 3);
var serverLastSentState = authoritativeNetworkTransform.GetLastSentState();
var clientReplicatedState = otherSideNetworkTransform.ReplicatedNetworkState.Value;
yield return WaitForConditionOrTimeOut(() => ValidateBitSetValues(serverLastSentState, clientReplicatedState));
AssertOnTimeout($"Server-side sent Bitset state != Client-side replicated Bitset state!");

yield return WaitForConditionOrTimeOut(ServerClientRotationMatches);
AssertOnTimeout($"[Timed-Out] Server-side client rotation {m_ServerSideClientPlayer.transform.rotation.eulerAngles} != Client-side client rotation {m_ClientSideClientPlayer.transform.rotation.eulerAngles}");
}

private bool Aproximately(float x, float y)
{
return Mathf.Abs(x - y) <= k_AproximateDeltaVariance;
}

private const float k_AproximateDeltaVariance = 0.01f;

private bool ServerClientRotationMatches()
{
var serverEulerRotation = m_ServerSideClientPlayer.transform.rotation.eulerAngles;
var clientEulerRotation = m_ClientSideClientPlayer.transform.rotation.eulerAngles;
var xIsEqual = Aproximately(serverEulerRotation.x, clientEulerRotation.x);
var yIsEqual = Aproximately(serverEulerRotation.y, clientEulerRotation.y);
var zIsEqual = Aproximately(serverEulerRotation.z, clientEulerRotation.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Server-side client rotation {m_ServerSideClientPlayer.transform.rotation.eulerAngles} != Client-side client rotation {m_ClientSideClientPlayer.transform.rotation.eulerAngles}");
}
return xIsEqual && yIsEqual && zIsEqual;
}

private bool ServerClientPositionMatches()
{
var serverPosition = m_ServerSideClientPlayer.transform.position;
var clientPosition = m_ClientSideClientPlayer.transform.position;
var xIsEqual = Aproximately(serverPosition.x, clientPosition.x);
var yIsEqual = Aproximately(serverPosition.y, clientPosition.y);
var zIsEqual = Aproximately(serverPosition.z, clientPosition.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Server-side client position {m_ServerSideClientPlayer.transform.position} != Client-side client position {m_ClientSideClientPlayer.transform.position}");
}
return xIsEqual && yIsEqual && zIsEqual;
}

private bool ServerClientScaleMatches()
{
var serverScale = m_ServerSideClientPlayer.transform.localScale;
var clientScale = m_ClientSideClientPlayer.transform.localScale;
var xIsEqual = Aproximately(serverScale.x, clientScale.x);
var yIsEqual = Aproximately(serverScale.y, clientScale.y);
var zIsEqual = Aproximately(serverScale.z, clientScale.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Server-side client scale {m_ServerSideClientPlayer.transform.localScale} != Client-side client scale {m_ClientSideClientPlayer.transform.localScale}");
}
return xIsEqual && yIsEqual && zIsEqual;
}


/*
* ownership change
* test teleport with interpolation
Expand All @@ -259,7 +345,8 @@ public IEnumerator TestRotationThresholdDeltaCheck()
*/
protected override IEnumerator OnTearDown()
{
UnityEngine.Object.DestroyImmediate(m_PlayerPrefab);
m_EnableVerboseDebug = false;
Object.DestroyImmediate(m_PlayerPrefab);
yield return base.OnTearDown();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,10 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: 29cabf623d47bb345a9bb4140e4397d7,
type: 3}
propertyPath: PositionThreshold
value: 0.01
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 29cabf623d47bb345a9bb4140e4397d7, type: 3}
11 changes: 7 additions & 4 deletions testproject/Assets/Prefabs/Player.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 5, y: 0.625, z: 5}
m_LocalScale: {x: 1.25, y: 1.25, z: 1.25}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3519470446676406143}
m_Father: {fileID: 0}
Expand All @@ -62,12 +63,11 @@ MonoBehaviour:
SyncScaleX: 1
SyncScaleY: 1
SyncScaleZ: 1
PositionThreshold: 0
RotAngleThreshold: 0
ScaleThreshold: 0
PositionThreshold: 0.01
RotAngleThreshold: 0.01
ScaleThreshold: 0.1
InLocalSpace: 0
Interpolate: 1
FixedSendsPerSecond: 15
--- !u!114 &-3775814466963834669
MonoBehaviour:
m_ObjectHideFlags: 0
Expand Down Expand Up @@ -103,6 +103,7 @@ MeshRenderer:
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
Expand Down Expand Up @@ -239,6 +240,7 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1.045, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4079352819444256611}
m_RootOrder: 0
Expand All @@ -262,6 +264,7 @@ MeshRenderer:
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,21 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: ScaleThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: PositionThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: RotAngleThreshold
value: 0.1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3}
--- !u!1001 &365996112
Expand Down Expand Up @@ -269,6 +284,21 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: ScaleThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: PositionThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: RotAngleThreshold
value: 0.1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3}
--- !u!850595691 &903034822
Expand All @@ -278,7 +308,7 @@ LightingSettings:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
serializedVersion: 3
serializedVersion: 4
m_GIWorkflowMode: 1
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
Expand All @@ -291,7 +321,7 @@ LightingSettings:
m_LightmapMaxSize: 1024
m_BakeResolution: 40
m_Padding: 2
m_TextureCompression: 1
m_LightmapCompression: 3
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
Expand Down Expand Up @@ -332,6 +362,7 @@ LightingSettings:
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_PVRTiledBaking: 0
--- !u!1001 &1152084533
PrefabInstance:
m_ObjectHideFlags: 0
Expand Down Expand Up @@ -404,6 +435,21 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: ScaleThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: PositionThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: RotAngleThreshold
value: 0.1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3}
--- !u!1001 &1247056486
Expand Down Expand Up @@ -478,5 +524,20 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: ScaleThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: PositionThreshold
value: 0.01
objectReference: {fileID: 0}
- target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b,
type: 3}
propertyPath: RotAngleThreshold
value: 0.1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Unity.Netcode.Components;

namespace TestProject.ManualTests
{
public class ClientNetworkTransform : NetworkTransform
{
protected override bool OnIsServerAuthoritative()
{
return false;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading