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