/// This is a specialized NetworkManager that includes a networked room.
/// </summary>
/// <remarks>
/// <para>The room has slots that track the joined players, and a maximum player count that is enforced. It requires that the NetworkRoomPlayer component be on the room player objects.</para>
/// <para>NetworkRoomManager is derived from NetworkManager, and so it implements many of the virtual functions provided by the NetworkManager class. To avoid accidentally replacing functionality of the NetworkRoomManager, there are new virtual functions on the NetworkRoomManager that begin with "OnRoom". These should be used on classes derived from NetworkRoomManager instead of the virtual functions on NetworkManager.</para>
/// <para>The OnRoom*() functions have empty implementations on the NetworkRoomManager base class, so the base class functions do not have to be called.</para>
/// CheckReadyToBegin checks all of the players in the room to see if their readyToBegin flag is set.
/// <para>If all of the players are ready, then the server switches from the RoomScene to the PlayScene, essentially starting the game. This is called automatically in response to NetworkRoomPlayer.CmdChangeReadyState.</para>
/// Called on the server when a new client connects.
/// <para>Unity calls this on the Server when a Client connects to the Server. Use an override to tell the NetworkManager what to do when a client connects to the server.</para>
/// </summary>
/// <param name="conn">Connection from client.</param>
Debug.Log($"Not in Room scene...disconnecting {conn}");
conn.Disconnect();
return;
}
base.OnServerConnect(conn);
OnRoomServerConnect(conn);
}
/// <summary>
/// Called on the server when a client disconnects.
/// <para>This is called on the Server when a Client disconnects from the Server. Use an override to decide what should happen when a disconnection is detected.</para>
/// </summary>
/// <param name="conn">Connection from client.</param>
// Late joiners not supported...should've been kicked by OnServerDisconnect
Debug.Log($"Not in Room scene...disconnecting {conn}");
conn.Disconnect();
}
}
[Server]
publicvoidRecalculateRoomPlayerIndices()
{
if(roomSlots.Count>0)
{
inti=0;
foreach(NetworkRoomPlayerplayerinroomSlots)
player.index=i++;
}
}
/// <summary>
/// This causes the server to switch scenes and sets the networkSceneName.
/// <para>Clients that connect to this server will automatically switch to this scene. This is called automatically if onlineScene or offlineScene are set, but it can be called from user code to switch scenes again while the game is in progress. This automatically sets clients to be not-ready. The clients must call NetworkClient.Ready() again to participate in the new scene.</para>
Debug.LogError("NetworkRoomManager no GamePlayer prefab is registered. Please add a GamePlayer prefab.");
OnRoomStartClient();
}
/// <summary>
/// Called on the client when connected to a server.
/// <para>The default implementation of this function sets the client as ready and adds a player. Override the function to dictate what happens when the client connects.</para>
/// </summary>
publicoverridevoidOnClientConnect()
{
OnRoomClientConnect();
base.OnClientConnect();
}
/// <summary>
/// Called on clients when disconnected from a server.
/// <para>This is called on the client when it disconnects from the server. Override this function to decide what happens when the client disconnects.</para>
/// </summary>
publicoverridevoidOnClientDisconnect()
{
OnRoomClientDisconnect();
base.OnClientDisconnect();
}
/// <summary>
/// This is called when a client is stopped.
/// </summary>
publicoverridevoidOnStopClient()
{
OnRoomStopClient();
CallOnClientExitRoom();
roomSlots.Clear();
}
/// <summary>
/// Called on clients when a scene has completed loaded, when the scene load was initiated by the server.
/// <para>Scene changes can cause player objects to be destroyed. The default implementation of OnClientSceneChanged in the NetworkManager is to add a player object for the connection if no player object exists.</para>
/// </summary>
publicoverridevoidOnClientSceneChanged()
{
if(Utils.IsSceneActive(RoomScene))
{
if(NetworkClient.isConnected)
CallOnClientEnterRoom();
}
else
CallOnClientExitRoom();
base.OnClientSceneChanged();
OnRoomClientSceneChanged();
}
#endregion
#regionroomservervirtuals
/// <summary>
/// This is called on the host when a host is started.
/// </summary>
publicvirtualvoidOnRoomStartHost(){}
/// <summary>
/// This is called on the host when the host is stopped.
/// </summary>
publicvirtualvoidOnRoomStopHost(){}
/// <summary>
/// This is called on the server when the server is started - including when a host is started.
/// </summary>
publicvirtualvoidOnRoomStartServer(){}
/// <summary>
/// This is called on the server when the server is started - including when a host is stopped.
/// </summary>
publicvirtualvoidOnRoomStopServer(){}
/// <summary>
/// This is called on the server when a new client connects to the server.
/// </summary>
/// <param name="conn">The new connection.</param>
/// This allows customization of the creation of the GamePlayer object on the server.
/// <para>By default the gamePlayerPrefab is used to create the game-player, but this function allows that behaviour to be customized. The object returned from the function will be used to replace the room-player on the connection.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
/// <param name="roomPlayer">The room player object for this connection.</param>
/// This allows customization of the creation of the GamePlayer object on the server.
/// <para>This is only called for subsequent GamePlay scenes after the first one.</para>
/// <para>See <see cref="OnRoomServerCreateGamePlayer(NetworkConnectionToClient, GameObject)">OnRoomServerCreateGamePlayer(NetworkConnection, GameObject)</see> to customize the player object for the initial GamePlay scene.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
// for users to apply settings from their room player object to their in-game player object
/// <summary>
/// This is called on the server when it is told that a client has finished switching from the room scene to a game player scene.
/// <para>When switching from the room, the room-player is replaced with a game-player object. This callback function gives an opportunity to apply state from the room-player to the game-player object.</para>
/// </summary>
/// <param name="conn">The connection of the player</param>
/// <param name="roomPlayer">The room player object.</param>
/// <param name="gamePlayer">The game player object.</param>
/// <returns>False to not allow this player to replace the room player.</returns>
/// This is called on server from NetworkRoomPlayer.CmdChangeReadyState when client indicates change in Ready status.
/// </summary>
publicvirtualvoidReadyStatusChanged()
{
intCurrentPlayers=0;
intReadyPlayers=0;
foreach(NetworkRoomPlayeriteminroomSlots)
{
if(item!=null)
{
CurrentPlayers++;
if(item.readyToBegin)
ReadyPlayers++;
}
}
if(CurrentPlayers==ReadyPlayers)
CheckReadyToBegin();
else
allPlayersReady=false;
}
/// <summary>
/// This is called on the server when all the players in the room are ready.
/// <para>The default implementation of this function uses ServerChangeScene() to switch to the game player scene. By implementing this callback you can customize what happens when all the players in the room are ready, such as adding a countdown or a confirmation for a group leader.</para>
/// </summary>
publicvirtualvoidOnRoomServerPlayersReady()
{
// all players are readyToBegin, start the game
ServerChangeScene(GameplayScene);
}
/// <summary>
/// This is called on the server when CheckReadyToBegin finds that players are not ready
/// <para>May be called multiple times while not ready players are joining</para>
/// </summary>
publicvirtualvoidOnRoomServerPlayersNotReady(){}
#endregion
#regionroomclientvirtuals
/// <summary>
/// This is a hook to allow custom behaviour when the game client enters the room.
/// </summary>
publicvirtualvoidOnRoomClientEnter(){}
/// <summary>
/// This is a hook to allow custom behaviour when the game client exits the room.
/// </summary>
publicvirtualvoidOnRoomClientExit(){}
/// <summary>
/// This is called on the client when it connects to server.
/// </summary>
publicvirtualvoidOnRoomClientConnect(){}
/// <summary>
/// This is called on the client when disconnected from a server.
/// </summary>
publicvirtualvoidOnRoomClientDisconnect(){}
/// <summary>
/// This is called on the client when a client is started.
/// </summary>
publicvirtualvoidOnRoomStartClient(){}
/// <summary>
/// This is called on the client when the client stops.
/// </summary>
publicvirtualvoidOnRoomStopClient(){}
/// <summary>
/// This is called on the client when the client is finished loading a new networked scene.
/// </summary>
publicvirtualvoidOnRoomClientSceneChanged(){}
#endregion
#regionoptionalUI
/// <summary>
/// virtual so inheriting classes can roll their own
/// This component works in conjunction with the NetworkRoomManager to make up the multiplayer room system.
/// <para>The RoomPrefab object of the NetworkRoomManager must have this component on it. This component holds basic room player data required for the room to function. Game specific data for room players can be put in other components on the RoomPrefab or in scripts derived from NetworkRoomPlayer.</para>
/// This flag controls whether the default UI is shown for the room player.
/// <para>As this UI is rendered using the old GUI system, it is only recommended for testing purposes.</para>
/// </summary>
[Tooltip("This flag controls whether the default UI is shown for the room player")]
publicboolshowRoomGUI=true;
[Header("Diagnostics")]
/// <summary>
/// Diagnostic flag indicating whether this player is ready for the game to begin.
/// <para>Invoke CmdChangeReadyState method on the client to set this flag.</para>
/// <para>When all players are ready to begin, the game will start. This should not be set directly, CmdChangeReadyState should be called on the client to set it on the server.</para>
/// </summary>
[ReadOnly,Tooltip("Diagnostic flag indicating whether this player is ready for the game to begin")]
[SyncVar(hook=nameof(ReadyStateChanged))]
publicboolreadyToBegin;
/// <summary>
/// Diagnostic index of the player, e.g. Player1, Player2, etc.
/// </summary>
[ReadOnly,Tooltip("Diagnostic index of the player, e.g. Player1, Player2, etc.")]
[SyncVar(hook=nameof(IndexChanged))]
publicintindex;
#regionUnityCallbacks
/// <summary>
/// Do not use Start - Override OnStartHost / OnStartClient instead!
// NetworkRoomPlayer object must be set to DontDestroyOnLoad along with NetworkRoomManager
// in server and all clients, otherwise it will be respawned in the game scene which would
// have undesirable effects.
if(room.dontDestroyOnLoad)
DontDestroyOnLoad(gameObject);
room.roomSlots.Add(this);
if(NetworkServer.active)
room.RecalculateRoomPlayerIndices();
if(NetworkClient.active)
room.CallOnClientEnterRoom();
}
elseDebug.LogError("RoomPlayer could not find a NetworkRoomManager. The RoomPlayer requires a NetworkRoomManager object to function. Make sure that there is one in the scene.");
[Obsolete("NetworkTransform was renamed to NetworkTransformUnreliable.\nYou can easily swap the component's script by going into the Unity Inspector debug mode:\n1. Click the vertical dots on the top right in the Inspector tab.\n2. Find your NetworkTransform component\n3. Drag NetworkTransformUnreliable into the 'Script' field in the Inspector.\n4. Find the three dots and return to Normal mode.")]
[Header("Selective Sync\nDon't change these at Runtime")]
publicboolsyncPosition=true;// do not change at runtime!
publicboolsyncRotation=true;// do not change at runtime!
publicboolsyncScale=false;// do not change at runtime! rare. off by default.
[Header("Bandwidth Savings")]
[Tooltip("When true, changes are not sent unless greater than sensitivity values below.")]
publicboolonlySyncOnChange=true;
[Tooltip("Apply smallest-three quaternion compression. This is lossy, you can disable it if the small rotation inaccuracies are noticeable in your project.")]
publicboolcompressRotation=true;
// interpolation is on by default, but can be disabled to jump to
// the destination immediately. some projects need this.
[Header("Interpolation")]
[Tooltip("Set to false to have a snap-like effect on position movement.")]
publicboolinterpolatePosition=true;
[Tooltip("Set to false to have a snap-like effect on rotations.")]
publicboolinterpolateRotation=true;
[Tooltip("Set to false to remove scale smoothing. Example use-case: Instant flipping of sprites that use -X and +X for direction.")]
[Tooltip("Check/Sync every multiple of Network Manager send interval (= 1 / NM Send Rate), instead of every send interval.\n(30 NM send rate, and 3 interval, is a send every 0.1 seconds)\nA larger interval means less network sends, which has a variety of upsides. The drawbacks are delays and lower accuracy, you should find a nice balance between not sending too much, but the results looking good for your particular scenario.")]
[Range(1,120)]
publicuintsendIntervalMultiplier=1;
[Header("Timeline Offset")]
[Tooltip("Add a small timeline offset to account for decoupled arrival of NetworkTime and NetworkTransform snapshots.\nfixes: https://github.com/MirrorNetworking/Mirror/issues/3427")]
publicbooltimelineOffset=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.
// A component to synchronize the position of child transforms of networked objects.
// There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the recieved values.
usingSystem;
usingUnityEngine;
namespaceMirror
{
// Deprecated 2022-10-25
[AddComponentMenu("")]
[Obsolete("NetworkTransformChild is not needed anymore. The .target is now exposed in NetworkTransform itself. Note you can open the Inspector in debug view and replace the source script instead of reassigning everything.")]
[Tooltip("If we only sync on change, then we need to correct old snapshots if more time than sendInterval * multiplier has elapsed.\n\nOtherwise the first move will always start interpolating from the last move sequence's time, which will make it stutter when starting every time.")]
[Tooltip("Sensitivity of changes needed before an updated state is sent over the network")]
publicfloatrotationSensitivity=0.01f;
// delta compression is capable of detecting byte-level changes.
// if we scale float position to bytes,
// then small movements will only change one byte.
// this gives optimal bandwidth.
// benchmark with 0.01 precision: 130 KB/s => 60 KB/s
// benchmark with 0.1 precision: 130 KB/s => 30 KB/s
[Header("Precision")]
[Tooltip("Position is rounded in order to drastically minimize bandwidth.\n\nFor example, a precision of 0.01 rounds to a centimeter. In other words, sub-centimeter movements aren't synced until they eventually exceeded an actual centimeter.\n\nDepending on how important the object is, a precision of 0.01-0.10 (1-10 cm) is recommended.\n\nFor example, even a 1cm precision combined with delta compression cuts the Benchmark demo's bandwidth in half, compared to sending every tiny change.")]
[Range(0.00_01f,1f)]// disallow 0 division. 1mm to 1m precision is enough range.
publicfloatpositionPrecision=0.01f;// 1 cm
[Range(0.00_01f,1f)]// disallow 0 division. 1mm to 1m precision is enough range.
publicfloatscalePrecision=0.01f;// 1 cm
// delta compression needs to remember 'last' to compress against