diff --git a/Assets/Mirror/Components/Discovery.meta b/Assets/Mirror/Components/Discovery.meta new file mode 100644 index 000000000..d5bb0cbb2 --- /dev/null +++ b/Assets/Mirror/Components/Discovery.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b5dcf9618f5e14a4eb60bff5480284a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs b/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs new file mode 100644 index 000000000..bd5714b79 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs @@ -0,0 +1,112 @@ +using UnityEngine; +using System.Net; +using System; +using UnityEngine.Events; + +namespace Mirror.Discovery +{ + [Serializable] + public class ServerFoundUnityEvent : UnityEvent { }; + + [DisallowMultipleComponent] + [AddComponentMenu("Network/NetworkDiscovery")] + public class NetworkDiscovery : NetworkDiscoveryBase + { + #region Server + + public long ServerId { get; private set; } + + [Tooltip("Transport to be advertised during discovery")] + public Transport transport; + + [Tooltip("Invoked when a server is found")] + public ServerFoundUnityEvent OnServerFound; + + public void Start() + { + ServerId = RandomLong(); + + // active transport gets initialized in awake + // so make sure we set it here in Start() (after awakes) + // Or just let the user assign it in the inspector + if (transport == null) + transport = Transport.activeTransport; + } + + /// + /// Process the request from a client + /// + /// + /// Override if you wish to provide more information to the clients + /// such as the name of the host player + /// + /// Request comming from client + /// Address of the client that sent the request + /// The message to be sent back to the client or null + protected override ServerResponse ProcessRequest(ServerRequest request, IPEndPoint endpoint) + { + // In this case we don't do anything with the request + // but other discovery implementations might want to use the data + // in there, This way the client can ask for + // specific game mode or something + + try + { + // this is an example reply message, return your own + // to include whatever is relevant for your game + return new ServerResponse + { + serverId = ServerId, + uri = transport.ServerUri() + }; + } + catch (NotImplementedException) + { + Debug.LogError($"Transport {transport} does not support network discovery"); + throw; + } + } + + #endregion + + #region Client + + /// + /// Create a message that will be broadcasted on the network to discover servers + /// + /// + /// Override if you wish to include additional data in the discovery message + /// such as desired game mode, language, difficulty, etc... + /// An instance of ServerRequest with data to be broadcasted + protected override ServerRequest GetRequest() => new ServerRequest(); + + /// + /// Process the answer from a server + /// + /// + /// A client receives a reply from a server, this method processes the + /// reply and raises an event + /// + /// Response that came from the server + /// Address of the server that replied + protected override void ProcessResponse(ServerResponse response, IPEndPoint endpoint) + { + // we received a message from the remote endpoint + response.EndPoint = endpoint; + + // although we got a supposedly valid url, we may not be able to resolve + // the provided host + // However we know the real ip address of the server because we just + // received a packet from it, so use that as host. + UriBuilder realUri = new UriBuilder(response.uri) + { + Host = response.EndPoint.Address.ToString() + }; + response.uri = realUri.Uri; + + OnServerFound.Invoke(response); + } + + #endregion + } +} diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta b/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta new file mode 100644 index 000000000..523cc52ec --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c761308e733c51245b2e8bb4201f46dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs new file mode 100644 index 000000000..419627476 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs @@ -0,0 +1,357 @@ +using UnityEngine; +using System.Net; +using System.Net.Sockets; +using System; +using System.Threading.Tasks; + +// Based on https://github.com/EnlightenedOne/MirrorNetworkDiscovery +// forked from https://github.com/in0finite/MirrorNetworkDiscovery +// Both are MIT Licensed + +namespace Mirror.Discovery +{ + /// + /// Base implementation for Network Discovery. Extend this component + /// to provide custom discovery with game specific data + /// for a sample implementation + /// + [DisallowMultipleComponent] + [HelpURL("https://mirror-networking.com/docs/Components/NetworkDiscovery.html")] + public abstract class NetworkDiscoveryBase : MonoBehaviour + where Request : IMessageBase, new() + where Response : IMessageBase, new() + { + public static bool SupportedOnThisPlatform { get { return Application.platform != RuntimePlatform.WebGLPlayer; } } + + // each game should have a random unique handshake, this way you can tell if this is the same game or not + [HideInInspector] + public long secretHandshake; + + [SerializeField] + [Tooltip("The UDP port the server will listen for multi-cast messages")] + int serverBroadcastListenPort = 47777; + + [SerializeField] + [Tooltip("Time in seconds between multi-cast messages")] + [Range(1, 60)] + float ActiveDiscoveryInterval = 3; + + protected UdpClient serverUdpClient = null; + protected UdpClient clientUdpClient = null; + +#if UNITY_EDITOR + void OnValidate() + { + if (secretHandshake == 0) + { + secretHandshake = RandomLong(); + UnityEditor.Undo.RecordObject(this, "Set secret handshake"); + } + } +#endif + + public static long RandomLong() + { + int value1 = UnityEngine.Random.Range(int.MinValue, int.MaxValue); + int value2 = UnityEngine.Random.Range(int.MinValue, int.MaxValue); + return value1 + ((long)value2 << 32); + } + + // Ensure the ports are cleared no matter when Game/Unity UI exits + void OnApplicationQuit() + { + Shutdown(); + } + + void Shutdown() + { + if (serverUdpClient != null) + { + try + { + serverUdpClient.Close(); + } + catch (Exception) + { + // it is just close, swallow the error + } + + serverUdpClient = null; + } + + if (clientUdpClient != null) + { + try + { + clientUdpClient.Close(); + } + catch (Exception) + { + // it is just close, swallow the error + } + + clientUdpClient = null; + } + + CancelInvoke(); + } + + #region Server + + /// + /// Advertise this server in the local network + /// + /// Network Manager + public void AdvertiseServer() + { + if (!SupportedOnThisPlatform) + throw new PlatformNotSupportedException("Network discovery not supported in this platform"); + + StopDiscovery(); + + // Setup port -- may throw exception + serverUdpClient = new UdpClient(serverBroadcastListenPort) + { + EnableBroadcast = true, + MulticastLoopback = false + }; + + // listen for client pings + _ = ServerListenAsync(); + } + + public async Task ServerListenAsync() + { + while (true) + { + try + { + await ReceiveRequestAsync(serverUdpClient); + } + catch (ObjectDisposedException) + { + // socket has been closed + break; + } + catch (Exception) + { + continue; + } + } + } + + async Task ReceiveRequestAsync(UdpClient udpClient) + { + // only proceed if there is available data in network buffer, or otherwise Receive() will block + // average time for UdpClient.Available : 10 us + + UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); + + NetworkReader reader = new NetworkReader(udpReceiveResult.Buffer); + + long handshake = reader.ReadInt64(); + if (handshake != secretHandshake) + { + // message is not for us + throw new ProtocolViolationException("Invalid handshake"); + } + + Request request = new Request(); + request.Deserialize(reader); + + ProcessClientRequest(request, udpReceiveResult.RemoteEndPoint); + } + + /// + /// Reply to the client to inform it of this server + /// + /// + /// Override if you wish to ignore server requests based on + /// custom criteria such as language, full server game mode or difficulty + /// + /// Request comming from client + /// Address of the client that sent the request + protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint) + { + Response info = ProcessRequest(request, endpoint); + + if (info == null) + return; + + NetworkWriter writer = NetworkWriterPool.GetWriter(); + + try + { + writer.WriteInt64(secretHandshake); + + info.Serialize(writer); + + ArraySegment data = writer.ToArraySegment(); + // signature matches + // send response + serverUdpClient.Send(data.Array, data.Count, endpoint); + } + catch (Exception ex) + { + Debug.LogException(ex, this); + } + finally + { + NetworkWriterPool.Recycle(writer); + } + } + + /// + /// Process the request from a client + /// + /// + /// Override if you wish to provide more information to the clients + /// such as the name of the host player + /// + /// Request comming from client + /// Address of the client that sent the request + /// The message to be sent back to the client or null + protected abstract Response ProcessRequest(Request request, IPEndPoint endpoint); + + #endregion + + #region Client + + /// + /// Start Active Discovery + /// + public void StartDiscovery() + { + if (!SupportedOnThisPlatform) + throw new PlatformNotSupportedException("Network discovery not supported in this platform"); + + StopDiscovery(); + + try + { + // Setup port + clientUdpClient = new UdpClient(0) + { + EnableBroadcast = true, + MulticastLoopback = false + }; + } + catch (Exception) + { + // Free the port if we took it + Shutdown(); + throw; + } + + _ = ClientListenAsync(); + + InvokeRepeating(nameof(BroadcastDiscoveryRequest), 0, ActiveDiscoveryInterval); + } + + /// + /// Start Active Discovery + /// + public void StopDiscovery() + { + Shutdown(); + } + + /// + /// Awaits for server response + /// + /// ClientListenAsync Task + public async Task ClientListenAsync() + { + while (true) + { + try + { + await ReceiveGameBroadcastAsync(clientUdpClient); + } + catch (ObjectDisposedException) + { + // socket was closed, no problem + return; + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + } + + /// + /// Sends discovery request from client + /// + public void BroadcastDiscoveryRequest() + { + if (clientUdpClient == null) + return; + + IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort); + + NetworkWriter writer = NetworkWriterPool.GetWriter(); + + writer.WriteInt64(secretHandshake); + + try + { + Request request = GetRequest(); + + request.Serialize(writer); + + ArraySegment data = writer.ToArraySegment(); + + clientUdpClient.SendAsync(data.Array, data.Count, endPoint); + } + catch (Exception) + { + // It is ok if we can't broadcast to one of the addresses + } + finally + { + NetworkWriterPool.Recycle(writer); + } + } + + /// + /// Create a message that will be broadcasted on the network to discover servers + /// + /// + /// Override if you wish to include additional data in the discovery message + /// such as desired game mode, language, difficulty, etc... + /// An instance of ServerRequest with data to be broadcasted + protected virtual Request GetRequest() => new Request(); + + async Task ReceiveGameBroadcastAsync(UdpClient udpClient) + { + // only proceed if there is available data in network buffer, or otherwise Receive() will block + // average time for UdpClient.Available : 10 us + + UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); + + NetworkReader reader = new NetworkReader(udpReceiveResult.Buffer); + + if (reader.ReadInt64() != secretHandshake) + return; + + Response response = new Response(); + response.Deserialize(reader); + + ProcessResponse(response, udpReceiveResult.RemoteEndPoint); + } + + /// + /// Process the answer from a server + /// + /// + /// A client receives a reply from a server, this method processes the + /// reply and raises an event + /// + /// Response that came from the server + /// Address of the server that replied + protected abstract void ProcessResponse(Response response, IPEndPoint endpoint); + + #endregion + } +} diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta new file mode 100644 index 000000000..7dfbaf62a --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9971d60ce61f4e39b07cd9e7e0c68fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs b/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs new file mode 100644 index 000000000..cfb53d686 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.Discovery +{ + [DisallowMultipleComponent] + [AddComponentMenu("Network/NetworkDiscoveryHUD")] + [HelpURL("https://mirror-networking.com/docs/Components/NetworkDiscovery.html")] + [RequireComponent(typeof(NetworkDiscovery))] + public class NetworkDiscoveryHUD : MonoBehaviour + { + readonly Dictionary discoveredServers = new Dictionary(); + Vector2 scrollViewPos = Vector2.zero; + + public NetworkDiscovery networkDiscovery; + +#if UNITY_EDITOR + void OnValidate() + { + if (networkDiscovery == null) + { + networkDiscovery = GetComponent(); + UnityEditor.Events.UnityEventTools.AddPersistentListener(networkDiscovery.OnServerFound, OnDiscoveredServer); + UnityEditor.Undo.RecordObjects(new Object[] { this, networkDiscovery }, "Set NetworkDiscovery"); + } + } +#endif + + void OnGUI() + { + if (NetworkManager.singleton == null) + return; + + if (NetworkServer.active || NetworkClient.active) + return; + + if (!NetworkClient.isConnected && !NetworkServer.active && !NetworkClient.active) + DrawGUI(); + } + + void DrawGUI() + { + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("Find Servers")) + { + discoveredServers.Clear(); + networkDiscovery.StartDiscovery(); + } + + // LAN Host + if (GUILayout.Button("Start Host")) + { + discoveredServers.Clear(); + NetworkManager.singleton.StartHost(); + networkDiscovery.AdvertiseServer(); + } + + // Dedicated server + if (GUILayout.Button("Start Server")) + { + discoveredServers.Clear(); + NetworkManager.singleton.StartServer(); + + networkDiscovery.AdvertiseServer(); + } + + GUILayout.EndHorizontal(); + + // show list of found server + + GUILayout.Label($"Discovered Servers [{discoveredServers.Count}]:"); + + // servers + scrollViewPos = GUILayout.BeginScrollView(scrollViewPos); + + foreach (ServerResponse info in discoveredServers.Values) + if (GUILayout.Button(info.EndPoint.Address.ToString())) + Connect(info); + + GUILayout.EndScrollView(); + } + + void Connect(ServerResponse info) + { + NetworkManager.singleton.StartClient(info.uri); + } + + public void OnDiscoveredServer(ServerResponse info) + { + // Note that you can check the versioning to decide if you can connect to the server or not using this method + discoveredServers[info.serverId] = info; + } + } +} diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta b/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta new file mode 100644 index 000000000..5e27a2282 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88c37d3deca7a834d80cfd8d3cfcc510 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/Discovery/ServerRequest.cs b/Assets/Mirror/Components/Discovery/ServerRequest.cs new file mode 100644 index 000000000..3ac59cfa4 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/ServerRequest.cs @@ -0,0 +1,4 @@ +namespace Mirror.Discovery +{ + public class ServerRequest : MessageBase { } +} diff --git a/Assets/Mirror/Components/Discovery/ServerRequest.cs.meta b/Assets/Mirror/Components/Discovery/ServerRequest.cs.meta new file mode 100644 index 000000000..84f3232a1 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/ServerRequest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea7254bf7b9454da4adad881d94cd141 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/Discovery/ServerResponse.cs b/Assets/Mirror/Components/Discovery/ServerResponse.cs new file mode 100644 index 000000000..34173e532 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/ServerResponse.cs @@ -0,0 +1,18 @@ +using System; +using System.Net; + +namespace Mirror.Discovery +{ + public class ServerResponse : MessageBase + { + // The server that sent this + // this is a property so that it is not serialized, but the + // client fills this up after we receive it + public IPEndPoint EndPoint { get; set; } + + public Uri uri; + + // Prevent duplicate server appearance when a connection can be made via LAN on multiple NICs + public long serverId; + } +} \ No newline at end of file diff --git a/Assets/Mirror/Components/Discovery/ServerResponse.cs.meta b/Assets/Mirror/Components/Discovery/ServerResponse.cs.meta new file mode 100644 index 000000000..44f23bac4 --- /dev/null +++ b/Assets/Mirror/Components/Discovery/ServerResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36f97227fdf2d7a4e902db5bfc43039c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Discovery.meta b/Assets/Mirror/Examples/Discovery.meta new file mode 100644 index 000000000..85a73ff3a --- /dev/null +++ b/Assets/Mirror/Examples/Discovery.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 450d6133608b04c57a6ebd6830d455fd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Discovery/Prefabs.meta b/Assets/Mirror/Examples/Discovery/Prefabs.meta new file mode 100644 index 000000000..ef0083dc9 --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8d8abc53a4efb4544ad9cb7a44b4840a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab b/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab new file mode 100644 index 000000000..2d917ed8e --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab @@ -0,0 +1,110 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &9081919128954505657 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8463701767414927392} + - component: {fileID: 435337138507318507} + - component: {fileID: 8589595951595565844} + - component: {fileID: 8188542106662419882} + - component: {fileID: 1410032569926419539} + m_Layer: 0 + m_Name: Player + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8463701767414927392 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9081919128954505657} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &435337138507318507 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9081919128954505657} + m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &8589595951595565844 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9081919128954505657} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 0 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 +--- !u!136 &8188542106662419882 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9081919128954505657} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + m_Radius: 0.5 + m_Height: 2 + m_Direction: 1 + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &1410032569926419539 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9081919128954505657} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + serverOnly: 0 + m_AssetId: + m_SceneId: 0 diff --git a/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab.meta b/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab.meta new file mode 100644 index 000000000..1c818fadc --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Prefabs/Player.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ecd52c53a6ef7496693343d3e32dace1 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Discovery/Scenes.meta b/Assets/Mirror/Examples/Discovery/Scenes.meta new file mode 100644 index 000000000..ed0ba6454 --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ceaf2344f4e6944258442667a9fbbfdf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity new file mode 100644 index 000000000..396aa9ee4 --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity @@ -0,0 +1,785 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &62199026 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 62199028} + - component: {fileID: 62199027} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &62199027 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 62199026} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &62199028 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 62199026} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &441913360 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 441913362} + - component: {fileID: 441913361} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &441913361 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 441913360} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &441913362 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 441913360} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -3.78, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &919124423 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 919124425} + - component: {fileID: 919124424} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &919124424 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919124423} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &919124425 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919124423} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.09, y: 0, z: -4.03} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &970214386 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 970214388} + - component: {fileID: 970214387} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &970214387 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 970214386} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &970214388 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 970214386} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.99, y: 0, z: -4.03} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1392889995 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1392889998} + - component: {fileID: 1392889997} + - component: {fileID: 1392889996} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1392889996 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392889995} + m_Enabled: 1 +--- !u!20 &1392889997 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392889995} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1392889998 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392889995} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1556883243 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1556883247} + - component: {fileID: 1556883245} + - component: {fileID: 1556883244} + - component: {fileID: 1556883248} + - component: {fileID: 1556883246} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1556883244 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1556883243} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3} + m_Name: + m_EditorClassIdentifier: + OnClientConnected: + m_PersistentCalls: + m_Calls: [] + OnClientDataReceived: + m_PersistentCalls: + m_Calls: [] + OnClientError: + m_PersistentCalls: + m_Calls: [] + OnClientDisconnected: + m_PersistentCalls: + m_Calls: [] + OnServerConnected: + m_PersistentCalls: + m_Calls: [] + OnServerDataReceived: + m_PersistentCalls: + m_Calls: [] + OnServerError: + m_PersistentCalls: + m_Calls: [] + OnServerDisconnected: + m_PersistentCalls: + m_Calls: [] + port: 7777 + NoDelay: 1 + serverMaxMessageSize: 16384 + clientMaxMessageSize: 16384 +--- !u!114 &1556883245 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1556883243} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8aab4c8111b7c411b9b92cf3dbc5bd4e, type: 3} + m_Name: + m_EditorClassIdentifier: + dontDestroyOnLoad: 1 + runInBackground: 1 + startOnHeadless: 1 + serverTickRate: 30 + showDebugMessages: 0 + offlineScene: + onlineScene: + transport: {fileID: 1556883244} + networkAddress: localhost + maxConnections: 4 + authenticator: {fileID: 0} + playerPrefab: {fileID: 9081919128954505657, guid: ecd52c53a6ef7496693343d3e32dace1, + type: 3} + autoCreatePlayer: 1 + playerSpawnMethod: 0 + spawnPrefabs: [] +--- !u!114 &1556883246 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1556883243} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 88c37d3deca7a834d80cfd8d3cfcc510, type: 3} + m_Name: + m_EditorClassIdentifier: + networkDiscovery: {fileID: 1556883248} +--- !u!4 &1556883247 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1556883243} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1556883248 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1556883243} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c761308e733c51245b2e8bb4201f46dc, type: 3} + m_Name: + m_EditorClassIdentifier: + secretHandshake: 1558261479176021378 + serverBroadcastListenPort: 47777 + ActiveDiscoveryInterval: 3 + transport: {fileID: 0} + OnServerFound: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1556883246} + m_MethodName: OnDiscoveredServer + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &1611696151 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1611696153} + - component: {fileID: 1611696152} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1611696152 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1611696151} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1611696153 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1611696151} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.6, y: 0, z: -1.43} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 9 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1730851146 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1730851148} + - component: {fileID: 1730851147} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1730851147 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1730851146} + m_Enabled: 1 + serializedVersion: 9 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.802082 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &1730851148 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1730851146} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &1911023976 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1911023978} + - component: {fileID: 1911023977} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1911023977 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1911023976} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1911023978 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1911023976} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -3.78, y: 0, z: -4.03} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1958729888 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1958729890} + - component: {fileID: 1958729889} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1958729889 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1958729888} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1958729890 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1958729888} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.6, y: 0, z: -4.08} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 10 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2054361114 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2054361116} + - component: {fileID: 2054361115} + m_Layer: 0 + m_Name: StartPos + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2054361115 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054361114} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &2054361116 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054361114} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.99, y: 0, z: -1.43} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity.meta b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity.meta new file mode 100644 index 000000000..b02433fdb --- /dev/null +++ b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 90fddc74fa21c423599167eb28b09dd1 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/FallbackTransport.cs b/Assets/Mirror/Runtime/Transport/FallbackTransport.cs index f9f4c77b9..a5194876d 100644 --- a/Assets/Mirror/Runtime/Transport/FallbackTransport.cs +++ b/Assets/Mirror/Runtime/Transport/FallbackTransport.cs @@ -175,5 +175,6 @@ public override string ToString() { return available.ToString(); } + } } diff --git a/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt b/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt new file mode 100644 index 000000000..01b8b1f31 --- /dev/null +++ b/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt @@ -0,0 +1,84 @@ +using System.Net; +using Mirror; +using Mirror.Discovery; + +/* + Discovery Guide: https://mirror-networking.com/docs/Guides/NetworkDiscovery.html + Documentation: https://mirror-networking.com/docs/Components/NetworkDiscovery.html + API Reference: https://mirror-networking.com/docs/api/Mirror.Discovery.NetworkDiscovery.html +*/ + +public class DiscoveryRequest : MessageBase +{ + // Add properties for whatever information you want sent by clients + // in their broadcast messages that servers will consume. +} + +public class DiscoveryResponse : MessageBase +{ + // Add properties for whatever information you want the server to return to + // clients for them to display or consume for establishing a connection. +} + +public class #SCRIPTNAME# : NetworkDiscoveryBase +{ + #region Server + + /// + /// Reply to the client to inform it of this server + /// + /// + /// Override if you wish to ignore server requests based on + /// custom criteria such as language, full server game mode or difficulty + /// + /// Request comming from client + /// Address of the client that sent the request + protected override void ProcessClientRequest(DiscoveryRequest request, IPEndPoint endpoint) + { + base.ProcessClientRequest(request, endpoint); + } + + /// + /// Process the request from a client + /// + /// + /// Override if you wish to provide more information to the clients + /// such as the name of the host player + /// + /// Request comming from client + /// Address of the client that sent the request + /// A message containing information about this server + protected override DiscoveryResponse ProcessRequest(DiscoveryRequest request, IPEndPoint endpoint) + { + return new DiscoveryResponse(); + } + + #endregion + + #region Client + + /// + /// Create a message that will be broadcasted on the network to discover servers + /// + /// + /// Override if you wish to include additional data in the discovery message + /// such as desired game mode, language, difficulty, etc... + /// An instance of ServerRequest with data to be broadcasted + protected override DiscoveryRequest GetRequest() + { + return new DiscoveryRequest(); + } + + /// + /// Process the answer from a server + /// + /// + /// A client receives a reply from a server, this method processes the + /// reply and raises an event + /// + /// Response that came from the server + /// Address of the server that replied + protected override void ProcessResponse(DiscoveryResponse response, IPEndPoint endpoint) { } + + #endregion +} diff --git a/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt.meta b/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt.meta new file mode 100644 index 000000000..a034ec865 --- /dev/null +++ b/Assets/ScriptTemplates/56-Mirror__Network Discovery-NewNetworkDiscovery.cs.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 04337367db30af3459bf9e9f3f880734 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/Components/NetworkDiscovery.md b/doc/Components/NetworkDiscovery.md new file mode 100644 index 000000000..764ef9a35 --- /dev/null +++ b/doc/Components/NetworkDiscovery.md @@ -0,0 +1,15 @@ +# NetworkDiscovery + +Network Discovery uses a UDP broadcast on the LAN enabling clients to find the running server and connect to it. + +![Inspector](NetworkDiscovery.png) + +NetworkDiscovery and NetworkDiscoveryHUD components are included, or you can make your own from a [ScriptTemplate](../General/ScriptTemplates.md). + +When a server is started, it listens on the UDP Broadcast Listen Port for requests from clients and returns a connection URI that clients apply to their transport. + +You can adjust how often the clients send their requests out to find a server in seconds with the Active Discovery Interval. + +The Server Found event must be assigned to a handler method, e.g. the OnDiscoveredServer method of NetworkDiscoveryHUD. + +In the NetworkDiscoveryHUD, the NetworkDiscovery component should be assigned automatically. diff --git a/doc/Components/NetworkDiscovery.png b/doc/Components/NetworkDiscovery.png new file mode 100644 index 000000000..5adbc166b Binary files /dev/null and b/doc/Components/NetworkDiscovery.png differ diff --git a/doc/Components/index.md b/doc/Components/index.md index e99eb8c67..f725d855c 100644 --- a/doc/Components/index.md +++ b/doc/Components/index.md @@ -10,6 +10,8 @@ These core components are included in Mirror: The Network Manager is a component for managing the networking aspects of a multiplayer game. - [NetworkManagerHUD](NetworkManagerHUD.md) The Network Manager HUD is a quick-start tool to help you start building your multiplayer game straight away, without first having to build a user interface for game creation/connection/joining. It allows you to jump straight into your gameplay programming, and means you can build your own version of these controls later in your development schedule. + [NetworkDiscovery](NetworkDiscovery.md) + Network Discovery uses a UDP broadcast on the LAN enabling clients to find the running server and connect to it. - [NetworkProximityChecker](NetworkProximityChecker.md) The Network Proximity Checker component controls the visibility of game objects for network clients, based on proximity to players. - [NetworkRoomManager](NetworkRoomManager.md) diff --git a/doc/Components/toc.yml b/doc/Components/toc.yml index 7a9f7c39a..19fc74588 100644 --- a/doc/Components/toc.yml +++ b/doc/Components/toc.yml @@ -8,6 +8,8 @@ href: NetworkManager.md - name: NetworkManagerHUD href: NetworkManagerHUD.md +- name: NetworkDiscovery + href: NetworkDiscovery.md - name: NetworkProximityChecker href: NetworkProximityChecker.md - name: NetworkSceneChecker diff --git a/doc/General/ChangeLog.md b/doc/General/ChangeLog.md index 0aba15311..7549c6798 100644 --- a/doc/General/ChangeLog.md +++ b/doc/General/ChangeLog.md @@ -4,7 +4,9 @@ - Added: NetworkAnimator now has a ResetTrigger function and server / client authority warnings - Added: NetworkTransform now has 3 new floats for Sensitivity to quiet down message traffic from micro changes. - Added: Network Observer added to [Script Templates](ScriptTemplates.md) -- See the new Mirror section in the Assets > Create menu. -- Added: [Network Scene Checker Component](../Components/NetworkSceneChecker.html) +- Added: [Network Discovery](../Components/NetworkDiscovery.md) has been reimplemented including an example and script template -- thanks to all those who contributed! +- Added: [Network Discovery](../Guides/NetworkDiscovery.md) Guide added to documentation +- Added: [Network Scene Checker Component](../Components/NetworkSceneChecker.md) - Added: Mirror Icon for all components - Added: URI added to supported data types - Fixed: NetworkTransform and NetworkAnimator now uses NetworkWriterPool diff --git a/doc/General/Deprecations.md b/doc/General/Deprecations.md index 0cde41ba9..0b7f78a0c 100644 --- a/doc/General/Deprecations.md +++ b/doc/General/Deprecations.md @@ -6,10 +6,6 @@ Certain features of Unity Networking were removed from Mirror for various reason As part of the Unity Services, this entire namespace was removed. It didn't work well to begin with, and was incredibly complex to be part of the core networking package. We expect this, along with other back-end services, will be provided through standalone apps that have integration to Mirror. -## Network Discovery - -NetworkDiscovery was a UNet component intended for UDP projects. Since Mirror was built on TCP, it was removed. Now that all [transports](../Transports/index.md) are separate components, Discovery has been re-implemented in at least one of them. - ## networkPort in Network Manager Network Manager's `networkPort` property was removed now that all transports are separate components. Not all transports use ports, but those that do have a field for it. See [Transports](../Transports/index.md) for more info. diff --git a/doc/General/ScriptTemplates.png b/doc/General/ScriptTemplates.png index 0d8062d9a..0c85ad446 100644 Binary files a/doc/General/ScriptTemplates.png and b/doc/General/ScriptTemplates.png differ diff --git a/doc/General/ScriptTemplates.zip b/doc/General/ScriptTemplates.zip index 55e013547..c270bc9bd 100644 Binary files a/doc/General/ScriptTemplates.zip and b/doc/General/ScriptTemplates.zip differ diff --git a/doc/Guides/NetworkDiscovery.md b/doc/Guides/NetworkDiscovery.md new file mode 100644 index 000000000..35d68b7e4 --- /dev/null +++ b/doc/Guides/NetworkDiscovery.md @@ -0,0 +1,107 @@ +# Network Discovery + +Suppose your are next to a friend. He starts a game in host mode and you want to join him. How will your phone locate his? Finding out his IP address is not exactly intuitive or something kids can do. + +To solve this problem you can use Network Discovery. When your game starts, it sends a message in your current network asking "Is there any server available?". Any server within the same network will reply and provide information about how to connect to it. + +Mirror comes with a simple implementation of Network Discovery you can simply use in your game. It also provides a way for you to extend it so that you can pass additional data during the discovery phase. + +![Inspector](../Components/NetworkDiscovery.png) + +## Quick Start + +To use Network Discovery follow these steps: + +1. Create a gameobject with a NetworkManager if you have not done so already +2. Do not add a NetworkManagerHUD. Discovery has a different UI component. +3. Add a NetworkDiscoveryHUD component to the NetworkManager gameobject. + A NetworkDiscovery component will be automatically added and wired up to your HUD. +4. Add a player to the NetworkManager if you have not done so. +5. Build and run a standalone version +6. Click on Start Host +7. Start play mode in the editor and click on Find Servers +8. The editor should find the standalone version and display a button +9. Click on the button to connect to it. + +The NetworkDiscoveryHUD is provided as a simple and quick way to get started, but you will probably want to replace it with your own user interface. + +## Custom Network Discovery + +You can completely replace the user interface by adding your own interface (typically Unity UI based) instead of the default NetworkDiscoveryHUD. You do still need the NetworkDiscovery component to do the heavy lifting. + +Sometimes you want to provide more information in the discovery messages. Some use cases could include: + +- The client can show if the server is in PvP or PvE mode +- The client can show how full the servers are. +- The client can show the ping to each server so the player can chose the fastest server +- The client can show the language +- The client can show if the server is password protected + +To do this, we've provided a [Template](../General/ScriptTemplates.md), so from the Assets menu, click Create > Mirror > Network Discovery. + +This will create a script in your project with 2 empty message classes and a custom NetworkDiscovery class that inherits from NetworkDiscoveryBase and has all the override methods included and documented for you. + +The message classes define what is sent between the client and server. As long as you keep your messages simple using the [data types](DataTypes.md) that Mirror can serialize, you won't need to write custom serializers for them. + +```cs +public class DiscoveryRequest : MessageBase +{ + public string language="en"; + + // Add properties for whatever information you want sent by clients + // in their broadcast messages that servers will consume. +} + +public class DiscoveryResponse : MessageBase +{ + enum GameMode {PvP, PvE}; + + // you probably want uri so clients know how to connect to the server + public Uri uri; + + public GameMode GameMode; + public int TotalPlayers; + public int HostPlayerName; + + // Add properties for whatever information you want the server to return to + // clients for them to display or consume for establishing a connection. +} +``` + +The custom NetworkDiscovery class contains the overrides for handling the messages above. + +You may want to refer to the NetworkDiscovery.cs script in the Components/Discovery folder to see how these should be implemented. + +```cs +public class NewNetworkDiscovery: NetworkDiscoveryBase +{ + #region Server + + protected override void ProcessClientRequest(DiscoveryRequest request, IPEndPoint endpoint) + { + base.ProcessClientRequest(request, endpoint); + } + + protected override DiscoveryResponse ProcessRequest(DiscoveryRequest request, IPEndPoint endpoint) + { + // TODO: Create your response and return it + return new DiscoveryResponse(); + } + + #endregion + + #region Client + + protected override DiscoveryRequest GetRequest() + { + return new DiscoveryRequest(); + } + + protected override void ProcessResponse(DiscoveryResponse response, IPEndPoint endpoint) + { + // TODO: a server replied, do something with the response such as invoking a unityevent + } + + #endregion +} +``` diff --git a/doc/Guides/toc.yml b/doc/Guides/toc.yml index a3343a7a6..f8d99ab63 100644 --- a/doc/Guides/toc.yml +++ b/doc/Guides/toc.yml @@ -8,6 +8,8 @@ href: ClientsServers.md - name: Authentication href: Authentication.md +- name: Network Discovery + href: NetworkDiscovery.md - name: Conversion href: Conversion.md - name: Network Profiler