diff --git a/Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs b/Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs index d9f30421a04..e5fcdf5f641 100644 --- a/Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs +++ b/Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs @@ -33,8 +33,9 @@ public class NetworkLerpRigidbody : NetworkBehaviour bool ClientWithAuthority => clientAuthority && isOwned; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); if (target == null) target = GetComponent(); } diff --git a/Assets/Mirror/Components/Experimental/NetworkRigidbody.cs b/Assets/Mirror/Components/Experimental/NetworkRigidbody.cs index 660adc53f23..5e0b108d11d 100644 --- a/Assets/Mirror/Components/Experimental/NetworkRigidbody.cs +++ b/Assets/Mirror/Components/Experimental/NetworkRigidbody.cs @@ -37,8 +37,9 @@ public class NetworkRigidbody : NetworkBehaviour /// readonly ClientSyncState previousValue = new ClientSyncState(); - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); if (target == null) target = GetComponent(); } diff --git a/Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs b/Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs index 5a2c3402c63..e3bdd2992b9 100644 --- a/Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs +++ b/Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs @@ -37,8 +37,9 @@ public class NetworkRigidbody2D : NetworkBehaviour /// readonly ClientSyncState previousValue = new ClientSyncState(); - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); if (target == null) target = GetComponent(); } diff --git a/Assets/Mirror/Components/NetworkAnimator.cs b/Assets/Mirror/Components/NetworkAnimator.cs index 9d36e46110d..9e9a929eaaa 100644 --- a/Assets/Mirror/Components/NetworkAnimator.cs +++ b/Assets/Mirror/Components/NetworkAnimator.cs @@ -13,8 +13,8 @@ namespace Mirror /// If the object has authority on the server, then it should be animated on the server and state information will be sent to all clients. This is common for objects not related to a specific client, such as an enemy unit. /// The NetworkAnimator synchronizes all animation parameters of the selected Animator. It does not automatically synchronize triggers. The function SetTrigger can by used by an object with authority to fire an animation trigger on other clients. /// + // [RequireComponent(typeof(NetworkIdentity))] disabled to allow child NetworkBehaviours [AddComponentMenu("Network/Network Animator")] - [RequireComponent(typeof(NetworkIdentity))] [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-animator")] public class NetworkAnimator : NetworkBehaviour { diff --git a/Assets/Mirror/Components/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransformBase.cs index e368842f68d..2b07dae8629 100644 --- a/Assets/Mirror/Components/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransformBase.cs @@ -25,6 +25,7 @@ namespace Mirror public abstract class NetworkTransformBase : NetworkBehaviour { // 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; @@ -67,17 +68,17 @@ public abstract class NetworkTransformBase : NetworkBehaviour 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. + // 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; @@ -92,8 +93,10 @@ public abstract class NetworkTransformBase : NetworkBehaviour // make sure to call this when inheriting too! protected virtual void Awake() { } - protected virtual void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + // set target to self if none yet if (target == null) target = transform; diff --git a/Assets/Mirror/Components/RemoteStatistics.cs b/Assets/Mirror/Components/RemoteStatistics.cs index d5f48138042..aa1881563d9 100644 --- a/Assets/Mirror/Components/RemoteStatistics.cs +++ b/Assets/Mirror/Components/RemoteStatistics.cs @@ -133,8 +133,9 @@ void LoadPassword() } } - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); syncMode = SyncMode.Owner; } diff --git a/Assets/Mirror/Core/NetworkBehaviour.cs b/Assets/Mirror/Core/NetworkBehaviour.cs index 82ff75d8490..cb58bf3b04d 100644 --- a/Assets/Mirror/Core/NetworkBehaviour.cs +++ b/Assets/Mirror/Core/NetworkBehaviour.cs @@ -19,8 +19,8 @@ public enum SyncMode { Observers, Owner } public enum SyncDirection { ServerToClient, ClientToServer } /// Base class for networked components. + // [RequireComponent(typeof(NetworkIdentity))] disabled to allow child NetworkBehaviours [AddComponentMenu("")] - [RequireComponent(typeof(NetworkIdentity))] [HelpURL("https://mirror-networking.gitbook.io/docs/guides/networkbehaviour")] public abstract class NetworkBehaviour : MonoBehaviour { @@ -293,6 +293,27 @@ protected void InitSyncObject(SyncObject syncObject) }; } + protected virtual void OnValidate() + { + // we now allow child NetworkBehaviours. + // we can not [RequireComponent(typeof(NetworkIdentity))] anymore. + // instead, we need to ensure a NetworkIdentity is somewhere in the + // parents. + // only run this in Editor. don't add more runtime overhead. + +#if UNITY_EDITOR + if (GetComponent() == null && +#if UNITY_2020_3_OR_NEWER + GetComponentInParent(true) == null) +#else + GetComponentInParent() == null) +#endif + { + Debug.LogError($"{GetType()} on {name} requires a NetworkIdentity. Please add a NetworkIdentity component to {name} or it's parents."); + } +#endif + } + // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions protected void SendCommandInternal(string functionFullName, int functionHashCode, NetworkWriter writer, int channelId, bool requiresAuthority = true) { diff --git a/Assets/Mirror/Core/NetworkIdentity.cs b/Assets/Mirror/Core/NetworkIdentity.cs index df2bb9c1f13..391d7956d63 100644 --- a/Assets/Mirror/Core/NetworkIdentity.cs +++ b/Assets/Mirror/Core/NetworkIdentity.cs @@ -292,9 +292,12 @@ internal static void ResetServerStatics() // BUT internal so tests can add them after creating the NetworkIdentity internal void InitializeNetworkBehaviours() { - // Get all NetworkBehaviours - // (never null. GetComponents returns [] if none found) - NetworkBehaviours = GetComponents(); + // Get all NetworkBehaviour components, including children. + // Some users need NetworkTransform on child bones, etc. + // => Deterministic: https://forum.unity.com/threads/getcomponentsinchildren.4582/#post-33983 + // => Never null. GetComponents returns [] if none found. + // => Include inactive. We need all child components. + NetworkBehaviours = GetComponentsInChildren(true); ValidateComponents(); // initialize each one diff --git a/Assets/Mirror/Examples/AdditiveLevels/Scripts/PlayerController.cs b/Assets/Mirror/Examples/AdditiveLevels/Scripts/PlayerController.cs index 01bd142f4d9..d4ecc233bb9 100644 --- a/Assets/Mirror/Examples/AdditiveLevels/Scripts/PlayerController.cs +++ b/Assets/Mirror/Examples/AdditiveLevels/Scripts/PlayerController.cs @@ -54,8 +54,10 @@ public enum GroundState : byte { Jumping, Falling, Grounded } public Vector3Int velocity; public Vector3 direction; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (characterController == null) characterController = GetComponent(); diff --git a/Assets/Mirror/Examples/AdditiveScenes/Scripts/PlayerController.cs b/Assets/Mirror/Examples/AdditiveScenes/Scripts/PlayerController.cs index 683a51ec588..175b7bb1e95 100644 --- a/Assets/Mirror/Examples/AdditiveScenes/Scripts/PlayerController.cs +++ b/Assets/Mirror/Examples/AdditiveScenes/Scripts/PlayerController.cs @@ -55,8 +55,10 @@ public enum GroundState : byte { Jumping, Falling, Grounded } public Vector3Int velocity; public Vector3 direction; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (characterController == null) characterController = GetComponent(); diff --git a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PhysicsCollision.cs b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PhysicsCollision.cs index 221c00b99cc..811a301fbf5 100644 --- a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PhysicsCollision.cs +++ b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PhysicsCollision.cs @@ -10,8 +10,10 @@ public class PhysicsCollision : NetworkBehaviour public Rigidbody rigidbody3D; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (rigidbody3D == null) rigidbody3D = GetComponent(); } diff --git a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PlayerController.cs b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PlayerController.cs index 5d221bc32b1..7b0744625b2 100644 --- a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PlayerController.cs +++ b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/PlayerController.cs @@ -54,8 +54,10 @@ public enum GroundState : byte { Jumping, Falling, Grounded } public Vector3Int velocity; public Vector3 direction; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (characterController == null) characterController = GetComponent(); diff --git a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/Reward.cs b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/Reward.cs index e06a52c8ca9..580e57af434 100644 --- a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/Reward.cs +++ b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scripts/Reward.cs @@ -8,8 +8,10 @@ public class Reward : NetworkBehaviour public bool available = true; public RandomColor randomColor; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (randomColor == null) randomColor = GetComponent(); } diff --git a/Assets/Mirror/Examples/RigidbodyPhysics/Scripts/AddForce.cs b/Assets/Mirror/Examples/RigidbodyPhysics/Scripts/AddForce.cs index 2242bb2c4e9..f2cc09cc903 100644 --- a/Assets/Mirror/Examples/RigidbodyPhysics/Scripts/AddForce.cs +++ b/Assets/Mirror/Examples/RigidbodyPhysics/Scripts/AddForce.cs @@ -8,8 +8,10 @@ public class AddForce : NetworkBehaviour public Rigidbody rigidbody3d; public float force = 500f; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + rigidbody3d = GetComponent(); rigidbody3d.isKinematic = true; } diff --git a/Assets/Mirror/Examples/Room/Scripts/PlayerController.cs b/Assets/Mirror/Examples/Room/Scripts/PlayerController.cs index 229ef12f56f..7e623998379 100644 --- a/Assets/Mirror/Examples/Room/Scripts/PlayerController.cs +++ b/Assets/Mirror/Examples/Room/Scripts/PlayerController.cs @@ -54,8 +54,10 @@ public enum GroundState : byte { Jumping, Falling, Grounded } public Vector3Int velocity; public Vector3 direction; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (characterController == null) characterController = GetComponent(); diff --git a/Assets/Mirror/Examples/Room/Scripts/Reward.cs b/Assets/Mirror/Examples/Room/Scripts/Reward.cs index 3b1558de5fa..681f8e16948 100644 --- a/Assets/Mirror/Examples/Room/Scripts/Reward.cs +++ b/Assets/Mirror/Examples/Room/Scripts/Reward.cs @@ -8,8 +8,10 @@ public class Reward : NetworkBehaviour public bool available = true; public RandomColor randomColor; - void OnValidate() + protected override void OnValidate() { + base.OnValidate(); + if (randomColor == null) randomColor = GetComponent(); } diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab index 53acb996daf..38ec15af669 100644 --- a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab @@ -11,7 +11,6 @@ GameObject: - component: {fileID: 4492442352427800} - component: {fileID: 114118589361100106} - component: {fileID: 114250499875391520} - - component: {fileID: 7144377311613369288} - component: {fileID: 114654712548978148} - component: {fileID: 2240606817507776182} - component: {fileID: 6900008319038825817} @@ -90,39 +89,6 @@ MonoBehaviour: positionSensitivity: 0.01 rotationSensitivity: 0.01 scaleSensitivity: 0.01 ---- !u!114 &7144377311613369288 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1916082411674582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3} - m_Name: - m_EditorClassIdentifier: - syncDirection: 1 - syncMode: 0 - syncInterval: 0 - target: {fileID: 7831918942946891958} - clientAuthority: 0 - syncPosition: 0 - syncRotation: 1 - syncScale: 0 - interpolatePosition: 0 - interpolateRotation: 1 - interpolateScale: 0 - sendIntervalMultiplier: 3 - timelineOffset: 1 - showGizmos: 0 - showOverlay: 0 - overlayColor: {r: 0, g: 0, b: 0, a: 0.5} - onlySyncOnChange: 1 - bufferResetMultiplier: 5 - positionSensitivity: 0.01 - rotationSensitivity: 0.01 - scaleSensitivity: 0.01 --- !u!114 &114654712548978148 MonoBehaviour: m_ObjectHideFlags: 0 @@ -144,7 +110,8 @@ MonoBehaviour: turret: {fileID: 7831918942946891958} rotationSpeed: 80 shootKey: 32 - projectilePrefab: {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, type: 3} + projectilePrefab: {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, + type: 3} projectileMount: {fileID: 5718089106632469514} health: 4 --- !u!95 &2240606817507776182 @@ -408,11 +375,52 @@ PrefabInstance: m_SourcePrefab: {fileID: 100100000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} --- !u!4 &7831918942946891954 stripped Transform: - m_CorrespondingSourceObject: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_CorrespondingSourceObject: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, + type: 3} m_PrefabInstance: {fileID: 7831918942947279416} m_PrefabAsset: {fileID: 0} --- !u!4 &7831918942946891958 stripped Transform: - m_CorrespondingSourceObject: {fileID: 400014, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_CorrespondingSourceObject: {fileID: 400014, guid: 38b49695fc0a4418bbc350f2366660c5, + type: 3} + m_PrefabInstance: {fileID: 7831918942947279416} + m_PrefabAsset: {fileID: 0} +--- !u!1 &7831918942947312790 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 100014, guid: 38b49695fc0a4418bbc350f2366660c5, + type: 3} m_PrefabInstance: {fileID: 7831918942947279416} m_PrefabAsset: {fileID: 0} +--- !u!114 &9196118806080746389 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7831918942947312790} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3} + m_Name: + m_EditorClassIdentifier: + syncDirection: 1 + syncMode: 0 + syncInterval: 0 + target: {fileID: 7831918942946891958} + clientAuthority: 0 + syncPosition: 0 + syncRotation: 1 + syncScale: 0 + interpolatePosition: 0 + interpolateRotation: 1 + interpolateScale: 0 + sendIntervalMultiplier: 3 + timelineOffset: 1 + showGizmos: 0 + showOverlay: 0 + overlayColor: {r: 0, g: 0, b: 0, a: 0.5} + onlySyncOnChange: 1 + bufferResetMultiplier: 5 + positionSensitivity: 0.01 + rotationSensitivity: 0.01 + scaleSensitivity: 0.01 diff --git a/Assets/Mirror/Examples/Tanks/Readme.txt b/Assets/Mirror/Examples/Tanks/Readme.txt new file mode 100644 index 00000000000..9b18dec7654 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Readme.txt @@ -0,0 +1,2 @@ +Tanks demo to showcase simple networked movement, RPCs and SyncVars. +Also showcases Child-NetworkBehaviour components, see NetworkTransform on 'Turret'. \ No newline at end of file diff --git a/Assets/Mirror/Examples/Tanks/Readme.txt.meta b/Assets/Mirror/Examples/Tanks/Readme.txt.meta new file mode 100644 index 00000000000..43a50cc4519 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Readme.txt.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0f246189ee014e60a4f1b95f02d50888 +timeCreated: 1684039107 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Common/MirrorTest.cs b/Assets/Mirror/Tests/Common/MirrorTest.cs index ea15d8ef06f..41f0ce58f78 100644 --- a/Assets/Mirror/Tests/Common/MirrorTest.cs +++ b/Assets/Mirror/Tests/Common/MirrorTest.cs @@ -98,6 +98,28 @@ protected void CreateNetworked(out GameObject go, out NetworkIdentity identit instantiated.Add(go); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworkedChild(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T component) + where T : NetworkBehaviour + { + // create a GameObject with child + parent = new GameObject("parent"); + child = new GameObject("child"); + child.transform.SetParent(parent.transform); + + // setup NetworkIdentity + NetworkBehaviour in child + identity = parent.AddComponent(); + component = child.AddComponent(); + // always set syncinterval = 0 for immediate testing + component.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(parent); + } + // create GameObject + NetworkIdentity + 2x NetworkBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB) @@ -118,6 +140,31 @@ protected void CreateNetworked(out GameObject go, out NetworkIdentity iden instantiated.Add(go); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworkedChild(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T componentA, out U componentB) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + // create a GameObject with child + parent = new GameObject("parent"); + child = new GameObject("child"); + child.transform.SetParent(parent.transform); + + // setup NetworkIdentity + NetworkBehaviour in child + identity = parent.AddComponent(); + componentA = child.AddComponent(); + componentB = child.AddComponent(); + // always set syncinterval = 0 for immediate testing + componentA.syncInterval = 0; + componentB.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(parent); + } + // create GameObject + NetworkIdentity + 2x NetworkBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC) @@ -141,6 +188,34 @@ protected void CreateNetworked(out GameObject go, out NetworkIdentity i instantiated.Add(go); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworkedChild(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + // create a GameObject with child + parent = new GameObject("parent"); + child = new GameObject("child"); + child.transform.SetParent(parent.transform); + + // setup NetworkIdentity + NetworkBehaviour in child + identity = parent.AddComponent(); + componentA = child.AddComponent(); + componentB = child.AddComponent(); + componentC = child.AddComponent(); + // always set syncinterval = 0 for immediate testing + componentA.syncInterval = 0; + componentB.syncInterval = 0; + componentC.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(parent); + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, NetworkConnectionToClient ownerConnection = null) @@ -214,6 +289,26 @@ protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity Debug.Assert(component.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedChildAndSpawn(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T component, NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworkedChild(out parent, out child, out identity, out component); + + // spawn + NetworkServer.Spawn(parent, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + Debug.Assert(component.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. @@ -253,6 +348,45 @@ protected void CreateNetworkedAndSpawn( Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedChildAndSpawn( + out GameObject serverParent, out GameObject serverChild, out NetworkIdentity serverIdentity, out T serverComponent, + out GameObject clientParent, out GameObject clientChild, out NetworkIdentity clientIdentity, out T clientComponent, + NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworkedChild(out serverParent, out serverChild, out serverIdentity, out serverComponent); + CreateNetworkedChild(out clientParent, out clientChild, out clientIdentity, out clientComponent); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverParent.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverParent, ownerConnection); + ProcessMessages(); + + // double check isServer/isClient. avoids debugging headaches. + Assert.That(serverIdentity.isServer, Is.True); + Assert.That(clientIdentity.isClient, Is.True); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + Debug.Assert(clientComponent.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, NetworkConnectionToClient ownerConnection = null) @@ -277,6 +411,30 @@ protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdent } } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedChildAndSpawn(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T componentA, out U componentB, NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworkedChild(out parent, out child, out identity, out componentA, out componentB); + + // spawn + NetworkServer.Spawn(parent, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(componentA.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentB.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. @@ -320,6 +478,49 @@ protected void CreateNetworkedAndSpawn( Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedChildAndSpawn( + out GameObject serverParent, out GameObject serverChild, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, + out GameObject clientParent, out GameObject clientChild, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, + NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworkedChild(out serverParent, out serverChild, out serverIdentity, out serverComponentA, out serverComponentB); + CreateNetworkedChild(out clientParent, out clientChild, out clientIdentity, out clientComponentA, out clientComponentB); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverParent.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverParent, ownerConnection); + ProcessMessages(); + + // double check isServer/isClient. avoids debugging headaches. + Assert.That(serverIdentity.isServer, Is.True); + Assert.That(clientIdentity.isClient, Is.True); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(clientComponentA.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(clientComponentB.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC, NetworkConnectionToClient ownerConnection = null) @@ -348,6 +549,32 @@ protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkId // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedChildAndSpawn(out GameObject parent, out GameObject child, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC, NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworkedChild(out parent, out child, out identity, out componentA, out componentB, out componentC); + + // spawn + NetworkServer.Spawn(parent, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(componentA.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentB.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentC.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + } + + // create GameObject + NetworkIdentity + NetworkBehaviour in children & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawn( @@ -391,6 +618,51 @@ protected void CreateNetworkedAndSpawn( Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } + // create GameObject + NetworkIdentity + NetworkBehaviour in children & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedChildAndSpawn( + out GameObject serverParent, out GameObject serverChild, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, out V serverComponentC, + out GameObject clientParent, out GameObject clientChild, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, out V clientComponentC, + NetworkConnectionToClient ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworkedChild(out serverParent, out serverChild, out serverIdentity, out serverComponentA, out serverComponentB, out serverComponentC); + CreateNetworkedChild(out clientParent, out clientChild, out clientIdentity, out clientComponentA, out clientComponentB, out clientComponentC); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverParent.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverParent, ownerConnection); + ProcessMessages(); + + // double check isServer/isClient. avoids debugging headaches. + Assert.That(serverIdentity.isServer, Is.True); + Assert.That(clientIdentity.isClient, Is.True); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(clientComponentA.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(clientComponentB.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(clientComponentC.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs new file mode 100644 index 00000000000..528b1276d9c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs @@ -0,0 +1,234 @@ +// test coverage for child NetworkBehaviour components +using NUnit.Framework; +using UnityEngine; + +// Note: Weaver doesn't run on nested class so use namespace to group classes instead +namespace Mirror.Tests.NetworkBehaviours +{ + public class NetworkBehaviourChildSerializeTest : MirrorEditModeTest + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // SyncLists are only set dirty while owner has observers. + // need a connection. + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out _); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BehaviourWithSyncVarTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out BehaviourWithSyncVar source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out BehaviourWithSyncVar target, + out _, out _, out _, out _); + + source.SyncField = 10; + source.syncList.Add(true); + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncField, Is.EqualTo(10)); + Assert.That(target.syncList.Count, Is.EqualTo(1)); + Assert.That(target.syncList[0], Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourFromSyncVarTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourFromSyncVar source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourFromSyncVar target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 12; + source.syncListInAbstract.Add(true); + source.syncListInAbstract.Add(false); + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(12)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(2)); + Assert.That(target.syncListInAbstract[0], Is.True); + Assert.That(target.syncListInAbstract[1], Is.False); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourWithSyncVarFromSyncVarTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourWithSyncVarFromSyncVar source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourWithSyncVarFromSyncVar target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.SyncFieldInOverride = 52; + source.syncListInOverride.Add(false); + source.syncListInOverride.Add(true); + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + + Assert.That(target.SyncFieldInOverride, Is.EqualTo(52)); + Assert.That(target.syncListInOverride.Count, Is.EqualTo(2)); + Assert.That(target.syncListInOverride[0], Is.False); + Assert.That(target.syncListInOverride[1], Is.True); + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SubClassTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out SubClass source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out SubClass target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.anotherSyncField = new Vector3(40, 20, 10); + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + Assert.That(target.anotherSyncField, Is.EqualTo(new Vector3(40, 20, 10))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SubClassFromSyncVarTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out SubClassFromSyncVar source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out SubClassFromSyncVar target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.syncFieldInMiddle = "Hello World!"; + source.syncFieldInSub = new Vector3(40, 20, 10); + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + Assert.That(target.syncFieldInMiddle, Is.EqualTo("Hello World!")); + Assert.That(target.syncFieldInSub, Is.EqualTo(new Vector3(40, 20, 10))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BehaviourWithSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out BehaviourWithSyncVarWithOnSerialize source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out BehaviourWithSyncVarWithOnSerialize target, + out _, out _, out _, out _); + + source.SyncField = 10; + source.syncList.Add(true); + + source.customSerializeField = 20.5f; + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncField, Is.EqualTo(10)); + Assert.That(target.syncList.Count, Is.EqualTo(1)); + Assert.That(target.syncList[0], Is.True); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourFromSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourFromSyncVarWithOnSerialize source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourFromSyncVarWithOnSerialize target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 12; + source.syncListInAbstract.Add(true); + source.syncListInAbstract.Add(false); + + source.customSerializeField = 20.5f; + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(12)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(2)); + Assert.That(target.syncListInAbstract[0], Is.True); + Assert.That(target.syncListInAbstract[1], Is.False); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourWithSyncVarFromSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourWithSyncVarFromSyncVarWithOnSerialize source, + out _, out _, out _, out _); + CreateNetworkedChildAndSpawn(out _, out _, out _, out OverrideBehaviourWithSyncVarFromSyncVarWithOnSerialize target, + out _, out _, out _, out _); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.SyncFieldInOverride = 52; + source.syncListInOverride.Add(false); + source.syncListInOverride.Add(true); + + source.customSerializeField = 20.5f; + + NetworkBehaviourSerializeTest.SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + + Assert.That(target.SyncFieldInOverride, Is.EqualTo(52)); + Assert.That(target.syncListInOverride.Count, Is.EqualTo(2)); + Assert.That(target.syncListInOverride[0], Is.False); + Assert.That(target.syncListInOverride[1], Is.True); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs.meta new file mode 100644 index 00000000000..38722d054e6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourChildSerializeTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f7dc20c4b7474481b3e22d9604012293 +timeCreated: 1684920135 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourSerializeTest.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourSerializeTest.cs index 86c0be63870..88eb889a609 100644 --- a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourSerializeTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourSerializeTest.cs @@ -123,7 +123,7 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) public class NetworkBehaviourSerializeTest : MirrorEditModeTest { - static void SyncNetworkBehaviour(NetworkBehaviour source, NetworkBehaviour target, bool initialState) + public static void SyncNetworkBehaviour(NetworkBehaviour source, NetworkBehaviour target, bool initialState) { using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { diff --git a/Assets/ScriptTemplates/52-Mirror__Network Behaviour-NewNetworkBehaviour.cs.txt b/Assets/ScriptTemplates/52-Mirror__Network Behaviour-NewNetworkBehaviour.cs.txt index a53aee49643..baf81be8c1b 100644 --- a/Assets/ScriptTemplates/52-Mirror__Network Behaviour-NewNetworkBehaviour.cs.txt +++ b/Assets/ScriptTemplates/52-Mirror__Network Behaviour-NewNetworkBehaviour.cs.txt @@ -11,6 +11,18 @@ using Mirror; public class #SCRIPTNAME# : NetworkBehaviour { + #region Unity Callbacks + + /// + /// Add your validation code here after the base.OnValidate(); call. + /// + protected override void OnValidate() + { + base.OnValidate(); + } + + #endregion + #region Start & Stop Callbacks ///