The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [2.0.0-exp.2] - 2024-04-02 ### Added - Added updates to all internal messages to account for a distributed authority network session connection. (#2863) - Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to: - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`. - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use. - Provides additional public methods: - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting). - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping. - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep. - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic. - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state. - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized. - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) - Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863) - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority. - Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863) - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. - Added distributed authority mode support to `NetworkAnimator`. (#2863) - Added session mode selection to `NetworkManager` inspector view. (#2863) - Added distributed authority permissions feature. (#2863) - Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863) - Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863) - Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863) - Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863) - Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863) - Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863) - Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863) - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example). - Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863) - Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) - Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863) - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true. - Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863) - Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863) - Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863) ### Fixed - Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) - Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) ### Changed - Changed client side awareness of other clients is now the same as a server or host. (#2863) - Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863) - Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863) - Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863) - Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions). (#2863) - Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863) - Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863) - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
379 lines
17 KiB
C#
379 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
/// <summary>
|
|
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
|
/// </summary>
|
|
internal class DefaultSceneManagerHandler : ISceneManagerHandler
|
|
{
|
|
private Scene m_InvalidScene = new Scene();
|
|
|
|
internal struct SceneEntry
|
|
{
|
|
public bool IsAssigned;
|
|
public Scene Scene;
|
|
}
|
|
public bool IsIntegrationTest() { return false; }
|
|
|
|
internal Dictionary<string, Dictionary<int, SceneEntry>> SceneNameToSceneHandles = new Dictionary<string, Dictionary<int, SceneEntry>>();
|
|
|
|
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
|
|
{
|
|
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
|
|
sceneEventProgress.SetAsyncOperation(operation);
|
|
return operation;
|
|
}
|
|
|
|
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
|
|
{
|
|
var operation = SceneManager.UnloadSceneAsync(scene);
|
|
sceneEventProgress.SetAsyncOperation(operation);
|
|
return operation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets scene tracking
|
|
/// </summary>
|
|
public void ClearSceneTracking(NetworkManager networkManager)
|
|
{
|
|
SceneNameToSceneHandles.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops tracking a specific scene
|
|
/// </summary>
|
|
public void StopTrackingScene(int handle, string name, NetworkManager networkManager)
|
|
{
|
|
if (SceneNameToSceneHandles.ContainsKey(name))
|
|
{
|
|
if (SceneNameToSceneHandles[name].ContainsKey(handle))
|
|
{
|
|
SceneNameToSceneHandles[name].Remove(handle);
|
|
if (SceneNameToSceneHandles[name].Count == 0)
|
|
{
|
|
SceneNameToSceneHandles.Remove(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts tracking a specific scene
|
|
/// </summary>
|
|
public void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager)
|
|
{
|
|
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
|
|
{
|
|
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
|
|
}
|
|
|
|
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
|
|
{
|
|
var sceneEntry = new SceneEntry()
|
|
{
|
|
IsAssigned = true,
|
|
Scene = scene
|
|
};
|
|
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
|
|
}
|
|
else
|
|
{
|
|
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if there is an existing scene loaded that matches the scene name but has not been assigned
|
|
/// </summary>
|
|
public bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager)
|
|
{
|
|
var scenesWithSceneName = new List<Scene>();
|
|
|
|
// Get all loaded scenes with the same name
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (scene.name == sceneName)
|
|
{
|
|
scenesWithSceneName.Add(scene);
|
|
}
|
|
}
|
|
|
|
// If there are no scenes of this name loaded then we have no loaded scenes
|
|
// to use
|
|
if (scenesWithSceneName.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we have 1 or more scenes with the name and we have no entries, then we do have
|
|
// a scene to use
|
|
if (scenesWithSceneName.Count > 0 && !SceneNameToSceneHandles.ContainsKey(sceneName))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Determine if any of the loaded scenes has been used for synchronizing
|
|
foreach (var scene in scenesWithSceneName)
|
|
{
|
|
// If we don't have the handle, then we can use that scene
|
|
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If we have an entry, but it is not yet assigned (i.e. preloaded)
|
|
// then we can use that.
|
|
if (!SceneNameToSceneHandles[scene.name][scene.handle].IsAssigned)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// If none were found, then we have no available scene (which most likely means one will get loaded)
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will find any scene entry that hasn't been used/assigned, set the entry to assigned, and
|
|
/// return the associated scene. If none are found it returns an invalid scene.
|
|
/// </summary>
|
|
public Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager)
|
|
{
|
|
if (SceneNameToSceneHandles.ContainsKey(sceneName))
|
|
{
|
|
foreach (var sceneHandleEntry in SceneNameToSceneHandles[sceneName])
|
|
{
|
|
if (!sceneHandleEntry.Value.IsAssigned)
|
|
{
|
|
var sceneEntry = sceneHandleEntry.Value;
|
|
sceneEntry.IsAssigned = true;
|
|
SceneNameToSceneHandles[sceneName][sceneHandleEntry.Key] = sceneEntry;
|
|
return sceneEntry.Scene;
|
|
}
|
|
}
|
|
}
|
|
// If we found nothing return an invalid scene
|
|
return m_InvalidScene;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only invoked is client synchronization is additive, this will generate the scene tracking table
|
|
/// in order to re-use the same scenes the server is synchronizing instead of having to unload the
|
|
/// scenes and reload them when synchronizing (i.e. client disconnects due to external reason, the
|
|
/// same application instance is still running, the same scenes are still loaded on the client, and
|
|
/// upon reconnecting the client doesn't have to unload the scenes and then reload them)
|
|
/// </summary>
|
|
public void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager)
|
|
{
|
|
SceneNameToSceneHandles.Clear();
|
|
var sceneCount = SceneManager.sceneCount;
|
|
for (int i = 0; i < sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
|
|
{
|
|
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
|
|
}
|
|
|
|
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
|
|
{
|
|
var sceneEntry = new SceneEntry()
|
|
{
|
|
IsAssigned = false,
|
|
Scene = scene
|
|
};
|
|
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
|
|
if (!scenesLoaded.ContainsKey(scene.handle))
|
|
{
|
|
scenesLoaded.Add(scene.handle, scene);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<Scene> m_ScenesToUnload = new List<Scene>();
|
|
|
|
/// <summary>
|
|
/// Unloads any scenes that have not been assigned.
|
|
/// </summary>
|
|
/// <param name="networkManager"></param>
|
|
public void UnloadUnassignedScenes(NetworkManager networkManager = null)
|
|
{
|
|
var sceneManager = networkManager.SceneManager;
|
|
SceneManager.sceneUnloaded += SceneManager_SceneUnloaded;
|
|
foreach (var sceneEntry in SceneNameToSceneHandles)
|
|
{
|
|
var scenHandleEntries = SceneNameToSceneHandles[sceneEntry.Key];
|
|
foreach (var sceneHandleEntry in scenHandleEntries)
|
|
{
|
|
if (!sceneHandleEntry.Value.IsAssigned)
|
|
{
|
|
if (sceneManager.VerifySceneBeforeUnloading == null || sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene))
|
|
{
|
|
m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach (var sceneToUnload in m_ScenesToUnload)
|
|
{
|
|
SceneManager.UnloadSceneAsync(sceneToUnload);
|
|
}
|
|
}
|
|
|
|
private void SceneManager_SceneUnloaded(Scene scene)
|
|
{
|
|
if (SceneNameToSceneHandles.ContainsKey(scene.name))
|
|
{
|
|
if (SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
|
|
{
|
|
SceneNameToSceneHandles[scene.name].Remove(scene.handle);
|
|
}
|
|
if (SceneNameToSceneHandles[scene.name].Count == 0)
|
|
{
|
|
SceneNameToSceneHandles.Remove(scene.name);
|
|
}
|
|
m_ScenesToUnload.Remove(scene);
|
|
if (m_ScenesToUnload.Count == 0)
|
|
{
|
|
SceneManager.sceneUnloaded -= SceneManager_SceneUnloaded;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles determining if a client should attempt to load a scene during synchronization.
|
|
/// </summary>
|
|
/// <param name="sceneName">name of the scene to be loaded</param>
|
|
/// <param name="isPrimaryScene">when in client synchronization mode single, this determines if the scene is the primary active scene</param>
|
|
/// <param name="clientSynchronizationMode">the current client synchronization mode</param>
|
|
/// <param name="networkManager"><see cref="NetworkManager"/> instance</param>
|
|
/// <returns></returns>
|
|
public bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager)
|
|
{
|
|
var shouldPassThrough = clientSynchronizationMode == LoadSceneMode.Single ? false : DoesSceneHaveUnassignedEntry(sceneName, networkManager);
|
|
var activeScene = SceneManager.GetActiveScene();
|
|
|
|
// If shouldPassThrough is not yet true and the scene to be loaded is the currently active scene
|
|
if (!shouldPassThrough && sceneName == activeScene.name)
|
|
{
|
|
// In additive mode we always pass through, but in LoadSceneMode.Single we only pass through if the currently active scene
|
|
// is the primary scene to be loaded
|
|
if (clientSynchronizationMode == LoadSceneMode.Additive || (isPrimaryScene && clientSynchronizationMode == LoadSceneMode.Single))
|
|
{
|
|
// don't try to reload this scene and pass through to post load processing.
|
|
shouldPassThrough = true;
|
|
}
|
|
}
|
|
return shouldPassThrough;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles migrating dynamically spawned NetworkObjects to the DDOL when a scene is unloaded
|
|
/// </summary>
|
|
/// <param name="networkManager"><see cref="NetworkManager"/>relative instance</param>
|
|
/// <param name="scene">scene being unloaded</param>
|
|
public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene)
|
|
{
|
|
bool isActiveScene = scene == SceneManager.GetActiveScene();
|
|
// Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects
|
|
// are despawned.
|
|
var localSpawnedObjectsHashSet = new HashSet<NetworkObject>(networkManager.SpawnManager.SpawnedObjectsList);
|
|
foreach (var networkObject in localSpawnedObjectsHashSet)
|
|
{
|
|
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only NetworkObjects marked to not be destroyed with the scene and are not already in the DDOL are preserved
|
|
if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene)
|
|
{
|
|
// Only move dynamically spawned NetworkObjects with no parent as the children will follow
|
|
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
|
|
{
|
|
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
|
}
|
|
}
|
|
else if (networkManager.IsServer)
|
|
{
|
|
networkObject.Despawn();
|
|
}
|
|
else // We are a client, migrate the object into the DDOL temporarily until it receives the destroy command from the server
|
|
{
|
|
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the client synchronization mode which impacts whether both the server or client take into consideration scenes loaded before
|
|
/// starting the <see cref="NetworkManager"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <see cref="LoadSceneMode.Single"/>: Does not take preloaded scenes into consideration
|
|
/// <see cref="LoadSceneMode.Single"/>: Does take preloaded scenes into consideration
|
|
/// </remarks>
|
|
/// <param name="networkManager">relative <see cref="NetworkManager"/> instance</param>
|
|
/// <param name="mode"><see cref="LoadSceneMode.Single"/> or <see cref="LoadSceneMode.Additive"/></param>
|
|
public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode)
|
|
{
|
|
var sceneManager = networkManager.SceneManager;
|
|
// Don't let non-authority set this value
|
|
if ((!networkManager.DistributedAuthorityMode && !networkManager.IsServer) || (networkManager.DistributedAuthorityMode && !networkManager.LocalClient.IsSessionOwner))
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning("Clients should not set this value as it is automatically synchronized with the server's setting!");
|
|
}
|
|
return;
|
|
}
|
|
else // Warn users if they are changing this after there are clients already connected and synchronized
|
|
if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode)
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
|
|
}
|
|
}
|
|
|
|
// For additive client synchronization, we take into consideration scenes
|
|
// already loaded.
|
|
if (mode == LoadSceneMode.Additive)
|
|
{
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
|
|
// If using scene verification
|
|
if (sceneManager.VerifySceneBeforeLoading != null)
|
|
{
|
|
// Determine if we should take this scene into consideration
|
|
if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If the scene is not already in the ScenesLoaded list, then add it
|
|
if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle))
|
|
{
|
|
sceneManager.ScenesLoaded.Add(scene.handle, scene);
|
|
}
|
|
}
|
|
}
|
|
// Set the client synchronization mode
|
|
sceneManager.ClientSynchronizationMode = mode;
|
|
}
|
|
}
|
|
}
|