Unity Project
This commit is contained in:
parent
ce7d98868c
commit
a6874d2d7f
1011 changed files with 105852 additions and 0 deletions
8
UnityProject/Assets/JsonDotNet.meta
Normal file
8
UnityProject/Assets/JsonDotNet.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07be693ce1685a54c9bc5608bf627573
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
UnityProject/Assets/JsonDotNet/Assemblies.meta
Normal file
9
UnityProject/Assets/JsonDotNet/Assemblies.meta
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 577d9725f58264943855b8ac185531fe
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788344
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
UnityProject/Assets/JsonDotNet/Assemblies/AOT.meta
Normal file
9
UnityProject/Assets/JsonDotNet/Assemblies/AOT.meta
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 14f21d7a1e53a8c4e87b25526a7eb63c
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788345
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8015
UnityProject/Assets/JsonDotNet/Assemblies/AOT/Newtonsoft.Json.XML
Normal file
8015
UnityProject/Assets/JsonDotNet/Assemblies/AOT/Newtonsoft.Json.XML
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: aadad8ac54f29e44583510294ac5c312
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,76 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6a3c684705042f345975d924f6983e36
|
||||
timeCreated: 1466788352
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
OSXIntel:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 1
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Tizen:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
WebGL:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
Win:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath: Assets/JsonDotNet/Assemblies/Standalone/Newtonsoft.Json.dll
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: Il2Cpp
|
||||
iOS:
|
||||
enabled: 1
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
tvOS:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01ef782d02bb1994dbe418b69432552b
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788344
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d6807fedb8dcaf04682d2c84f0ab753f
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,75 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 17aef65a15b471f468b5fbeb4ff0c6a1
|
||||
timeCreated: 1466788349
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86_64
|
||||
LinuxUniversal:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXUniversal:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 0
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Win:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath:
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: Il2Cpp
|
||||
iOS:
|
||||
enabled: 0
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
UnityProject/Assets/JsonDotNet/Assemblies/Windows.meta
Normal file
9
UnityProject/Assets/JsonDotNet/Assemblies/Windows.meta
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1418141139a6ac443b18cb05c0643a29
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788345
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36f7323c55864364d8bb88c736e4bca6
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,67 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9b6ba260dada0ea4a871a42011f8b87d
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
OSXIntel:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 0
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Win:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath: Assets/JsonDotNet/Assemblies/Standalone/Newtonsoft.Json.dll
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: DotNet
|
||||
iOS:
|
||||
enabled: 0
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
UnityProject/Assets/JsonDotNet/Documentation.meta
Normal file
9
UnityProject/Assets/JsonDotNet/Documentation.meta
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 76f828f47ce26cc43991113c6a39dbbf
|
||||
folderAsset: yes
|
||||
timeCreated: 1466010535
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e7d9a07cc3f02a41a575406e7230846
|
||||
timeCreated: 1466788421
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
UnityProject/Assets/JsonDotNet/JsonDotNet201Source.zip
Normal file
BIN
UnityProject/Assets/JsonDotNet/JsonDotNet201Source.zip
Normal file
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9a6f8c7c1ea72ce46831c5e1b6150d0c
|
||||
timeCreated: 1466790933
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
UnityProject/Assets/JsonDotNet/link.xml
Normal file
7
UnityProject/Assets/JsonDotNet/link.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<linker>
|
||||
<assembly fullname="System">
|
||||
<type fullname="System.ComponentModel.TypeConverter" preserve="all" />
|
||||
<!-- <namespace fullname="System.ComponentModel" preserve="all" /> -->
|
||||
</assembly>
|
||||
</linker>
|
||||
6
UnityProject/Assets/JsonDotNet/link.xml.meta
Normal file
6
UnityProject/Assets/JsonDotNet/link.xml.meta
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 06314f49bdda26043963578d60a0a7ee
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1923
UnityProject/Assets/LRMTestScene.unity
Normal file
1923
UnityProject/Assets/LRMTestScene.unity
Normal file
File diff suppressed because it is too large
Load diff
7
UnityProject/Assets/LRMTestScene.unity.meta
Normal file
7
UnityProject/Assets/LRMTestScene.unity.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e860d5862486efc438d11e20bc7660aa
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
UnityProject/Assets/LRMTester.cs
Normal file
50
UnityProject/Assets/LRMTester.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Mirror;
|
||||
using Newtonsoft.Json;
|
||||
using LightReflectiveMirror;
|
||||
|
||||
public class LRMTester : MonoBehaviour
|
||||
{
|
||||
public Transform serverListParent;
|
||||
public GameObject serverListEntry;
|
||||
|
||||
private LightReflectiveMirrorTransport _LRM;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (_LRM == null)
|
||||
{
|
||||
_LRM = (LightReflectiveMirrorTransport)Transport.activeTransport;
|
||||
_LRM.serverListUpdated.AddListener(OnServerListUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (_LRM != null)
|
||||
_LRM.serverListUpdated.RemoveListener(OnServerListUpdated);
|
||||
}
|
||||
|
||||
public void RequestServerList()
|
||||
{
|
||||
_LRM.RequestServerList();
|
||||
}
|
||||
|
||||
public void OnServerListUpdated()
|
||||
{
|
||||
foreach (Transform t in serverListParent)
|
||||
Destroy(t.gameObject);
|
||||
|
||||
for(int i = 0; i < _LRM.relayServerList.Count; i++)
|
||||
{
|
||||
var serverEntry = Instantiate(serverListEntry, serverListParent);
|
||||
|
||||
serverEntry.transform.GetChild(0).GetComponent<Text>().text = $"{_LRM.relayServerList[i].serverName + " - " + JsonConvert.SerializeObject(_LRM.relayServerList[i].relayInfo)}";
|
||||
int serverID = _LRM.relayServerList[i].serverId;
|
||||
serverEntry.GetComponent<Button>().onClick.AddListener(() => { NetworkManager.singleton.networkAddress = serverID.ToString(); NetworkManager.singleton.StartClient(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/LRMTester.cs.meta
Normal file
11
UnityProject/Assets/LRMTester.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a2a72ca69937b3c45a212b89be668a79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror.meta
Normal file
8
UnityProject/Assets/Mirror.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3b4a06bd4467a4347806d66e367e3b69
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Authenticators.meta
Normal file
8
UnityProject/Assets/Mirror/Authenticators.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b2f9d254154cd942ba40b06b869b8f3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
183
UnityProject/Assets/Mirror/Authenticators/BasicAuthenticator.cs
Normal file
183
UnityProject/Assets/Mirror/Authenticators/BasicAuthenticator.cs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Authenticators
|
||||
{
|
||||
[AddComponentMenu("Network/Authenticators/BasicAuthenticator")]
|
||||
public class BasicAuthenticator : NetworkAuthenticator
|
||||
{
|
||||
[Header("Custom Properties")]
|
||||
|
||||
// set these in the inspector
|
||||
public string username;
|
||||
public string password;
|
||||
|
||||
#region Messages
|
||||
|
||||
public struct AuthRequestMessage : NetworkMessage
|
||||
{
|
||||
// use whatever credentials make sense for your game
|
||||
// for example, you might want to pass the accessToken if using oauth
|
||||
public string authUsername;
|
||||
public string authPassword;
|
||||
}
|
||||
|
||||
public struct AuthResponseMessage : NetworkMessage
|
||||
{
|
||||
public byte code;
|
||||
public string message;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Server
|
||||
|
||||
/// <summary>
|
||||
/// Called on server from StartServer to initialize the Authenticator
|
||||
/// <para>Server message handlers should be registered in this method.</para>
|
||||
/// </summary>
|
||||
public override void OnStartServer()
|
||||
{
|
||||
// register a handler for the authentication request we expect from client
|
||||
NetworkServer.RegisterHandler<AuthRequestMessage>(OnAuthRequestMessage, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on server from StopServer to reset the Authenticator
|
||||
/// <para>Server message handlers should be registered in this method.</para>
|
||||
/// </summary>
|
||||
public override void OnStopServer()
|
||||
{
|
||||
// unregister the handler for the authentication request
|
||||
NetworkServer.UnregisterHandler<AuthRequestMessage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on server from OnServerAuthenticateInternal when a client needs to authenticate
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection to client.</param>
|
||||
public override void OnServerAuthenticate(NetworkConnection conn)
|
||||
{
|
||||
// do nothing...wait for AuthRequestMessage from client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on server when the client's AuthRequestMessage arrives
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection to client.</param>
|
||||
/// <param name="msg">The message payload</param>
|
||||
public void OnAuthRequestMessage(NetworkConnection conn, AuthRequestMessage msg)
|
||||
{
|
||||
// Debug.LogFormat(LogType.Log, "Authentication Request: {0} {1}", msg.authUsername, msg.authPassword);
|
||||
|
||||
// check the credentials by calling your web server, database table, playfab api, or any method appropriate.
|
||||
if (msg.authUsername == username && msg.authPassword == password)
|
||||
{
|
||||
// create and send msg to client so it knows to proceed
|
||||
AuthResponseMessage authResponseMessage = new AuthResponseMessage
|
||||
{
|
||||
code = 100,
|
||||
message = "Success"
|
||||
};
|
||||
|
||||
conn.Send(authResponseMessage);
|
||||
|
||||
// Accept the successful authentication
|
||||
ServerAccept(conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// create and send msg to client so it knows to disconnect
|
||||
AuthResponseMessage authResponseMessage = new AuthResponseMessage
|
||||
{
|
||||
code = 200,
|
||||
message = "Invalid Credentials"
|
||||
};
|
||||
|
||||
conn.Send(authResponseMessage);
|
||||
|
||||
// must set NetworkConnection isAuthenticated = false
|
||||
conn.isAuthenticated = false;
|
||||
|
||||
// disconnect the client after 1 second so that response message gets delivered
|
||||
StartCoroutine(DelayedDisconnect(conn, 1));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator DelayedDisconnect(NetworkConnection conn, float waitTime)
|
||||
{
|
||||
yield return new WaitForSeconds(waitTime);
|
||||
|
||||
// Reject the unsuccessful authentication
|
||||
ServerReject(conn);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client
|
||||
|
||||
/// <summary>
|
||||
/// Called on client from StartClient to initialize the Authenticator
|
||||
/// <para>Client message handlers should be registered in this method.</para>
|
||||
/// </summary>
|
||||
public override void OnStartClient()
|
||||
{
|
||||
// register a handler for the authentication response we expect from server
|
||||
NetworkClient.RegisterHandler<AuthResponseMessage>((Action<AuthResponseMessage>)OnAuthResponseMessage, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on client from StopClient to reset the Authenticator
|
||||
/// <para>Client message handlers should be unregistered in this method.</para>
|
||||
/// </summary>
|
||||
public override void OnStopClient()
|
||||
{
|
||||
// unregister the handler for the authentication response
|
||||
NetworkClient.UnregisterHandler<AuthResponseMessage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on client from OnClientAuthenticateInternal when a client needs to authenticate
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection of the client.</param>
|
||||
public override void OnClientAuthenticate(NetworkConnection conn)
|
||||
{
|
||||
AuthRequestMessage authRequestMessage = new AuthRequestMessage
|
||||
{
|
||||
authUsername = username,
|
||||
authPassword = password
|
||||
};
|
||||
|
||||
conn.Send(authRequestMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on client when the server's AuthResponseMessage arrives
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection to client.</param>
|
||||
/// <param name="msg">The message payload</param>
|
||||
public void OnAuthResponseMessage(AuthResponseMessage msg)
|
||||
{
|
||||
if (msg.code == 100)
|
||||
{
|
||||
// Debug.LogFormat(LogType.Log, "Authentication Response: {0}", msg.message);
|
||||
|
||||
// Authentication has been accepted
|
||||
ClientAccept(NetworkClient.connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Authentication Response: {msg.message}");
|
||||
|
||||
// Authentication has been rejected
|
||||
ClientReject(NetworkClient.connection);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Call OnAuthResponseMessage without the NetworkConnection parameter. It always points to NetworkClient.connection anyway.")]
|
||||
public void OnAuthResponseMessage(NetworkConnection conn, AuthResponseMessage msg) => OnAuthResponseMessage(msg);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 28496b776660156428f00cf78289c1ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Mirror.Authenticators",
|
||||
"references": [
|
||||
"Mirror"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e720aa64e3f58fb4880566a322584340
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Authenticators
|
||||
{
|
||||
/// <summary>
|
||||
/// An authenticator that disconnects connections if they don't
|
||||
/// authenticate within a specified time limit.
|
||||
/// </summary>
|
||||
[AddComponentMenu("Network/Authenticators/TimeoutAuthenticator")]
|
||||
public class TimeoutAuthenticator : NetworkAuthenticator
|
||||
{
|
||||
public NetworkAuthenticator authenticator;
|
||||
|
||||
[Range(0, 600), Tooltip("Timeout to auto-disconnect in seconds. Set to 0 for no timeout.")]
|
||||
public float timeout = 60;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
authenticator.OnServerAuthenticated.AddListener(connection => OnServerAuthenticated.Invoke(connection));
|
||||
authenticator.OnClientAuthenticated.AddListener(connection => OnClientAuthenticated.Invoke(connection));
|
||||
}
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
authenticator.OnStartServer();
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
authenticator.OnStopServer();
|
||||
}
|
||||
|
||||
public override void OnStartClient()
|
||||
{
|
||||
authenticator.OnStartClient();
|
||||
}
|
||||
|
||||
public override void OnStopClient()
|
||||
{
|
||||
authenticator.OnStopClient();
|
||||
}
|
||||
|
||||
public override void OnServerAuthenticate(NetworkConnection conn)
|
||||
{
|
||||
authenticator.OnServerAuthenticate(conn);
|
||||
if (timeout > 0)
|
||||
StartCoroutine(BeginAuthentication(conn));
|
||||
}
|
||||
|
||||
public override void OnClientAuthenticate(NetworkConnection conn)
|
||||
{
|
||||
authenticator.OnClientAuthenticate(conn);
|
||||
if (timeout > 0)
|
||||
StartCoroutine(BeginAuthentication(conn));
|
||||
}
|
||||
|
||||
IEnumerator BeginAuthentication(NetworkConnection conn)
|
||||
{
|
||||
// Debug.Log($"Authentication countdown started {conn} {timeout}");
|
||||
|
||||
yield return new WaitForSecondsRealtime(timeout);
|
||||
|
||||
if (!conn.isAuthenticated)
|
||||
{
|
||||
// Debug.Log($"Authentication Timeout {conn}");
|
||||
|
||||
conn.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 24d8269a07b8e4edfa374753a91c946e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Cloud.meta
Normal file
8
UnityProject/Assets/Mirror/Cloud.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 73a9bb2dacafa8141bce8feef34e33a7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
61
UnityProject/Assets/Mirror/Cloud/ApiConnector.cs
Normal file
61
UnityProject/Assets/Mirror/Cloud/ApiConnector.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using Mirror.Cloud.ListServerService;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to requests and responses from the mirror api
|
||||
/// </summary>
|
||||
public interface IApiConnector
|
||||
{
|
||||
ListServer ListServer { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to requests and responses from the mirror api
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/CloudServices/ApiConnector")]
|
||||
[HelpURL("https://mirror-networking.com/docs/api/Mirror.Cloud.ApiConnector.html")]
|
||||
public class ApiConnector : MonoBehaviour, IApiConnector, ICoroutineRunner
|
||||
{
|
||||
#region Inspector
|
||||
[Header("Settings")]
|
||||
|
||||
[Tooltip("Base URL of api, including https")]
|
||||
[SerializeField] string ApiAddress = "";
|
||||
|
||||
[Tooltip("Api key required to access api")]
|
||||
[SerializeField] string ApiKey = "";
|
||||
|
||||
[Header("Events")]
|
||||
|
||||
[Tooltip("Triggered when server list updates")]
|
||||
[SerializeField] ServerListEvent _onServerListUpdated = new ServerListEvent();
|
||||
#endregion
|
||||
|
||||
IRequestCreator requestCreator;
|
||||
|
||||
public ListServer ListServer { get; private set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
requestCreator = new RequestCreator(ApiAddress, ApiKey, this);
|
||||
|
||||
InitListServer();
|
||||
}
|
||||
|
||||
void InitListServer()
|
||||
{
|
||||
IListServerServerApi serverApi = new ListServerServerApi(this, requestCreator);
|
||||
IListServerClientApi clientApi = new ListServerClientApi(this, requestCreator, _onServerListUpdated);
|
||||
ListServer = new ListServer(serverApi, clientApi);
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
ListServer?.ServerApi.Shutdown();
|
||||
ListServer?.ClientApi.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/ApiConnector.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/ApiConnector.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8bdb99a29e179d14cb0acc43f175d9ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Cloud/Core.meta
Normal file
8
UnityProject/Assets/Mirror/Cloud/Core.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3f34c32971e65984c93a15376ec11c65
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
UnityProject/Assets/Mirror/Cloud/Core/BaseApi.cs
Normal file
25
UnityProject/Assets/Mirror/Cloud/Core/BaseApi.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
public interface IBaseApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Cleans up any data created by the instance
|
||||
/// <para>For Example: removing server from list</para>
|
||||
/// </summary>
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
public abstract class BaseApi
|
||||
{
|
||||
protected readonly ICoroutineRunner runner;
|
||||
protected readonly IRequestCreator requestCreator;
|
||||
|
||||
protected BaseApi(ICoroutineRunner runner, IRequestCreator requestCreator)
|
||||
{
|
||||
this.runner = runner ?? throw new ArgumentNullException(nameof(runner));
|
||||
this.requestCreator = requestCreator ?? throw new ArgumentNullException(nameof(requestCreator));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/BaseApi.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/BaseApi.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 70f563b7a7210ae43bbcde5cb7721a94
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
UnityProject/Assets/Mirror/Cloud/Core/Events.cs
Normal file
12
UnityProject/Assets/Mirror/Cloud/Core/Events.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using Mirror.Cloud.ListServerService;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
[Serializable]
|
||||
public class ServerListEvent : UnityEvent<ServerCollectionJson> {}
|
||||
|
||||
[Serializable]
|
||||
public class MatchFoundEvent : UnityEvent<ServerJson> {}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/Events.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/Events.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7c472a3ea1bc4348bd5a0b05bf7cc3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
UnityProject/Assets/Mirror/Cloud/Core/Extensions.cs
Normal file
12
UnityProject/Assets/Mirror/Cloud/Core/Extensions.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static bool IsOk(this UnityWebRequest webRequest)
|
||||
{
|
||||
return 200 <= webRequest.responseCode && webRequest.responseCode <= 299;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/Extensions.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/Extensions.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 97501e783fc67a4459b15d10e6c63563
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
UnityProject/Assets/Mirror/Cloud/Core/ICoroutineRunner.cs
Normal file
12
UnityProject/Assets/Mirror/Cloud/Core/ICoroutineRunner.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
public interface ICoroutineRunner : IUnityEqualCheck
|
||||
{
|
||||
Coroutine StartCoroutine(IEnumerator routine);
|
||||
void StopCoroutine(IEnumerator routine);
|
||||
void StopCoroutine(Coroutine routine);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 43472c60a7c72e54eafe559290dd0fc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
UnityProject/Assets/Mirror/Cloud/Core/IRequestCreator.cs
Normal file
42
UnityProject/Assets/Mirror/Cloud/Core/IRequestCreator.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
public delegate void RequestSuccess(string responseBody);
|
||||
|
||||
public delegate void RequestFail(string responseBody);
|
||||
|
||||
/// <summary>
|
||||
/// Objects that can be sent to the Api must have this interface
|
||||
/// </summary>
|
||||
public interface ICanBeJson {}
|
||||
|
||||
/// <summary>
|
||||
/// Methods to create and send UnityWebRequest
|
||||
/// </summary>
|
||||
public interface IRequestCreator
|
||||
{
|
||||
UnityWebRequest Delete(string page);
|
||||
UnityWebRequest Get(string page);
|
||||
UnityWebRequest Patch<T>(string page, T json) where T : struct, ICanBeJson;
|
||||
UnityWebRequest Post<T>(string page, T json) where T : struct, ICanBeJson;
|
||||
|
||||
/// <summary>
|
||||
/// Sends Request to api and invokes callback when finished
|
||||
/// <para>Starts Coroutine of SendRequestEnumerator</para>
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="onSuccess"></param>
|
||||
/// <param name="onFail"></param>
|
||||
void SendRequest(UnityWebRequest request, RequestSuccess onSuccess = null, RequestFail onFail = null);
|
||||
/// <summary>
|
||||
/// Sends Request to api and invokes callback when finished
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="onSuccess"></param>
|
||||
/// <param name="onFail"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerator SendRequestEnumerator(UnityWebRequest request, RequestSuccess onSuccess = null, RequestFail onFail = null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b80b95532a9d6e8418aa676a261e4f69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
UnityProject/Assets/Mirror/Cloud/Core/IUnityEqualCheck.cs
Normal file
26
UnityProject/Assets/Mirror/Cloud/Core/IUnityEqualCheck.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Extension to check if unity object is null.
|
||||
/// <para>Use these methods to stop MissingReferenceException</para>
|
||||
/// </summary>
|
||||
public interface IUnityEqualCheck
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static class UnityEqualCheckExtension
|
||||
{
|
||||
public static bool IsNull(this IUnityEqualCheck obj)
|
||||
{
|
||||
return (obj as Object) == null;
|
||||
}
|
||||
|
||||
public static bool IsNotNull(this IUnityEqualCheck obj)
|
||||
{
|
||||
return (obj as Object) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 05185b973ba389a4588fc8a99c75a4f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
UnityProject/Assets/Mirror/Cloud/Core/JsonStructs.cs
Normal file
24
UnityProject/Assets/Mirror/Cloud/Core/JsonStructs.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
[Serializable]
|
||||
public struct CreatedIdJson : ICanBeJson
|
||||
{
|
||||
public string id;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct ErrorJson : ICanBeJson
|
||||
{
|
||||
public string code;
|
||||
public string message;
|
||||
|
||||
public int HtmlCode => int.Parse(code);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct EmptyJson : ICanBeJson
|
||||
{
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/JsonStructs.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/JsonStructs.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0688c0fdae5376e4ea74d5c3904eed17
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
UnityProject/Assets/Mirror/Cloud/Core/Logger.cs
Normal file
62
UnityProject/Assets/Mirror/Cloud/Core/Logger.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
public static bool VerboseLogging = false;
|
||||
|
||||
public static void LogRequest(string page, string method, bool hasJson, string json)
|
||||
{
|
||||
if (hasJson)
|
||||
{
|
||||
Debug.LogFormat("Request: {0} {1} {2}", method, page, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogFormat("Request: {0} {1}", method, page);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogResponse(UnityWebRequest statusRequest)
|
||||
{
|
||||
long code = statusRequest.responseCode;
|
||||
|
||||
string format = "Response: {0} {1} {2} {3}";
|
||||
// we split path like this to make sure api key doesn't leak
|
||||
Uri uri = new Uri(statusRequest.url);
|
||||
string path = string.Join("", uri.Segments);
|
||||
string msg = string.Format(format, statusRequest.method, code, path, statusRequest.downloadHandler.text);
|
||||
Debug.Log(msg);
|
||||
|
||||
if (!string.IsNullOrEmpty(statusRequest.error))
|
||||
{
|
||||
msg = string.Format("WEB REQUEST ERROR: {0}", statusRequest.error);
|
||||
Debug.LogError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Log(string msg)
|
||||
{
|
||||
Debug.Log(msg);
|
||||
}
|
||||
|
||||
internal static void LogWarning(string msg)
|
||||
{
|
||||
Debug.LogWarning(msg);
|
||||
}
|
||||
|
||||
internal static void LogError(string msg)
|
||||
{
|
||||
Debug.LogError(msg);
|
||||
}
|
||||
|
||||
internal static void Verbose(string msg)
|
||||
{
|
||||
if (VerboseLogging)
|
||||
Debug.Log(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/Logger.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/Logger.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 457ba2df6cb6e1542996c17c715ee81b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
144
UnityProject/Assets/Mirror/Cloud/Core/RequestCreator.cs
Normal file
144
UnityProject/Assets/Mirror/Cloud/Core/RequestCreator.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to create and send UnityWebRequest
|
||||
/// </summary>
|
||||
public class RequestCreator : IRequestCreator
|
||||
{
|
||||
const string GET = "GET";
|
||||
const string POST = "POST";
|
||||
const string PATCH = "PATCH";
|
||||
const string DELETE = "DELETE";
|
||||
|
||||
public readonly string baseAddress;
|
||||
public readonly string apiKey;
|
||||
readonly ICoroutineRunner runner;
|
||||
|
||||
public RequestCreator(string baseAddress, string apiKey, ICoroutineRunner coroutineRunner)
|
||||
{
|
||||
if (string.IsNullOrEmpty(baseAddress))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(baseAddress));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(apiKey));
|
||||
}
|
||||
|
||||
this.baseAddress = baseAddress;
|
||||
this.apiKey = apiKey;
|
||||
|
||||
runner = coroutineRunner ?? throw new ArgumentNullException(nameof(coroutineRunner));
|
||||
}
|
||||
|
||||
|
||||
Uri CreateUri(string page)
|
||||
{
|
||||
return new Uri(string.Format("{0}/{1}?key={2}", baseAddress, page, apiKey));
|
||||
}
|
||||
|
||||
UnityWebRequest CreateWebRequest(string page, string method, string json = null)
|
||||
{
|
||||
bool hasJson = !string.IsNullOrEmpty(json);
|
||||
Logger.LogRequest(page, method, hasJson, json);
|
||||
|
||||
UnityWebRequest request = new UnityWebRequest(CreateUri(page));
|
||||
request.method = method;
|
||||
if (hasJson)
|
||||
{
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
|
||||
byte[] bodyRaw = hasJson
|
||||
? Encoding.UTF8.GetBytes(json)
|
||||
: null;
|
||||
|
||||
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create Get Request to page
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
public UnityWebRequest Get(string page)
|
||||
{
|
||||
return CreateWebRequest(page, GET);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Post Request to page with Json body
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
public UnityWebRequest Post<T>(string page, T json) where T : struct, ICanBeJson
|
||||
{
|
||||
string jsonString = JsonUtility.ToJson(json);
|
||||
return CreateWebRequest(page, POST, jsonString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Patch Request to page with Json body
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
public UnityWebRequest Patch<T>(string page, T json) where T : struct, ICanBeJson
|
||||
{
|
||||
string jsonString = JsonUtility.ToJson(json);
|
||||
return CreateWebRequest(page, PATCH, jsonString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Delete Request to page
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
public UnityWebRequest Delete(string page)
|
||||
{
|
||||
return CreateWebRequest(page, DELETE);
|
||||
}
|
||||
|
||||
|
||||
public void SendRequest(UnityWebRequest request, RequestSuccess onSuccess = null, RequestFail onFail = null)
|
||||
{
|
||||
runner.StartCoroutine(SendRequestEnumerator(request, onSuccess, onFail));
|
||||
}
|
||||
|
||||
public IEnumerator SendRequestEnumerator(UnityWebRequest request, RequestSuccess onSuccess = null, RequestFail onFail = null)
|
||||
{
|
||||
using (UnityWebRequest webRequest = request)
|
||||
{
|
||||
yield return webRequest.SendWebRequest();
|
||||
Logger.LogResponse(webRequest);
|
||||
|
||||
string text = webRequest.downloadHandler.text;
|
||||
Logger.Verbose(text);
|
||||
if (webRequest.IsOk())
|
||||
{
|
||||
onSuccess?.Invoke(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
onFail?.Invoke(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
UnityProject/Assets/Mirror/Cloud/Core/RequestCreator.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Cloud/Core/RequestCreator.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cfaa626443cc7c94eae138a2e3a04d7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Cloud/ListServer.meta
Normal file
8
UnityProject/Assets/Mirror/Cloud/ListServer.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c4c4be148a492b143a881cd08bf7e320
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
66
UnityProject/Assets/Mirror/Cloud/ListServer/ListServer.cs
Normal file
66
UnityProject/Assets/Mirror/Cloud/ListServer/ListServer.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Mirror.Cloud.ListServerService
|
||||
{
|
||||
public sealed class ListServer
|
||||
{
|
||||
public readonly IListServerServerApi ServerApi;
|
||||
public readonly IListServerClientApi ClientApi;
|
||||
|
||||
public ListServer(IListServerServerApi serverApi, IListServerClientApi clientApi)
|
||||
{
|
||||
ServerApi = serverApi ?? throw new ArgumentNullException(nameof(serverApi));
|
||||
ClientApi = clientApi ?? throw new ArgumentNullException(nameof(clientApi));
|
||||
}
|
||||
}
|
||||
|
||||
public interface IListServerServerApi : IBaseApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Has a server been added to the list with this connection
|
||||
/// </summary>
|
||||
bool ServerInList { get; }
|
||||
/// <summary>
|
||||
/// Add a server to the list
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
void AddServer(ServerJson server);
|
||||
/// <summary>
|
||||
/// Update the current server
|
||||
/// </summary>
|
||||
/// <param name="newPlayerCount"></param>
|
||||
void UpdateServer(int newPlayerCount);
|
||||
/// <summary>
|
||||
/// Update the current server
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
void UpdateServer(ServerJson server);
|
||||
/// <summary>
|
||||
/// Removes the current server
|
||||
/// </summary>
|
||||
void RemoveServer();
|
||||
}
|
||||
|
||||
public interface IListServerClientApi : IBaseApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the server list is updated
|
||||
/// </summary>
|
||||
event UnityAction<ServerCollectionJson> onServerListUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Get the server list once
|
||||
/// </summary>
|
||||
void GetServerList();
|
||||
/// <summary>
|
||||
/// Start getting the server list every interval
|
||||
/// </summary>
|
||||
/// <param name="interval"></param>
|
||||
void StartGetServerListRepeat(int interval);
|
||||
/// <summary>
|
||||
/// Stop getting the server list
|
||||
/// </summary>
|
||||
void StopGetServerListRepeat();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f0311899162c5b49a3c11fa9bd9c133
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Mirror.Cloud.ListServerService
|
||||
{
|
||||
public abstract class ListServerBaseApi : BaseApi
|
||||
{
|
||||
protected ListServerBaseApi(ICoroutineRunner runner, IRequestCreator requestCreator) : base(runner, requestCreator)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b6838f9df45594d48873518cbb75b329
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud.ListServerService
|
||||
{
|
||||
public sealed class ListServerClientApi : ListServerBaseApi, IListServerClientApi
|
||||
{
|
||||
readonly ServerListEvent _onServerListUpdated;
|
||||
|
||||
Coroutine getServerListRepeatCoroutine;
|
||||
|
||||
public event UnityAction<ServerCollectionJson> onServerListUpdated
|
||||
{
|
||||
add => _onServerListUpdated.AddListener(value);
|
||||
remove => _onServerListUpdated.RemoveListener(value);
|
||||
}
|
||||
|
||||
public ListServerClientApi(ICoroutineRunner runner, IRequestCreator requestCreator, ServerListEvent onServerListUpdated) : base(runner, requestCreator)
|
||||
{
|
||||
_onServerListUpdated = onServerListUpdated;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
StopGetServerListRepeat();
|
||||
}
|
||||
|
||||
public void GetServerList()
|
||||
{
|
||||
runner.StartCoroutine(getServerList());
|
||||
}
|
||||
|
||||
public void StartGetServerListRepeat(int interval)
|
||||
{
|
||||
getServerListRepeatCoroutine = runner.StartCoroutine(GetServerListRepeat(interval));
|
||||
}
|
||||
|
||||
public void StopGetServerListRepeat()
|
||||
{
|
||||
// if runner is null it has been destroyed and will already be null
|
||||
if (runner.IsNotNull() && getServerListRepeatCoroutine != null)
|
||||
{
|
||||
runner.StopCoroutine(getServerListRepeatCoroutine);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator GetServerListRepeat(int interval)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return getServerList();
|
||||
|
||||
yield return new WaitForSeconds(interval);
|
||||
}
|
||||
}
|
||||
IEnumerator getServerList()
|
||||
{
|
||||
UnityWebRequest request = requestCreator.Get("servers");
|
||||
yield return requestCreator.SendRequestEnumerator(request, onSuccess);
|
||||
|
||||
void onSuccess(string responseBody)
|
||||
{
|
||||
ServerCollectionJson serverlist = JsonUtility.FromJson<ServerCollectionJson>(responseBody);
|
||||
_onServerListUpdated?.Invoke(serverlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d49649fb32cb96b46b10f013b38a4b50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
207
UnityProject/Assets/Mirror/Cloud/ListServer/ListServerJson.cs
Normal file
207
UnityProject/Assets/Mirror/Cloud/ListServer/ListServerJson.cs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mirror.Cloud.ListServerService
|
||||
{
|
||||
[Serializable]
|
||||
public struct ServerCollectionJson : ICanBeJson
|
||||
{
|
||||
public ServerJson[] servers;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct ServerJson : ICanBeJson
|
||||
{
|
||||
public string protocol;
|
||||
public int port;
|
||||
public int playerCount;
|
||||
public int maxPlayerCount;
|
||||
|
||||
/// <summary>
|
||||
/// optional
|
||||
/// </summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>
|
||||
/// Uri string of the ip and port of the server.
|
||||
/// <para>The ip is calculated by the request to the API</para>
|
||||
/// <para>This is returns from the api, any incoming address fields will be ignored</para>
|
||||
/// </summary>
|
||||
public string address;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to set custom uri
|
||||
/// <para>optional</para>
|
||||
/// </summary>
|
||||
public string customAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Array of custom data, use SetCustomData to set values
|
||||
/// <para>optional</para>
|
||||
/// </summary>
|
||||
public KeyValue[] customData;
|
||||
|
||||
/// <summary>
|
||||
/// Uri from address field
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Uri GetServerUri() => new Uri(address);
|
||||
|
||||
/// <summary>
|
||||
/// Uri from customAddress field
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Uri GetCustomUri() => new Uri(customAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the customData array
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void SetCustomData(Dictionary<string, string> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
customData = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
customData = data.ToKeyValueArray();
|
||||
CustomDataHelper.ValidateCustomData(customData);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
CustomDataHelper.ValidateCustomData(customData);
|
||||
|
||||
if (string.IsNullOrEmpty(protocol))
|
||||
{
|
||||
Logger.LogError("ServerJson should not have empty protocol");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port == 0)
|
||||
{
|
||||
Logger.LogError("ServerJson should not have port equal 0");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxPlayerCount == 0)
|
||||
{
|
||||
Logger.LogError("ServerJson should not have maxPlayerCount equal 0");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct PartialServerJson : ICanBeJson
|
||||
{
|
||||
/// <summary>
|
||||
/// optional
|
||||
/// </summary>
|
||||
public int playerCount;
|
||||
|
||||
/// <summary>
|
||||
/// optional
|
||||
/// </summary>
|
||||
public int maxPlayerCount;
|
||||
|
||||
/// <summary>
|
||||
/// optional
|
||||
/// </summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>
|
||||
/// Array of custom data, use SetCustomData to set values
|
||||
/// <para>optional</para>
|
||||
/// </summary>
|
||||
public KeyValue[] customData;
|
||||
|
||||
|
||||
public void SetCustomData(Dictionary<string, string> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
customData = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
customData = data.ToKeyValueArray();
|
||||
CustomDataHelper.ValidateCustomData(customData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
CustomDataHelper.ValidateCustomData(customData);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CustomDataHelper
|
||||
{
|
||||
const int MaxCustomData = 16;
|
||||
|
||||
public static Dictionary<string, string> ToDictionary(this KeyValue[] keyValues)
|
||||
{
|
||||
return keyValues.ToDictionary(x => x.key, x => x.value);
|
||||
}
|
||||
public static KeyValue[] ToKeyValueArray(this Dictionary<string, string> dictionary)
|
||||
{
|
||||
return dictionary.Select(kvp => new KeyValue(kvp.Key, kvp.Value)).ToArray();
|
||||
}
|
||||
|
||||
public static void ValidateCustomData(KeyValue[] customData)
|
||||
{
|
||||
if (customData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (customData.Length > MaxCustomData)
|
||||
{
|
||||
Logger.LogError($"There can only be {MaxCustomData} custom data but there was {customData.Length} values given");
|
||||
Array.Resize(ref customData, MaxCustomData);
|
||||
}
|
||||
|
||||
foreach (KeyValue item in customData)
|
||||
{
|
||||
item.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct KeyValue
|
||||
{
|
||||
const int MaxKeySize = 32;
|
||||
const int MaxValueSize = 256;
|
||||
|
||||
public string key;
|
||||
public string value;
|
||||
|
||||
public KeyValue(string key, string value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (key.Length > MaxKeySize)
|
||||
{
|
||||
Logger.LogError($"Custom Data must have key with length less than {MaxKeySize}");
|
||||
key = key.Substring(0, MaxKeySize);
|
||||
}
|
||||
|
||||
if (value.Length > MaxValueSize)
|
||||
{
|
||||
Logger.LogError($"Custom Data must have value with length less than {MaxValueSize}");
|
||||
value = value.Substring(0, MaxValueSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a963606335eae0f47abe7ecb5fd028ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Mirror.Cloud.ListServerService
|
||||
{
|
||||
public sealed class ListServerServerApi : ListServerBaseApi, IListServerServerApi
|
||||
{
|
||||
const int PingInterval = 20;
|
||||
const int MaxPingFails = 15;
|
||||
|
||||
ServerJson currentServer;
|
||||
string serverId;
|
||||
|
||||
Coroutine _pingCoroutine;
|
||||
/// <summary>
|
||||
/// If the server has already been added
|
||||
/// </summary>
|
||||
bool added;
|
||||
/// <summary>
|
||||
/// if a request is currently sending
|
||||
/// </summary>
|
||||
bool sending;
|
||||
/// <summary>
|
||||
/// If an update request was recently sent
|
||||
/// </summary>
|
||||
bool skipNextPing;
|
||||
/// <summary>
|
||||
/// How many failed pings in a row
|
||||
/// </summary>
|
||||
int pingFails = 0;
|
||||
|
||||
public bool ServerInList => added;
|
||||
|
||||
public ListServerServerApi(ICoroutineRunner runner, IRequestCreator requestCreator) : base(runner, requestCreator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
stopPingCoroutine();
|
||||
if (added)
|
||||
{
|
||||
removeServerWithoutCoroutine();
|
||||
}
|
||||
added = false;
|
||||
}
|
||||
|
||||
public void AddServer(ServerJson server)
|
||||
{
|
||||
if (added) { Logger.LogWarning("AddServer called when server was already adding or added"); return; }
|
||||
bool valid = server.Validate();
|
||||
if (!valid) { return; }
|
||||
|
||||
runner.StartCoroutine(addServer(server));
|
||||
}
|
||||
|
||||
public void UpdateServer(int newPlayerCount)
|
||||
{
|
||||
if (!added) { Logger.LogWarning("UpdateServer called when before server was added"); return; }
|
||||
|
||||
currentServer.playerCount = newPlayerCount;
|
||||
UpdateServer(currentServer);
|
||||
}
|
||||
|
||||
public void UpdateServer(ServerJson server)
|
||||
{
|
||||
// TODO, use PartialServerJson as Arg Instead
|
||||
if (!added) { Logger.LogWarning("UpdateServer called when before server was added"); return; }
|
||||
|
||||
PartialServerJson partialServer = new PartialServerJson
|
||||
{
|
||||
displayName = server.displayName,
|
||||
playerCount = server.playerCount,
|
||||
maxPlayerCount = server.maxPlayerCount,
|
||||
customData = server.customData,
|
||||
};
|
||||
partialServer.Validate();
|
||||
|
||||
runner.StartCoroutine(updateServer(partialServer));
|
||||
}
|
||||
|
||||
public void RemoveServer()
|
||||
{
|
||||
if (!added) { return; }
|
||||
|
||||
if (string.IsNullOrEmpty(serverId))
|
||||
{
|
||||
Logger.LogWarning("Can not remove server because serverId was empty");
|
||||
return;
|
||||
}
|
||||
|
||||
stopPingCoroutine();
|
||||
runner.StartCoroutine(removeServer());
|
||||
}
|
||||
|
||||
void stopPingCoroutine()
|
||||
{
|
||||
if (_pingCoroutine != null)
|
||||
{
|
||||
runner.StopCoroutine(_pingCoroutine);
|
||||
_pingCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator addServer(ServerJson server)
|
||||
{
|
||||
added = true;
|
||||
sending = true;
|
||||
currentServer = server;
|
||||
|
||||
UnityWebRequest request = requestCreator.Post("servers", currentServer);
|
||||
yield return requestCreator.SendRequestEnumerator(request, onSuccess, onFail);
|
||||
sending = false;
|
||||
|
||||
void onSuccess(string responseBody)
|
||||
{
|
||||
CreatedIdJson created = JsonUtility.FromJson<CreatedIdJson>(responseBody);
|
||||
serverId = created.id;
|
||||
|
||||
// Start ping to keep server alive
|
||||
_pingCoroutine = runner.StartCoroutine(ping());
|
||||
}
|
||||
void onFail(string responseBody)
|
||||
{
|
||||
added = false;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator updateServer(PartialServerJson server)
|
||||
{
|
||||
// wait to not be sending
|
||||
while (sending)
|
||||
{
|
||||
yield return new WaitForSeconds(1);
|
||||
}
|
||||
|
||||
// We need to check added in case Update is called soon after Add, and add failed
|
||||
if (!added) { Logger.LogWarning("UpdateServer called when before server was added"); yield break; }
|
||||
|
||||
sending = true;
|
||||
UnityWebRequest request = requestCreator.Patch("servers/" + serverId, server);
|
||||
yield return requestCreator.SendRequestEnumerator(request, onSuccess);
|
||||
sending = false;
|
||||
|
||||
void onSuccess(string responseBody)
|
||||
{
|
||||
skipNextPing = true;
|
||||
|
||||
if (_pingCoroutine == null)
|
||||
{
|
||||
_pingCoroutine = runner.StartCoroutine(ping());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps server alive in database
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator ping()
|
||||
{
|
||||
while (pingFails <= MaxPingFails)
|
||||
{
|
||||
yield return new WaitForSeconds(PingInterval);
|
||||
if (skipNextPing)
|
||||
{
|
||||
skipNextPing = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
sending = true;
|
||||
UnityWebRequest request = requestCreator.Patch("servers/" + serverId, new EmptyJson());
|
||||
yield return requestCreator.SendRequestEnumerator(request, onSuccess, onFail);
|
||||
sending = false;
|
||||
}
|
||||
|
||||
Logger.LogWarning("Max ping fails reached, stopping to ping server");
|
||||
_pingCoroutine = null;
|
||||
|
||||
|
||||
void onSuccess(string responseBody)
|
||||
{
|
||||
pingFails = 0;
|
||||
}
|
||||
void onFail(string responseBody)
|
||||
{
|
||||
pingFails++;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator removeServer()
|
||||
{
|
||||
sending = true;
|
||||
UnityWebRequest request = requestCreator.Delete("servers/" + serverId);
|
||||
yield return requestCreator.SendRequestEnumerator(request);
|
||||
sending = false;
|
||||
|
||||
added = false;
|
||||
}
|
||||
|
||||
void removeServerWithoutCoroutine()
|
||||
{
|
||||
if (string.IsNullOrEmpty(serverId))
|
||||
{
|
||||
Logger.LogWarning("Can not remove server because serverId was empty");
|
||||
return;
|
||||
}
|
||||
|
||||
UnityWebRequest request = requestCreator.Delete("servers/" + serverId);
|
||||
UnityWebRequestAsyncOperation operation = request.SendWebRequest();
|
||||
|
||||
operation.completed += (op) =>
|
||||
{
|
||||
Logger.LogResponse(request);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 675f0d0fd4e82b04290c4d30c8d78ede
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
UnityProject/Assets/Mirror/Cloud/Mirror.Cloud.asmdef
Normal file
14
UnityProject/Assets/Mirror/Cloud/Mirror.Cloud.asmdef
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Mirror.Cloud",
|
||||
"references": [
|
||||
"Mirror"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c21ba7b8c3183cb47b7fe3b3799d49c4
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
152
UnityProject/Assets/Mirror/Cloud/README.md
Normal file
152
UnityProject/Assets/Mirror/Cloud/README.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Mirror Cloud Services
|
||||
|
||||
## Mirror List Server
|
||||
|
||||
Example has an API key which can be used as a demo.
|
||||
|
||||
To get an API key to use within your game you can subscribe on the [Mirror Networking Website](https://mirror-networking.com/list-server/)
|
||||
|
||||
### Key features
|
||||
|
||||
- The Cloud Service works via https so it is secure and can be used from any platform.
|
||||
- It runs on Google Cloud so there is no worry about server downtime.
|
||||
- It scales really well. Default quota is 1000 API requests per minute. If you have high demands, contact us and we can increase that limit.
|
||||
|
||||
## List Server Examples
|
||||
|
||||
An example for this can be found in [Mirror/Examples/Cloud/](https://github.com/vis2k/Mirror/tree/master/Assets/Mirror/Examples/Cloud)
|
||||
|
||||
*Note: you cannot connect to your own public IP address, you will need at least one other person to test this*
|
||||
|
||||
## How to use
|
||||
|
||||
Add `ApiConnector` component to an object in your game. It is probably best to put this on the same object as your NetworkManager. Once it has been added set the `ApiAddress` and `ApiKey` fields.
|
||||
|
||||
To use `ApiConnector` either directly reference it in an inspector field or get it when your script awakes
|
||||
```cs
|
||||
ApiConnector connector;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
connector = FindObjectOfType<ApiConnector>();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The Api calls are grouped into objects. `connector.ListServer.ServerApi` has the Server Api calls like `AddServer`. `connector.ListServer.ClientApi` has the Client Api calls like `GetServerList`.
|
||||
|
||||
### Server Api Example
|
||||
|
||||
Example of how to add server
|
||||
```cs
|
||||
void AddServer(int playerCount)
|
||||
{
|
||||
Transport transport = Transport.activeTransport;
|
||||
|
||||
Uri uri = transport.ServerUri();
|
||||
int port = uri.Port;
|
||||
string protocol = uri.Scheme;
|
||||
|
||||
connector.ListServer.ServerApi.AddServer(new ServerJson
|
||||
{
|
||||
displayName = "Fun game!!!",
|
||||
protocol = protocol,
|
||||
port = port,
|
||||
maxPlayerCount = NetworkManager.singleton.maxConnections,
|
||||
playerCount = playerCount
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Client Api Example
|
||||
Example of how to list servers
|
||||
|
||||
```cs
|
||||
ApiConnector connector;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
connector = FindObjectOfType<ApiConnector>();
|
||||
// add listener to event that will update UI when Server list is refreshed
|
||||
connector.ListServer.ClientApi.onServerListUpdated += onServerListUpdated;
|
||||
|
||||
// add listen to button so that player can refresh server list
|
||||
refreshButton.onClick.AddListener(RefreshButtonHandler);
|
||||
}
|
||||
|
||||
public void RefreshButtonHandler()
|
||||
{
|
||||
connector.ListServer.ClientApi.GetServerList();
|
||||
}
|
||||
|
||||
void onServerListUpdated()
|
||||
{
|
||||
// Update UI here
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Debug
|
||||
|
||||
If something doesn't seem to be working then here are some tips to help solve the problem
|
||||
|
||||
### Check logs
|
||||
|
||||
Enable `showDebugMessages` on your NetworkManager or use the log level window to enable logging for the cloud scripts
|
||||
|
||||
Below are some example logs to look for to check things are working.
|
||||
|
||||
#### Add Server
|
||||
|
||||
The add request is sent to add a server to the list server
|
||||
|
||||
```
|
||||
Request: POST servers {"protocol":"tcp4","port":7777,"playerCount":0,"maxPlayerCount":4,"displayName":"Tanks Game 521","address":"","customAddress":"","customData":[]}
|
||||
```
|
||||
```
|
||||
Response: POST 200 /servers {"id":"BI6bQQ2TbNiqhdp1D7UB"}
|
||||
```
|
||||
|
||||
#### Update Server
|
||||
|
||||
The object sent in update request maybe be empty, this is sent to keep the server record alive so it shows up.
|
||||
|
||||
The update request can also be used to change info. For example the player count when someone joins or leaves
|
||||
|
||||
```
|
||||
Request: PATCH servers/BI6bQQ2TbNiqhdp1D7UB {}
|
||||
```
|
||||
```
|
||||
Response: PATCH 204 /servers/BI6bQQ2TbNiqhdp1D7UB
|
||||
```
|
||||
|
||||
#### Remove Server
|
||||
|
||||
The remove request is sent to remove a server from the list server. This is automatically called when the ApiConnection is destroyed.
|
||||
|
||||
```
|
||||
Request: DELETE servers/BI6bQQ2TbNiqhdp1D7UB
|
||||
```
|
||||
```
|
||||
Response: DELETE 204 /servers/BI6bQQ2TbNiqhdp1D7UB
|
||||
```
|
||||
|
||||
|
||||
#### Get Servers
|
||||
|
||||
The get request is sent in order to get the list of servers.
|
||||
|
||||
The example below shows an array of 2 servers, one with name `Tanks Game 521` and the other with name `Tanks Game 212`
|
||||
|
||||
```
|
||||
Request: GET servers
|
||||
```
|
||||
```
|
||||
Response: GET 200 /servers {"servers":[{"address":"tcp4://xx.xx.xx.xx:7777","displayName":"Tanks Game 521","port":7777,"protocol":"tcp4","playerCount":0,"maxPlayerCount":4,"customAddress":"","customData":[]},{"address":"tcp4://xx.xx.xx.xx:7777","displayName":"Tanks Game 212","port":7777,"protocol":"tcp4","playerCount":0,"maxPlayerCount":4,"customData":[]}]}
|
||||
```
|
||||
*xx.xx.xx.xx will be the IP address for the server*
|
||||
|
||||
|
||||
### Use the QuickListServerDebug
|
||||
|
||||
The QuickListServerDebug script uses `OnGUI` to show the list of servers. This script can be used to check the server list without using Canvas UI.
|
||||
7
UnityProject/Assets/Mirror/Cloud/README.md.meta
Normal file
7
UnityProject/Assets/Mirror/Cloud/README.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 04945d14ccbed964597a1ee00805c059
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1
UnityProject/Assets/Mirror/Cloud/version.txt
Normal file
1
UnityProject/Assets/Mirror/Cloud/version.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
MirrorCloudServices v0.1.0
|
||||
7
UnityProject/Assets/Mirror/Cloud/version.txt.meta
Normal file
7
UnityProject/Assets/Mirror/Cloud/version.txt.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf81e376b88e68e48a47531b8bfeb0f4
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/CompilerSymbols.meta
Normal file
8
UnityProject/Assets/Mirror/CompilerSymbols.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1f8b918bcd89f5c488b06f5574f34760
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Mirror.CompilerSymbols",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 325984b52e4128546bc7558552f8b1d2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
static class PreprocessorDefine
|
||||
{
|
||||
/// <summary>
|
||||
/// Add define symbols as soon as Unity gets done compiling.
|
||||
/// </summary>
|
||||
[InitializeOnLoadMethod]
|
||||
public static void AddDefineSymbols()
|
||||
{
|
||||
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
HashSet<string> defines = new HashSet<string>(currentDefines.Split(';'))
|
||||
{
|
||||
"MIRROR",
|
||||
"MIRROR_17_0_OR_NEWER",
|
||||
"MIRROR_18_0_OR_NEWER",
|
||||
"MIRROR_24_0_OR_NEWER",
|
||||
"MIRROR_26_0_OR_NEWER",
|
||||
"MIRROR_27_0_OR_NEWER",
|
||||
"MIRROR_28_0_OR_NEWER",
|
||||
"MIRROR_29_0_OR_NEWER",
|
||||
"MIRROR_30_0_OR_NEWER",
|
||||
"MIRROR_30_5_2_OR_NEWER",
|
||||
"MIRROR_32_1_2_OR_NEWER",
|
||||
"MIRROR_32_1_4_OR_NEWER",
|
||||
"MIRROR_35_0_OR_NEWER",
|
||||
"MIRROR_35_1_OR_NEWER"
|
||||
};
|
||||
|
||||
// only touch PlayerSettings if we actually modified it.
|
||||
// otherwise it shows up as changed in git each time.
|
||||
string newDefines = string.Join(";", defines);
|
||||
if (newDefines != currentDefines)
|
||||
{
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, newDefines);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f1d66fe74ec6f42dd974cba37d25d453
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Components.meta
Normal file
8
UnityProject/Assets/Mirror/Components.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
UnityProject/Assets/Mirror/Components/AssemblyInfo.cs
Normal file
9
UnityProject/Assets/Mirror/Components/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Common")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Generated")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Editor")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Editor")]
|
||||
11
UnityProject/Assets/Mirror/Components/AssemblyInfo.cs.meta
Normal file
11
UnityProject/Assets/Mirror/Components/AssemblyInfo.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a65b9283f7a724e70b8e17cb277f4c1e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Components/Discovery.meta
Normal file
8
UnityProject/Assets/Mirror/Components/Discovery.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5dcf9618f5e14a4eb60bff5480284a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Mirror.Discovery
|
||||
{
|
||||
[Serializable]
|
||||
public class ServerFoundUnityEvent : UnityEvent<ServerResponse> {};
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkDiscovery")]
|
||||
public class NetworkDiscovery : NetworkDiscoveryBase<ServerRequest, ServerResponse>
|
||||
{
|
||||
#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 override 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;
|
||||
|
||||
base.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the request from a client
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override if you wish to provide more information to the clients
|
||||
/// such as the name of the host player
|
||||
/// </remarks>
|
||||
/// <param name="request">Request coming from client</param>
|
||||
/// <param name="endpoint">Address of the client that sent the request</param>
|
||||
/// <returns>The message to be sent back to the client or null</returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Create a message that will be broadcasted on the network to discover servers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override if you wish to include additional data in the discovery message
|
||||
/// such as desired game mode, language, difficulty, etc... </remarks>
|
||||
/// <returns>An instance of ServerRequest with data to be broadcasted</returns>
|
||||
protected override ServerRequest GetRequest() => new ServerRequest();
|
||||
|
||||
/// <summary>
|
||||
/// Process the answer from a server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A client receives a reply from a server, this method processes the
|
||||
/// reply and raises an event
|
||||
/// </remarks>
|
||||
/// <param name="response">Response that came from the server</param>
|
||||
/// <param name="endpoint">Address of the server that replied</param>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c761308e733c51245b2e8bb4201f46dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
// Based on https://github.com/EnlightenedOne/MirrorNetworkDiscovery
|
||||
// forked from https://github.com/in0finite/MirrorNetworkDiscovery
|
||||
// Both are MIT Licensed
|
||||
|
||||
namespace Mirror.Discovery
|
||||
{
|
||||
/// <summary>
|
||||
/// Base implementation for Network Discovery. Extend this component
|
||||
/// to provide custom discovery with game specific data
|
||||
/// <see cref="NetworkDiscovery">NetworkDiscovery</see> for a sample implementation
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[HelpURL("https://mirror-networking.com/docs/Articles/Components/NetworkDiscovery.html")]
|
||||
public abstract class NetworkDiscoveryBase<Request, Response> : MonoBehaviour
|
||||
where Request : NetworkMessage
|
||||
where Response : NetworkMessage
|
||||
{
|
||||
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")]
|
||||
protected int serverBroadcastListenPort = 47777;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If true, broadcasts a discovery request every ActiveDiscoveryInterval seconds")]
|
||||
public bool enableActiveDiscovery = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Time in seconds between multi-cast messages")]
|
||||
[Range(1, 60)]
|
||||
float ActiveDiscoveryInterval = 3;
|
||||
|
||||
protected UdpClient serverUdpClient;
|
||||
protected UdpClient clientUdpClient;
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// virtual so that inheriting classes' Start() can call base.Start() too
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
// Server mode? then start advertising
|
||||
#if UNITY_SERVER
|
||||
AdvertiseServer();
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/// <summary>
|
||||
/// Advertise this server in the local network
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
|
||||
{
|
||||
long handshake = networkReader.ReadInt64();
|
||||
if (handshake != secretHandshake)
|
||||
{
|
||||
// message is not for us
|
||||
throw new ProtocolViolationException("Invalid handshake");
|
||||
}
|
||||
|
||||
Request request = networkReader.Read<Request>();
|
||||
|
||||
ProcessClientRequest(request, udpReceiveResult.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reply to the client to inform it of this server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override if you wish to ignore server requests based on
|
||||
/// custom criteria such as language, full server game mode or difficulty
|
||||
/// </remarks>
|
||||
/// <param name="request">Request coming from client</param>
|
||||
/// <param name="endpoint">Address of the client that sent the request</param>
|
||||
protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint)
|
||||
{
|
||||
Response info = ProcessRequest(request, endpoint);
|
||||
|
||||
if (info == null)
|
||||
return;
|
||||
|
||||
using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.WriteInt64(secretHandshake);
|
||||
|
||||
writer.Write(info);
|
||||
|
||||
ArraySegment<byte> data = writer.ToArraySegment();
|
||||
// signature matches
|
||||
// send response
|
||||
serverUdpClient.Send(data.Array, data.Count, endpoint);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the request from a client
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override if you wish to provide more information to the clients
|
||||
/// such as the name of the host player
|
||||
/// </remarks>
|
||||
/// <param name="request">Request coming from client</param>
|
||||
/// <param name="endpoint">Address of the client that sent the request</param>
|
||||
/// <returns>The message to be sent back to the client or null</returns>
|
||||
protected abstract Response ProcessRequest(Request request, IPEndPoint endpoint);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client
|
||||
|
||||
/// <summary>
|
||||
/// Start Active Discovery
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
if (enableActiveDiscovery) InvokeRepeating(nameof(BroadcastDiscoveryRequest), 0, ActiveDiscoveryInterval);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop Active Discovery
|
||||
/// </summary>
|
||||
public void StopDiscovery()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Awaits for server response
|
||||
/// </summary>
|
||||
/// <returns>ClientListenAsync Task</returns>
|
||||
public async Task ClientListenAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReceiveGameBroadcastAsync(clientUdpClient);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// socket was closed, no problem
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends discovery request from client
|
||||
/// </summary>
|
||||
public void BroadcastDiscoveryRequest()
|
||||
{
|
||||
if (clientUdpClient == null)
|
||||
return;
|
||||
|
||||
IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort);
|
||||
|
||||
using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
|
||||
{
|
||||
writer.WriteInt64(secretHandshake);
|
||||
|
||||
try
|
||||
{
|
||||
Request request = GetRequest();
|
||||
|
||||
writer.Write(request);
|
||||
|
||||
ArraySegment<byte> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a message that will be broadcasted on the network to discover servers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override if you wish to include additional data in the discovery message
|
||||
/// such as desired game mode, language, difficulty, etc... </remarks>
|
||||
/// <returns>An instance of ServerRequest with data to be broadcasted</returns>
|
||||
protected virtual Request GetRequest() => default;
|
||||
|
||||
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();
|
||||
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
|
||||
{
|
||||
if (networkReader.ReadInt64() != secretHandshake)
|
||||
return;
|
||||
|
||||
Response response = networkReader.Read<Response>();
|
||||
|
||||
ProcessResponse(response, udpReceiveResult.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the answer from a server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A client receives a reply from a server, this method processes the
|
||||
/// reply and raises an event
|
||||
/// </remarks>
|
||||
/// <param name="response">Response that came from the server</param>
|
||||
/// <param name="endpoint">Address of the server that replied</param>
|
||||
protected abstract void ProcessResponse(Response response, IPEndPoint endpoint);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b9971d60ce61f4e39b07cd9e7e0c68fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Discovery
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkDiscoveryHUD")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Articles/Components/NetworkDiscovery.html")]
|
||||
[RequireComponent(typeof(NetworkDiscovery))]
|
||||
public class NetworkDiscoveryHUD : MonoBehaviour
|
||||
{
|
||||
readonly Dictionary<long, ServerResponse> discoveredServers = new Dictionary<long, ServerResponse>();
|
||||
Vector2 scrollViewPos = Vector2.zero;
|
||||
|
||||
public NetworkDiscovery networkDiscovery;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
if (networkDiscovery == null)
|
||||
{
|
||||
networkDiscovery = GetComponent<NetworkDiscovery>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88c37d3deca7a834d80cfd8d3cfcc510
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Mirror.Discovery
|
||||
{
|
||||
public struct ServerRequest : NetworkMessage {}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea7254bf7b9454da4adad881d94cd141
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Mirror.Discovery
|
||||
{
|
||||
public struct ServerResponse : NetworkMessage
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36f97227fdf2d7a4e902db5bfc43039c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
UnityProject/Assets/Mirror/Components/Experimental.meta
Normal file
8
UnityProject/Assets/Mirror/Components/Experimental.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bfbf2a1f2b300c5489dcab219ef2846e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
[AddComponentMenu("Network/Experimental/NetworkLerpRigidbody")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Articles/Components/NetworkLerpRigidbody.html")]
|
||||
public class NetworkLerpRigidbody : NetworkBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[SerializeField] internal Rigidbody target = null;
|
||||
[Tooltip("How quickly current velocity approaches target velocity")]
|
||||
[SerializeField] float lerpVelocityAmount = 0.5f;
|
||||
[Tooltip("How quickly current position approaches target position")]
|
||||
[SerializeField] float lerpPositionAmount = 0.5f;
|
||||
|
||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||
[SerializeField] bool clientAuthority = false;
|
||||
|
||||
float nextSyncTime;
|
||||
|
||||
|
||||
[SyncVar()]
|
||||
Vector3 targetVelocity;
|
||||
|
||||
[SyncVar()]
|
||||
Vector3 targetPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Ignore value if is host or client with Authority
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IgnoreSync => isServer || ClientWithAuthority;
|
||||
|
||||
bool ClientWithAuthority => clientAuthority && hasAuthority;
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
target = GetComponent<Rigidbody>();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (isServer)
|
||||
{
|
||||
SyncToClients();
|
||||
}
|
||||
else if (ClientWithAuthority)
|
||||
{
|
||||
SendToServer();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncToClients()
|
||||
{
|
||||
targetVelocity = target.velocity;
|
||||
targetPosition = target.position;
|
||||
}
|
||||
|
||||
void SendToServer()
|
||||
{
|
||||
float now = Time.time;
|
||||
if (now > nextSyncTime)
|
||||
{
|
||||
nextSyncTime = now + syncInterval;
|
||||
CmdSendState(target.velocity, target.position);
|
||||
}
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendState(Vector3 velocity, Vector3 position)
|
||||
{
|
||||
target.velocity = velocity;
|
||||
target.position = position;
|
||||
targetVelocity = velocity;
|
||||
targetPosition = position;
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (IgnoreSync) { return; }
|
||||
|
||||
target.velocity = Vector3.Lerp(target.velocity, targetVelocity, lerpVelocityAmount);
|
||||
target.position = Vector3.Lerp(target.position, targetPosition, lerpPositionAmount);
|
||||
// add velocity to position as position would have moved on server at that velocity
|
||||
targetPosition += target.velocity * Time.fixedDeltaTime;
|
||||
|
||||
// TODO does this also need to sync acceleration so and update velocity?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7f032128052c95a46afb0ddd97d994cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
[AddComponentMenu("Network/Experimental/NetworkRigidbody")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Articles/Components/NetworkRigidbody.html")]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[SerializeField] internal Rigidbody target = null;
|
||||
|
||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||
[SerializeField] bool clientAuthority = false;
|
||||
|
||||
[Header("Velocity")]
|
||||
|
||||
[Tooltip("Syncs Velocity every SyncInterval")]
|
||||
[SerializeField] bool syncVelocity = true;
|
||||
|
||||
[Tooltip("Set velocity to 0 each frame (only works if syncVelocity is false")]
|
||||
[SerializeField] bool clearVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float velocitySensitivity = 0.1f;
|
||||
|
||||
|
||||
[Header("Angular Velocity")]
|
||||
|
||||
[Tooltip("Syncs AngularVelocity every SyncInterval")]
|
||||
[SerializeField] bool syncAngularVelocity = true;
|
||||
|
||||
[Tooltip("Set angularVelocity to 0 each frame (only works if syncAngularVelocity is false")]
|
||||
[SerializeField] bool clearAngularVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float angularVelocitySensitivity = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Values sent on client with authority after they are sent to the server
|
||||
/// </summary>
|
||||
readonly ClientSyncState previousValue = new ClientSyncState();
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
target = GetComponent<Rigidbody>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Sync vars
|
||||
[SyncVar(hook = nameof(OnVelocityChanged))]
|
||||
Vector3 velocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularVelocityChanged))]
|
||||
Vector3 angularVelocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnIsKinematicChanged))]
|
||||
bool isKinematic;
|
||||
|
||||
[SyncVar(hook = nameof(OnUseGravityChanged))]
|
||||
bool useGravity;
|
||||
|
||||
[SyncVar(hook = nameof(OnuDragChanged))]
|
||||
float drag;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularDragChanged))]
|
||||
float angularDrag;
|
||||
|
||||
/// <summary>
|
||||
/// Ignore value if is host or client with Authority
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IgnoreSync => isServer || ClientWithAuthority;
|
||||
|
||||
bool ClientWithAuthority => clientAuthority && hasAuthority;
|
||||
|
||||
void OnVelocityChanged(Vector3 _, Vector3 newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.velocity = newValue;
|
||||
}
|
||||
|
||||
|
||||
void OnAngularVelocityChanged(Vector3 _, Vector3 newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularVelocity = newValue;
|
||||
}
|
||||
|
||||
void OnIsKinematicChanged(bool _, bool newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.isKinematic = newValue;
|
||||
}
|
||||
|
||||
void OnUseGravityChanged(bool _, bool newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.useGravity = newValue;
|
||||
}
|
||||
|
||||
void OnuDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.drag = newValue;
|
||||
}
|
||||
|
||||
void OnAngularDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularDrag = newValue;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (isServer)
|
||||
{
|
||||
SyncToClients();
|
||||
}
|
||||
else if (ClientWithAuthority)
|
||||
{
|
||||
SendToServer();
|
||||
}
|
||||
}
|
||||
|
||||
internal void FixedUpdate()
|
||||
{
|
||||
if (clearAngularVelocity && !syncAngularVelocity)
|
||||
{
|
||||
target.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
if (clearVelocity && !syncVelocity)
|
||||
{
|
||||
target.velocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates sync var values on server so that they sync to the client
|
||||
/// </summary>
|
||||
[Server]
|
||||
void SyncToClients()
|
||||
{
|
||||
// only update if they have changed more than Sensitivity
|
||||
|
||||
Vector3 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
|
||||
|
||||
if (velocityChanged)
|
||||
{
|
||||
velocity = currentVelocity;
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
angularVelocity = currentAngularVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
|
||||
// other rigidbody settings
|
||||
isKinematic = target.isKinematic;
|
||||
useGravity = target.useGravity;
|
||||
drag = target.drag;
|
||||
angularDrag = target.angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses Command to send values to server
|
||||
/// </summary>
|
||||
[Client]
|
||||
void SendToServer()
|
||||
{
|
||||
if (!hasAuthority)
|
||||
{
|
||||
Debug.LogWarning("SendToServer called without authority");
|
||||
return;
|
||||
}
|
||||
|
||||
SendVelocity();
|
||||
SendRigidBodySettings();
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendVelocity()
|
||||
{
|
||||
float now = Time.time;
|
||||
if (now < previousValue.nextSyncTime)
|
||||
return;
|
||||
|
||||
Vector3 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
|
||||
|
||||
// if angularVelocity has changed it is likely that velocity has also changed so just sync both values
|
||||
// however if only velocity has changed just send velocity
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
CmdSendVelocityAndAngular(currentVelocity, currentAngularVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
else if (velocityChanged)
|
||||
{
|
||||
CmdSendVelocity(currentVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
|
||||
// only update syncTime if either has changed
|
||||
if (angularVelocityChanged || velocityChanged)
|
||||
{
|
||||
previousValue.nextSyncTime = now + syncInterval;
|
||||
}
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendRigidBodySettings()
|
||||
{
|
||||
// These shouldn't change often so it is ok to send in their own Command
|
||||
if (previousValue.isKinematic != target.isKinematic)
|
||||
{
|
||||
CmdSendIsKinematic(target.isKinematic);
|
||||
previousValue.isKinematic = target.isKinematic;
|
||||
}
|
||||
if (previousValue.useGravity != target.useGravity)
|
||||
{
|
||||
CmdSendUseGravity(target.useGravity);
|
||||
previousValue.useGravity = target.useGravity;
|
||||
}
|
||||
if (previousValue.drag != target.drag)
|
||||
{
|
||||
CmdSendDrag(target.drag);
|
||||
previousValue.drag = target.drag;
|
||||
}
|
||||
if (previousValue.angularDrag != target.angularDrag)
|
||||
{
|
||||
CmdSendAngularDrag(target.angularDrag);
|
||||
previousValue.angularDrag = target.angularDrag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when only Velocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocity(Vector3 velocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.velocity = velocity;
|
||||
target.velocity = velocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when angularVelocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocityAndAngular(Vector3 velocity, Vector3 angularVelocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
if (syncVelocity)
|
||||
{
|
||||
this.velocity = velocity;
|
||||
|
||||
target.velocity = velocity;
|
||||
|
||||
}
|
||||
this.angularVelocity = angularVelocity;
|
||||
target.angularVelocity = angularVelocity;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendIsKinematic(bool isKinematic)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.isKinematic = isKinematic;
|
||||
target.isKinematic = isKinematic;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendUseGravity(bool useGravity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.useGravity = useGravity;
|
||||
target.useGravity = useGravity;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendDrag(float drag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.drag = drag;
|
||||
target.drag = drag;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendAngularDrag(float angularDrag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.angularDrag = angularDrag;
|
||||
target.angularDrag = angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// holds previously synced values
|
||||
/// </summary>
|
||||
public class ClientSyncState
|
||||
{
|
||||
/// <summary>
|
||||
/// Next sync time that velocity will be synced, based on syncInterval.
|
||||
/// </summary>
|
||||
public float nextSyncTime;
|
||||
public Vector3 velocity;
|
||||
public Vector3 angularVelocity;
|
||||
public bool isKinematic;
|
||||
public bool useGravity;
|
||||
public float drag;
|
||||
public float angularDrag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 83392ae5c1b731446909f252fd494ae4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue