diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index e2d4d7a36e..11f004f79d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added a warning when `NetworkManager.NetworkConfig.ConnectionApproval` is set in a distributed authority context. (#3658) ### Changed diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/connection-approval.md b/com.unity.netcode.gameobjects/Documentation~/basics/connection-approval.md index 548891d9cf..4c8647dcdb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/connection-approval.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/connection-approval.md @@ -1,5 +1,8 @@ # Connection approval +> [!NOTE] +>Connection approval is not currently supported when using a [distributed authority topology](../terms-concepts/distributed-authority.md). + With every new connection, Netcode for GameObjects performs a handshake in addition to handshakes done by the transport. This ensures that the NetworkConfig on the client matches the server's NetworkConfig. You can enable ConnectionApproval in the NetworkManager or via code by setting `NetworkManager.NetworkConfig.ConnectionApproval` to `true`. Connection approval allows you to decide, on a per connection basis, whether to allow a connection. You can also use connection approval to specify the player Prefab to create, allowing you to override the default NetworkManager-defined player Prefab on a per player basis. When you set the ConnectionApproval property of the NetworkManager to true, Netcode for GameObjects checks to make sure the `NetworkManager.ConnectionApprovalCallback` has been assigned. If assigned, the connection approval process is used for connecting clients to decide whether to allow a connection or deny it. If you don't assign the `NetworkManager.ConnectionApprovalCallback` (even with the `NetworkManager.ConnectionApprovalCallback` set to `true`), Netcode for GameObjects uses basic authentication for the user, which automatically authorizes and assigns the default player Prefab. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md index 5b03fccb2d..a36ec63e95 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md @@ -5,15 +5,15 @@ The NetworkManager is a required Netcode for GameObjects component that has all ## NetworkManager Inspector properties - **LogLevel**: Sets the network logging level -- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object and assigned to the newly connected and authorized client. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). +- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). - **NetworkPrefabs**: Where you register your network prefabs. You can also create a single network Prefab override per registered network Prefab here. - **Protocol Version**: Set this value to help distinguish between builds when the most current build has new assets that can cause issues with older builds connecting. -- **Network Transport**: Where your network specific settings and transport type is set. This field accepts any INetworkTransport implementation. However, unless you have unique transport specific needs UnityTransport is the recommended transport to use with Netcode for GameObjects. +- **Network Transport**: Where your network specific settings and transport type is set. When using a [client-server topology](../../terms-concepts/client-server.md), This field accepts any INetworkTransport implementation. However, unless you have unique transport specific needs UnityTransport is the recommended transport to use with Netcode for GameObjects. For a [distributed authority topology](../../terms-concepts/distributed-authority.md), refer to the [distributed authority quickstart](../../learn/distributed-authority-quick-start.md). - **Tick Rate**: This value controls the network tick update rate. - **Ensure Network Variable Length Safety**: (Increases cpu processing and bandwidth) When this property is checked, Netcode for GameObjects will prevent user code from writing past the boundaries of a NetworkVariable. -- **Connection Approval**: This enables [connection approval](../../basics/connection-approval.md) when this is checked and the `NetworkManager.ConnectionApprovalCallback` is assigned. +- **Connection Approval**: This enables [connection approval](../../basics/connection-approval.md) when this is checked and the `NetworkManager.ConnectionApprovalCallback` is assigned. This setting does nothing when using a [distributed authority topology](../../terms-concepts/distributed-authority.md). - **Client Connection Buffer Timeout**: This value sets the amount of time that has to pass for a connecting client to complete the connection approval process. If the time specified is exceeded the connecting client will be disconnected. -- **Force Same Prefabs**: When checked it will always verify that connecting clients have the same registered network prefabs as the server. When not checked, Netcode for GameObjects will ignore any differences. +- **Force Same Prefabs**: When checked it will always verify that connecting clients have the same registered network prefabs as the [authority game client](../../terms-concepts/authority.md). When not checked, Netcode for GameObjects will ignore any differences. - **Recycle Network Ids**: When checked this will re-use previously assigned `NetworkObject.NetworkObjectIds` after the specified period of time. - **Network Id Recycle Delay**: The time it takes for a previously assigned but currently unassigned identifier to be available for use. - **Enable Scene Management**: When checked, Netcode for GameObjects will handle scene management and client synchronization for you. When not checked, you will have to create your own scene management scripts and handle client synchronization. @@ -34,12 +34,32 @@ NetworkManager is also where you can find references to other Netcode related ma ## Starting a server, host, or client -To perform any netcode-related action that involves sending messages, you must first have a server started and listening for connections with at least one client (_a server can send RPCs to itself when running as a host_) that is connected. To accomplish this, you must first start your NetworkManager as a server, host, or client. There are three NetworkManager methods you can invoke to accomplish this: +To perform any netcode-related action that involves sending messages you must first start your NetworkManager. Your NetworkManager can be started as either a server, host, or a client. + +### Starting as a server + +A server can't act as a local client. When starting as a server, no messages will be sent until at least one other client is connected. A server can only be used with a [client-server topology](../../terms-concepts/client-server.md). + +```csharp +NetworkManager.Singleton.StartServer(); +``` + +### Starting as a host + +A host is both a server and a client (that is, has local client). A host can send RPCs to itself, and so can start sending some messages before another client is connected. A host can only be used with a [client-server topology](../../terms-concepts/client-server.md). + +```csharp +NetworkManager.Singleton.StartHost(); +``` + +### Starting as a client + +This is the most general option for the NetworkManager. Starting as a client starts a single game client that will either connect to the server or host (under a client-server topology) or connect to a distributed authority session (under a distributed authority topology). + +When using the distributed authority topology, the first client who joins and connects to the session will become the [session owner](../../terms-concepts/distributed-authority.md#session-ownership) ```csharp -NetworkManager.Singleton.StartServer(); // Starts the NetworkManager as just a server (that is, no local client). -NetworkManager.Singleton.StartHost(); // Starts the NetworkManager as both a server and a client (that is, has local client) -NetworkManager.Singleton.StartClient(); // Starts the NetworkManager as just a client. +NetworkManager.Singleton.StartClient(); ``` > [!NOTE] @@ -56,7 +76,8 @@ When starting a client, the NetworkManager uses the IP and the Port provided in The below examples use [Unity Transport](https://docs.unity3d.com/Packages/com.unity.transport@latest?subfolder=/manual/index.html) to show a few ways you can gain access to the `UnityTransport` component via the `NetworkManager.Singleton` to configure your project's network settings programmatically: -If you are only setting the IP address and port number, then you can use the `UnityTransport.SetConnectionData` method: +If you are only setting the IP address and port number, then you can use the `UnityTransport.SetConnectionData` method. This will return a [`ConnectionAddressData` **struct**](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L239-L286), holding this info. You are strongly advised to use the `SetConnectionData` method to update this info. + ```csharp NetworkManager.Singleton.GetComponent().SetConnectionData( "127.0.0.1", // The IP address is a string @@ -65,6 +86,7 @@ NetworkManager.Singleton.GetComponent().SetConnectionData( ``` If you are using the same code block to configure both your server and your client and you want to configure your server to listen to all IP addresses assigned to it, then you can also pass a 'listen address' of "0.0.0.0" to the `SetConnectionData` method, like so: + ```csharp NetworkManager.Singleton.GetComponent().SetConnectionData( "127.0.0.1", // The IP address is a string @@ -76,11 +98,8 @@ NetworkManager.Singleton.GetComponent().SetConnectionData( > [!NOTE] > Using an IP address of 0.0.0.0 for the server listen address will make a server or host listen on all IP addresses assigned to the local system. This can be particularly helpful if you are testing a client instance on the same system as well as one or more client instances connecting from other systems on your local area network. Another scenario is while developing and debugging you might sometimes test local client instances on the same system and sometimes test client instances running on external systems. -It is possible to access the current connection data at runtime, via `NetworkManager.Singleton.GetComponent().ConnectionData`. This will return a [`ConnectionAddressData` **struct**](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L239-L286), holding this info. You are strongly advised to use the `SetConnectionData` method to update this info. - If you are using Unity Relay to handle connections, however, **don't use `SetConnectionData`**. The host should call [`SetHostRelayData`](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L575), and clients should call [`SetClientRelayData`](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L588). Attempting to join a **Relay**-hosted game via entering IP/port number (via `SetConnectionData`) **won't work**. - [More information about Netcode for GameObjects Transports](../../advanced-topics/transports.md) ## Disconnecting and shutting down @@ -136,14 +155,16 @@ void DisconnectPlayer(NetworkObject player) ### Client disconnection notifications -Both the client and the server can subscribe to the `NetworkManager.OnClientDisconnectCallback` event to be notified when a client is disconnected. +Subscribe to the `NetworkManager.OnClientDisconnectCallback` event to receive notifications when a client is disconnected. **When disconnect notifications are triggered:** -- Clients are notified when they're disconnected by the server. + +- Clients are notified when they're disconnected by the server or from the distributed authority session. - The server is notified when any client disconnects from the server, whether the server disconnects the client or the client disconnects itself. - Both the server and clients are notified when their network connection is unexpectedly disconnected (network interruption). **Client notification identifiers** + - On the server-side, the client identifier parameter is the identifier of the client that disconnects. - On the client-side, the client identifier parameter is the identifier assigned to the client. - _The exception to this is when a client is disconnected before its connection is approved._ @@ -151,6 +172,7 @@ Both the client and the server can subscribe to the `NetworkManager.OnClientDisc You can also use the `NetworkManager.OnServerStopped` and `NetworkManager.OnClientStopped` callbacks to get local notifications when the server or client stops respectively. ### Connection notification manager example + Below is one example of how you can provide client connect and disconnect notifications to any type of NetworkBehaviour or MonoBehaviour derived component. > [!NOTE] diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index a89478be0a..a3faf4ad13 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -278,9 +278,16 @@ public void Handle(ref NetworkContext context) if (networkManager.DistributedAuthorityMode) { networkManager.SetSessionOwner(GetSessionOwner()); - if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) + if (networkManager.LocalClient.IsSessionOwner) { - networkManager.SceneManager.InitializeScenesLoaded(); + if (networkManager.NetworkConfig.EnableSceneManagement) + { + networkManager.SceneManager.InitializeScenesLoaded(); + } + if (networkManager.NetworkConfig.ConnectionApproval && networkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{nameof(NetworkConfig.ConnectionApproval)} is enabled but is not supported when using a distributed authority topology. The {nameof(NetworkManager.ConnectionApprovalCallback)} will not be invoked."); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 2c1e5798b8..1c80fc61c5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -27,12 +27,6 @@ public enum MotionModels private MotionModels m_MotionModel; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public NetworkTransformOwnershipTests(HostOrServer hostOrServer, MotionModels motionModel) : base(hostOrServer) { m_MotionModel = motionModel; @@ -193,7 +187,9 @@ public enum StartingOwnership private bool ClientAndServerSpawnedInstance() { - return VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClientId) && VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId); + var authorityId = GetAuthorityNetworkManager().LocalClientId; + var nonAuthorityId = GetNonAuthorityNetworkManager().LocalClientId; + return VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(authorityId) && VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(nonAuthorityId); } private bool m_UseAdjustedVariance; @@ -218,9 +214,12 @@ protected override float GetDeltaVarianceThreshold() [UnityTest] public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwnership) { + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + // Get the current ownership layout - var networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; - var networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; + var networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? authority : nonAuthority; + var networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? nonAuthority : authority; // Spawn the m_ClientNetworkTransformPrefab and wait for the client-side to spawn the object var serverSideInstance = SpawnObject(m_ClientNetworkTransformPrefab, networkManagerOwner); @@ -350,8 +349,8 @@ void LogOwnerRigidBody(int stage) LogNonOwnerRigidBody(4); // Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change - networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; - networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; + networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? nonAuthority : authority; + networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? authority : nonAuthority; ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerOwner.LocalClientId); Assert.NotNull(ownerInstance); yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId) != null); @@ -430,16 +429,19 @@ void LogOwnerRigidBody(int stage) [UnityTest] public IEnumerator ServerAuthoritativeTest() { + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + // Spawn the m_NetworkTransformPrefab and wait for the client-side to spawn the object - var serverSideInstance = SpawnObject(m_NetworkTransformPrefab, m_ServerNetworkManager); - yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(m_ClientNetworkManagers[0].LocalClientId)); + var serverSideInstance = SpawnObject(m_NetworkTransformPrefab, authority); + yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(nonAuthority.LocalClientId)); - var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ServerNetworkManager.LocalClientId); - var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[0].LocalClientId); + var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(authority.LocalClientId); + var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(nonAuthority.LocalClientId); // Make sure the owner is not kinematic and the non-owner(s) are kinematic - Assert.False(ownerInstance.GetComponent().isKinematic, $"{m_ServerNetworkManager.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); - Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); + Assert.False(ownerInstance.GetComponent().isKinematic, $"{authority.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); + Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{nonAuthority.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); // Server changes transform values var valueSetByOwner = Vector3.one * 2; @@ -458,7 +460,7 @@ public IEnumerator ServerAuthoritativeTest() } var transformToTest = nonOwnerInstance.transform; yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation); - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {nonAuthority.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}"); @@ -471,7 +473,7 @@ public IEnumerator ServerAuthoritativeTest() { yield return new WaitForFixedUpdate(); } - Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}"); + Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{nonAuthority.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}"); } protected override IEnumerator OnTearDown()