mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
commit
ad4a421a60
@ -8,6 +8,10 @@
|
|||||||
]
|
]
|
||||||
}],
|
}],
|
||||||
'@semantic-release/release-notes-generator',
|
'@semantic-release/release-notes-generator',
|
||||||
'@semantic-release/github'
|
["@semantic-release/github", {
|
||||||
|
"assets": [
|
||||||
|
{"path": "Mirror.unitypackage", "label": "Mirror.unitypackage"}
|
||||||
|
]
|
||||||
|
}],
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
@ -9,40 +10,12 @@ namespace Mirror
|
|||||||
public class NetworkAnimator : NetworkBehaviour
|
public class NetworkAnimator : NetworkBehaviour
|
||||||
{
|
{
|
||||||
// configuration
|
// configuration
|
||||||
[SerializeField] Animator m_Animator;
|
[FormerlySerializedAs("m_Animator")] public Animator animator;
|
||||||
[SerializeField] uint m_ParameterSendBits;
|
|
||||||
// Note: not an object[] array because otherwise initialization is real annoying
|
// Note: not an object[] array because otherwise initialization is real annoying
|
||||||
int[] lastIntParameters;
|
int[] lastIntParameters;
|
||||||
float[] lastFloatParameters;
|
float[] lastFloatParameters;
|
||||||
bool[] lastBoolParameters;
|
bool[] lastBoolParameters;
|
||||||
|
AnimatorControllerParameter[] parameters;
|
||||||
// properties
|
|
||||||
public Animator animator
|
|
||||||
{
|
|
||||||
get => m_Animator;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_Animator = value;
|
|
||||||
ResetParameterOptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetParameterAutoSend(int index, bool value)
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
m_ParameterSendBits |= (uint)(1 << index);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_ParameterSendBits &= (uint)(~(1 << index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool GetParameterAutoSend(int index)
|
|
||||||
{
|
|
||||||
return (m_ParameterSendBits & (uint)(1 << index)) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int m_AnimationHash;
|
int m_AnimationHash;
|
||||||
int m_TransitionHash;
|
int m_TransitionHash;
|
||||||
@ -71,10 +44,14 @@ bool sendMessagesAllowed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetParameterOptions()
|
void Awake()
|
||||||
{
|
{
|
||||||
Debug.Log("ResetParameterOptions");
|
// store the animator parameters in a variable - the "Animator.parameters" getter allocates
|
||||||
m_ParameterSendBits = 0;
|
// a new parameter array every time it is accessed so we should avoid doing it in a loop
|
||||||
|
parameters = animator.parameters;
|
||||||
|
lastIntParameters = new int[parameters.Length];
|
||||||
|
lastFloatParameters = new float[parameters.Length];
|
||||||
|
lastBoolParameters = new bool[parameters.Length];
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
@ -90,7 +67,7 @@ void FixedUpdate()
|
|||||||
}
|
}
|
||||||
|
|
||||||
NetworkWriter writer = new NetworkWriter();
|
NetworkWriter writer = new NetworkWriter();
|
||||||
WriteParameters(writer, false);
|
WriteParameters(writer);
|
||||||
|
|
||||||
SendAnimationMessage(stateHash, normalizedTime, writer.ToArray());
|
SendAnimationMessage(stateHash, normalizedTime, writer.ToArray());
|
||||||
}
|
}
|
||||||
@ -100,9 +77,9 @@ bool CheckAnimStateChanged(out int stateHash, out float normalizedTime)
|
|||||||
stateHash = 0;
|
stateHash = 0;
|
||||||
normalizedTime = 0;
|
normalizedTime = 0;
|
||||||
|
|
||||||
if (m_Animator.IsInTransition(0))
|
if (animator.IsInTransition(0))
|
||||||
{
|
{
|
||||||
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0);
|
AnimatorTransitionInfo tt = animator.GetAnimatorTransitionInfo(0);
|
||||||
if (tt.fullPathHash != m_TransitionHash)
|
if (tt.fullPathHash != m_TransitionHash)
|
||||||
{
|
{
|
||||||
// first time in this transition
|
// first time in this transition
|
||||||
@ -113,7 +90,7 @@ bool CheckAnimStateChanged(out int stateHash, out float normalizedTime)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
|
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(0);
|
||||||
if (st.fullPathHash != m_AnimationHash)
|
if (st.fullPathHash != m_AnimationHash)
|
||||||
{
|
{
|
||||||
// first time in this animation state
|
// first time in this animation state
|
||||||
@ -137,7 +114,7 @@ void CheckSendRate()
|
|||||||
m_SendTimer = Time.time + syncInterval;
|
m_SendTimer = Time.time + syncInterval;
|
||||||
|
|
||||||
NetworkWriter writer = new NetworkWriter();
|
NetworkWriter writer = new NetworkWriter();
|
||||||
if (WriteParameters(writer, true))
|
if (WriteParameters(writer))
|
||||||
{
|
{
|
||||||
SendAnimationParametersMessage(writer.ToArray());
|
SendAnimationParametersMessage(writer.ToArray());
|
||||||
}
|
}
|
||||||
@ -168,7 +145,7 @@ void SendAnimationParametersMessage(byte[] parameters)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void HandleAnimMsg(int stateHash, float normalizedTime, NetworkReader reader)
|
void HandleAnimMsg(int stateHash, float normalizedTime, NetworkReader reader)
|
||||||
{
|
{
|
||||||
if (hasAuthority)
|
if (hasAuthority)
|
||||||
return;
|
return;
|
||||||
@ -178,115 +155,119 @@ internal void HandleAnimMsg(int stateHash, float normalizedTime, NetworkReader r
|
|||||||
// NOTE: there is no API to play a transition(?)
|
// NOTE: there is no API to play a transition(?)
|
||||||
if (stateHash != 0)
|
if (stateHash != 0)
|
||||||
{
|
{
|
||||||
m_Animator.Play(stateHash, 0, normalizedTime);
|
animator.Play(stateHash, 0, normalizedTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadParameters(reader, false);
|
ReadParameters(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void HandleAnimParamsMsg(NetworkReader reader)
|
void HandleAnimParamsMsg(NetworkReader reader)
|
||||||
{
|
{
|
||||||
if (hasAuthority)
|
if (hasAuthority)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ReadParameters(reader, true);
|
ReadParameters(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void HandleAnimTriggerMsg(int hash)
|
void HandleAnimTriggerMsg(int hash)
|
||||||
{
|
{
|
||||||
m_Animator.SetTrigger(hash);
|
animator.SetTrigger(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WriteParameters(NetworkWriter writer, bool autoSend)
|
ulong NextDirtyBits()
|
||||||
{
|
{
|
||||||
// store the animator parameters in a variable - the "Animator.parameters" getter allocates
|
ulong dirtyBits = 0;
|
||||||
// a new parameter array every time it is accessed so we should avoid doing it in a loop
|
|
||||||
AnimatorControllerParameter[] parameters = m_Animator.parameters;
|
|
||||||
if (lastIntParameters == null) lastIntParameters = new int[parameters.Length];
|
|
||||||
if (lastFloatParameters == null) lastFloatParameters = new float[parameters.Length];
|
|
||||||
if (lastBoolParameters == null) lastBoolParameters = new bool[parameters.Length];
|
|
||||||
|
|
||||||
uint dirtyBits = 0;
|
|
||||||
// Save the position in the writer where to insert the dirty bits
|
|
||||||
int dirtyBitsPosition = writer.Position;
|
|
||||||
// Reserve the space for the bits
|
|
||||||
writer.Write(dirtyBits);
|
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
if (autoSend && !GetParameterAutoSend(i))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AnimatorControllerParameter par = parameters[i];
|
AnimatorControllerParameter par = parameters[i];
|
||||||
|
bool changed = false;
|
||||||
if (par.type == AnimatorControllerParameterType.Int)
|
if (par.type == AnimatorControllerParameterType.Int)
|
||||||
{
|
{
|
||||||
int newIntValue = m_Animator.GetInteger(par.nameHash);
|
int newIntValue = animator.GetInteger(par.nameHash);
|
||||||
if (newIntValue != lastIntParameters[i])
|
changed = newIntValue != lastIntParameters[i];
|
||||||
|
if (changed)
|
||||||
{
|
{
|
||||||
writer.WritePackedUInt32((uint) newIntValue);
|
|
||||||
dirtyBits |= 1u << i;
|
|
||||||
lastIntParameters[i] = newIntValue;
|
lastIntParameters[i] = newIntValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (par.type == AnimatorControllerParameterType.Float)
|
else if (par.type == AnimatorControllerParameterType.Float)
|
||||||
{
|
{
|
||||||
float newFloatValue = m_Animator.GetFloat(par.nameHash);
|
float newFloatValue = animator.GetFloat(par.nameHash);
|
||||||
if (Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f)
|
changed = Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f;
|
||||||
|
if (changed)
|
||||||
{
|
{
|
||||||
writer.Write(newFloatValue);
|
|
||||||
dirtyBits |= 1u << i;
|
|
||||||
lastFloatParameters[i] = newFloatValue;
|
lastFloatParameters[i] = newFloatValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (par.type == AnimatorControllerParameterType.Bool)
|
else if (par.type == AnimatorControllerParameterType.Bool)
|
||||||
{
|
{
|
||||||
bool newBoolValue = m_Animator.GetBool(par.nameHash);
|
bool newBoolValue = animator.GetBool(par.nameHash);
|
||||||
if (newBoolValue != lastBoolParameters[i])
|
changed = newBoolValue != lastBoolParameters[i];
|
||||||
|
if (changed)
|
||||||
{
|
{
|
||||||
writer.Write(newBoolValue);
|
|
||||||
dirtyBits |= 1u << i;
|
|
||||||
lastBoolParameters[i] = newBoolValue;
|
lastBoolParameters[i] = newBoolValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
dirtyBits |= 1ul << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirtyBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteParameters(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
ulong dirtyBits = NextDirtyBits();
|
||||||
|
writer.WritePackedUInt64(dirtyBits);
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
if ((dirtyBits & (1ul << i)) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
AnimatorControllerParameter par = parameters[i];
|
||||||
|
if (par.type == AnimatorControllerParameterType.Int)
|
||||||
|
{
|
||||||
|
int newIntValue = animator.GetInteger(par.nameHash);
|
||||||
|
writer.WritePackedUInt32((uint)newIntValue);
|
||||||
|
}
|
||||||
|
else if (par.type == AnimatorControllerParameterType.Float)
|
||||||
|
{
|
||||||
|
float newFloatValue = animator.GetFloat(par.nameHash);
|
||||||
|
writer.Write(newFloatValue);
|
||||||
|
}
|
||||||
|
else if (par.type == AnimatorControllerParameterType.Bool)
|
||||||
|
{
|
||||||
|
bool newBoolValue = animator.GetBool(par.nameHash);
|
||||||
|
writer.Write(newBoolValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Save the position we were at to return to after writing dirtyBits
|
|
||||||
int messageEndPosition = writer.Position;
|
|
||||||
// Write the dirty bits into the reserved position
|
|
||||||
writer.Position = dirtyBitsPosition;
|
|
||||||
writer.Write(dirtyBits);
|
|
||||||
// Return to the end position, so that serialization includes parameter data.
|
|
||||||
writer.Position = messageEndPosition;
|
|
||||||
return dirtyBits != 0;
|
return dirtyBits != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadParameters(NetworkReader reader, bool autoSend)
|
void ReadParameters(NetworkReader reader)
|
||||||
{
|
{
|
||||||
// store the animator parameters in a variable - the "Animator.parameters" getter allocates
|
ulong dirtyBits = reader.ReadPackedUInt64();
|
||||||
// a new parameter array every time it is accessed so we should avoid doing it in a loop
|
|
||||||
AnimatorControllerParameter[] parameters = m_Animator.parameters;
|
|
||||||
|
|
||||||
uint dirtyBits = reader.ReadUInt32();
|
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
if (autoSend && !GetParameterAutoSend(i))
|
if ((dirtyBits & (1ul << i)) == 0)
|
||||||
continue;
|
|
||||||
if ((dirtyBits & (1 << i)) == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AnimatorControllerParameter par = parameters[i];
|
AnimatorControllerParameter par = parameters[i];
|
||||||
if (par.type == AnimatorControllerParameterType.Int)
|
if (par.type == AnimatorControllerParameterType.Int)
|
||||||
{
|
{
|
||||||
int newIntValue = (int)reader.ReadPackedUInt32();
|
int newIntValue = (int)reader.ReadPackedUInt32();
|
||||||
m_Animator.SetInteger(par.nameHash, newIntValue);
|
animator.SetInteger(par.nameHash, newIntValue);
|
||||||
}
|
}
|
||||||
else if (par.type == AnimatorControllerParameterType.Float)
|
else if (par.type == AnimatorControllerParameterType.Float)
|
||||||
{
|
{
|
||||||
float newFloatValue = reader.ReadSingle();
|
float newFloatValue = reader.ReadSingle();
|
||||||
m_Animator.SetFloat(par.nameHash, newFloatValue);
|
animator.SetFloat(par.nameHash, newFloatValue);
|
||||||
}
|
}
|
||||||
else if (par.type == AnimatorControllerParameterType.Bool)
|
else if (par.type == AnimatorControllerParameterType.Bool)
|
||||||
{
|
{
|
||||||
bool newBoolValue = reader.ReadBoolean();
|
bool newBoolValue = reader.ReadBoolean();
|
||||||
m_Animator.SetBool(par.nameHash, newBoolValue);
|
animator.SetBool(par.nameHash, newBoolValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,19 +276,19 @@ public override bool OnSerialize(NetworkWriter writer, bool forceAll)
|
|||||||
{
|
{
|
||||||
if (forceAll)
|
if (forceAll)
|
||||||
{
|
{
|
||||||
if (m_Animator.IsInTransition(0))
|
if (animator.IsInTransition(0))
|
||||||
{
|
{
|
||||||
AnimatorStateInfo st = m_Animator.GetNextAnimatorStateInfo(0);
|
AnimatorStateInfo st = animator.GetNextAnimatorStateInfo(0);
|
||||||
writer.Write(st.fullPathHash);
|
writer.Write(st.fullPathHash);
|
||||||
writer.Write(st.normalizedTime);
|
writer.Write(st.normalizedTime);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
|
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(0);
|
||||||
writer.Write(st.fullPathHash);
|
writer.Write(st.fullPathHash);
|
||||||
writer.Write(st.normalizedTime);
|
writer.Write(st.normalizedTime);
|
||||||
}
|
}
|
||||||
WriteParameters(writer, false);
|
WriteParameters(writer);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -319,8 +300,8 @@ public override void OnDeserialize(NetworkReader reader, bool initialState)
|
|||||||
{
|
{
|
||||||
int stateHash = reader.ReadInt32();
|
int stateHash = reader.ReadInt32();
|
||||||
float normalizedTime = reader.ReadSingle();
|
float normalizedTime = reader.ReadSingle();
|
||||||
ReadParameters(reader, false);
|
ReadParameters(reader);
|
||||||
m_Animator.Play(stateHash, 0, normalizedTime);
|
animator.Play(stateHash, 0, normalizedTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +314,7 @@ public void SetTrigger(int hash)
|
|||||||
{
|
{
|
||||||
if (hasAuthority && localPlayerAuthority)
|
if (hasAuthority && localPlayerAuthority)
|
||||||
{
|
{
|
||||||
if (NetworkClient.singleton != null && ClientScene.readyConnection != null)
|
if (ClientScene.readyConnection != null)
|
||||||
{
|
{
|
||||||
CmdOnAnimationTriggerServerMessage(hash);
|
CmdOnAnimationTriggerServerMessage(hash);
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ public override void OnStopHost()
|
|||||||
|
|
||||||
#region client handlers
|
#region client handlers
|
||||||
|
|
||||||
public override void OnStartClient(NetworkClient lobbyClient)
|
public override void OnStartClient()
|
||||||
{
|
{
|
||||||
if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
|
if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
|
||||||
Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
|
Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
|
||||||
@ -340,7 +340,7 @@ public override void OnStartClient(NetworkClient lobbyClient)
|
|||||||
else
|
else
|
||||||
ClientScene.RegisterPrefab(playerPrefab);
|
ClientScene.RegisterPrefab(playerPrefab);
|
||||||
|
|
||||||
OnLobbyStartClient(lobbyClient);
|
OnLobbyStartClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnClientConnect(NetworkConnection conn)
|
public override void OnClientConnect(NetworkConnection conn)
|
||||||
@ -373,9 +373,9 @@ public override void OnClientChangeScene(string newSceneName)
|
|||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
|
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
|
||||||
|
|
||||||
if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && IsClientConnected() && client != null)
|
if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && NetworkClient.isConnected)
|
||||||
{
|
{
|
||||||
GameObject lobbyPlayer = client?.connection?.playerController?.gameObject;
|
GameObject lobbyPlayer = NetworkClient.connection?.playerController?.gameObject;
|
||||||
if (lobbyPlayer != null)
|
if (lobbyPlayer != null)
|
||||||
{
|
{
|
||||||
lobbyPlayer.transform.SetParent(null);
|
lobbyPlayer.transform.SetParent(null);
|
||||||
@ -385,14 +385,14 @@ public override void OnClientChangeScene(string newSceneName)
|
|||||||
Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
|
Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1} {2}", dontDestroyOnLoad, IsClientConnected(), client != null);
|
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1}", dontDestroyOnLoad, NetworkClient.isConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnClientSceneChanged(NetworkConnection conn)
|
public override void OnClientSceneChanged(NetworkConnection conn)
|
||||||
{
|
{
|
||||||
if (SceneManager.GetActiveScene().name == LobbyScene)
|
if (SceneManager.GetActiveScene().name == LobbyScene)
|
||||||
{
|
{
|
||||||
if (client.isConnected)
|
if (NetworkClient.isConnected)
|
||||||
CallOnClientEnterLobby();
|
CallOnClientEnterLobby();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -452,7 +452,7 @@ public virtual void OnLobbyClientConnect(NetworkConnection conn) {}
|
|||||||
|
|
||||||
public virtual void OnLobbyClientDisconnect(NetworkConnection conn) {}
|
public virtual void OnLobbyClientDisconnect(NetworkConnection conn) {}
|
||||||
|
|
||||||
public virtual void OnLobbyStartClient(NetworkClient lobbyClient) {}
|
public virtual void OnLobbyStartClient() {}
|
||||||
|
|
||||||
public virtual void OnLobbyStopClient() {}
|
public virtual void OnLobbyStopClient() {}
|
||||||
|
|
||||||
|
@ -10,12 +10,14 @@ public class NetworkLobbyPlayer : NetworkBehaviour
|
|||||||
{
|
{
|
||||||
public bool ShowLobbyGUI = true;
|
public bool ShowLobbyGUI = true;
|
||||||
|
|
||||||
[SyncVar]
|
[SyncVar(hook=nameof(ReadyStateChanged))]
|
||||||
public bool ReadyToBegin;
|
public bool ReadyToBegin;
|
||||||
|
|
||||||
[SyncVar]
|
[SyncVar]
|
||||||
public int Index;
|
public int Index;
|
||||||
|
|
||||||
|
#region Unity Callbacks
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do not use Start - Override OnStartrHost / OnStartClient instead!
|
/// Do not use Start - Override OnStartrHost / OnStartClient instead!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -34,15 +36,9 @@ void OnDisable()
|
|||||||
SceneManager.sceneLoaded -= ClientLoadedScene;
|
SceneManager.sceneLoaded -= ClientLoadedScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void ClientLoadedScene(Scene arg0, LoadSceneMode arg1)
|
#endregion
|
||||||
{
|
|
||||||
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
|
|
||||||
if (lobby != null && SceneManager.GetActiveScene().name == lobby.LobbyScene)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this != null && isLocalPlayer)
|
#region Commands
|
||||||
CmdSendLevelLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command]
|
[Command]
|
||||||
public void CmdChangeReadyState(bool ReadyState)
|
public void CmdChangeReadyState(bool ReadyState)
|
||||||
@ -59,7 +55,18 @@ public void CmdSendLevelLoaded()
|
|||||||
lobby?.PlayerLoadedScene(GetComponent<NetworkIdentity>().connectionToClient);
|
lobby?.PlayerLoadedScene(GetComponent<NetworkIdentity>().connectionToClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region lobby client virtuals
|
#endregion
|
||||||
|
|
||||||
|
#region SyncVar Hooks
|
||||||
|
|
||||||
|
private void ReadyStateChanged(bool NewReadyState)
|
||||||
|
{
|
||||||
|
OnClientReady(ReadyToBegin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lobby Client Virtuals
|
||||||
|
|
||||||
public virtual void OnClientEnterLobby() {}
|
public virtual void OnClientEnterLobby() {}
|
||||||
|
|
||||||
@ -67,9 +74,19 @@ public virtual void OnClientExitLobby() {}
|
|||||||
|
|
||||||
public virtual void OnClientReady(bool readyState) {}
|
public virtual void OnClientReady(bool readyState) {}
|
||||||
|
|
||||||
|
public virtual void ClientLoadedScene(Scene arg0, LoadSceneMode arg1)
|
||||||
|
{
|
||||||
|
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
|
||||||
|
if (lobby != null && SceneManager.GetActiveScene().name == lobby.LobbyScene)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this != null && isLocalPlayer)
|
||||||
|
CmdSendLevelLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region optional UI
|
#region Optional UI
|
||||||
|
|
||||||
public virtual void OnGUI()
|
public virtual void OnGUI()
|
||||||
{
|
{
|
||||||
|
@ -59,12 +59,8 @@ public override bool OnCheckObserver(NetworkConnection newObserver)
|
|||||||
if (forceHidden)
|
if (forceHidden)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (newObserver.playerController != null)
|
|
||||||
{
|
|
||||||
return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange;
|
return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
|
public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ public abstract class NetworkTransformBase : NetworkBehaviour
|
|||||||
// but would cause errors immediately and be pretty obvious.
|
// but would cause errors immediately and be pretty obvious.
|
||||||
[Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")]
|
[Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")]
|
||||||
[SerializeField] Compression compressRotation = Compression.Much;
|
[SerializeField] Compression compressRotation = Compression.Much;
|
||||||
public enum Compression { None, Much, Lots }; // easily understandable and funny
|
public enum Compression { None, Much, Lots , NoRotation }; // easily understandable and funny
|
||||||
|
|
||||||
// server
|
// server
|
||||||
Vector3 lastPosition;
|
Vector3 lastPosition;
|
||||||
@ -312,8 +312,11 @@ bool HasMovedOrRotated()
|
|||||||
void ApplyPositionAndRotation(Vector3 position, Quaternion rotation)
|
void ApplyPositionAndRotation(Vector3 position, Quaternion rotation)
|
||||||
{
|
{
|
||||||
targetComponent.transform.position = position;
|
targetComponent.transform.position = position;
|
||||||
|
if (Compression.NoRotation != compressRotation)
|
||||||
|
{
|
||||||
targetComponent.transform.rotation = rotation;
|
targetComponent.transform.rotation = rotation;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Animations;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Mirror
|
|
||||||
{
|
|
||||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
|
||||||
[CanEditMultipleObjects]
|
|
||||||
public class NetworkAnimatorEditor : Editor
|
|
||||||
{
|
|
||||||
NetworkAnimator m_AnimSync;
|
|
||||||
[NonSerialized] bool m_Initialized;
|
|
||||||
|
|
||||||
SerializedProperty m_AnimatorProperty;
|
|
||||||
GUIContent m_AnimatorLabel = new GUIContent("Animator", "The Animator component to synchronize.");
|
|
||||||
|
|
||||||
void Init()
|
|
||||||
{
|
|
||||||
if (m_Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_Initialized = true;
|
|
||||||
m_AnimSync = target as NetworkAnimator;
|
|
||||||
|
|
||||||
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
|
||||||
Init();
|
|
||||||
serializedObject.Update();
|
|
||||||
DrawControls();
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawControls()
|
|
||||||
{
|
|
||||||
EditorGUI.BeginChangeCheck();
|
|
||||||
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
|
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
m_AnimSync.ResetParameterOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_AnimSync.animator == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AnimatorController controller = m_AnimSync.animator.runtimeAnimatorController as AnimatorController;
|
|
||||||
if (controller != null)
|
|
||||||
{
|
|
||||||
bool showWarning = false;
|
|
||||||
EditorGUI.indentLevel += 1;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
foreach (AnimatorControllerParameter p in controller.parameters)
|
|
||||||
{
|
|
||||||
if (i >= 32)
|
|
||||||
{
|
|
||||||
showWarning = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool oldSend = m_AnimSync.GetParameterAutoSend(i);
|
|
||||||
bool send = EditorGUILayout.Toggle("Sync " + p.name, oldSend);
|
|
||||||
if (send != oldSend)
|
|
||||||
{
|
|
||||||
m_AnimSync.SetParameterAutoSend(i, send);
|
|
||||||
EditorUtility.SetDirty(target);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showWarning)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("NetworkAnimator can only select between the first 32 parameters in a mecanim controller", MessageType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUI.indentLevel -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,26 +2,24 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Callbacks;
|
using UnityEditor.Callbacks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
public class NetworkScenePostProcess : MonoBehaviour
|
public class NetworkScenePostProcess : MonoBehaviour
|
||||||
{
|
{
|
||||||
// helper function to check if a NetworkIdentity is in the active scene
|
|
||||||
static bool InActiveScene(NetworkIdentity identity) =>
|
|
||||||
identity.gameObject.scene == SceneManager.GetActiveScene();
|
|
||||||
|
|
||||||
[PostProcessScene]
|
[PostProcessScene]
|
||||||
public static void OnPostProcessScene()
|
public static void OnPostProcessScene()
|
||||||
{
|
{
|
||||||
// find all NetworkIdentities in this scene
|
// find all NetworkIdentities in all scenes
|
||||||
// => but really only from this scene. this avoids weird situations
|
// => can't limit it to GetActiveScene() because that wouldn't work
|
||||||
|
// for additive scene loads (the additively loaded scene is never
|
||||||
|
// the active scene)
|
||||||
|
// => ignore DontDestroyOnLoad scene! this avoids weird situations
|
||||||
// like in NetworkZones when we destroy the local player and
|
// like in NetworkZones when we destroy the local player and
|
||||||
// load another scene afterwards, yet the local player is still
|
// load another scene afterwards, yet the local player is still
|
||||||
// in the FindObjectsOfType result with scene=DontDestroyOnLoad
|
// in the FindObjectsOfType result with scene=DontDestroyOnLoad
|
||||||
// for some reason
|
// for some reason
|
||||||
foreach (NetworkIdentity identity in FindObjectsOfType<NetworkIdentity>().Where(InActiveScene))
|
foreach (NetworkIdentity identity in FindObjectsOfType<NetworkIdentity>().Where(identity => identity.gameObject.scene.name != "DontDestroyOnLoad"))
|
||||||
{
|
{
|
||||||
// if we had a [ConflictComponent] attribute that would be better than this check.
|
// if we had a [ConflictComponent] attribute that would be better than this check.
|
||||||
// also there is no context about which scene this is in.
|
// also there is no context about which scene this is in.
|
||||||
@ -29,21 +27,22 @@ public static void OnPostProcessScene()
|
|||||||
{
|
{
|
||||||
Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
|
Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
|
||||||
}
|
}
|
||||||
if (identity.isClient || identity.isServer)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// valid scene id? then set scene path part
|
// not spawned before?
|
||||||
|
// OnPostProcessScene is called after additive scene loads too,
|
||||||
|
// and we don't want to set main scene's objects inactive again
|
||||||
|
if (!identity.isClient && !identity.isServer)
|
||||||
|
{
|
||||||
|
// valid scene object?
|
||||||
// otherwise it might be an unopened scene that still has null
|
// otherwise it might be an unopened scene that still has null
|
||||||
// sceneIds. builds are interrupted if they contain 0 sceneIds,
|
// sceneIds. builds are interrupted if they contain 0 sceneIds,
|
||||||
// but it's still possible that we call LoadScene in Editor
|
// but it's still possible that we call LoadScene in Editor
|
||||||
// for a previously unopened scene.
|
// for a previously unopened scene.
|
||||||
// => throwing an exception would only show it for one object
|
// (and only do SetActive if this was actually a scene object)
|
||||||
// because this function would return afterwards.
|
|
||||||
if (identity.sceneId != 0)
|
if (identity.sceneId != 0)
|
||||||
{
|
{
|
||||||
|
// set scene hash
|
||||||
identity.SetSceneIdSceneHashPartInternal();
|
identity.SetSceneIdSceneHashPartInternal();
|
||||||
}
|
|
||||||
else Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
|
|
||||||
|
|
||||||
// disable it
|
// disable it
|
||||||
// note: NetworkIdentity.OnDisable adds itself to the
|
// note: NetworkIdentity.OnDisable adds itself to the
|
||||||
@ -72,6 +71,11 @@ public static void OnPostProcessScene()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// throwing an exception would only show it for one object
|
||||||
|
// because this function would return afterwards.
|
||||||
|
else Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,22 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
static class PreprocessorDefine
|
static class PreprocessorDefine
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Symbols that will be added to the editor
|
|
||||||
/// </summary>
|
|
||||||
public static readonly string[] Symbols = new string[] {
|
|
||||||
"MIRROR",
|
|
||||||
"MIRROR_1726_OR_NEWER"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add define symbols as soon as Unity gets done compiling.
|
/// Add define symbols as soon as Unity gets done compiling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InitializeOnLoadMethod]
|
[InitializeOnLoadMethod]
|
||||||
static void AddDefineSymbols()
|
static void AddDefineSymbols()
|
||||||
{
|
{
|
||||||
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
HashSet<string> defines = new HashSet<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'))
|
||||||
List<string> allDefines = definesString.Split(';').ToList();
|
{
|
||||||
allDefines.AddRange(Symbols.Except(allDefines));
|
"MIRROR",
|
||||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(
|
"MIRROR_1726_OR_NEWER"
|
||||||
EditorUserBuildSettings.selectedBuildTargetGroup,
|
};
|
||||||
string.Join(";", allDefines.ToArray()));
|
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, string.Join(";", defines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,7 +438,7 @@ public static int GetChannelId(CustomAttribute ca)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker)
|
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize)
|
||||||
{
|
{
|
||||||
// check for Hook function
|
// check for Hook function
|
||||||
MethodDefinition foundMethod;
|
MethodDefinition foundMethod;
|
||||||
@ -457,21 +457,21 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker)
|
|||||||
// move in and out of range repeatedly)
|
// move in and out of range repeatedly)
|
||||||
FieldDefinition netIdField = m_SyncVarNetIds[syncVar];
|
FieldDefinition netIdField = m_SyncVarNetIds[syncVar];
|
||||||
|
|
||||||
if (foundMethod == null)
|
VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type);
|
||||||
{
|
deserialize.Body.Variables.Add(tmpValue);
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
|
||||||
|
// read id and store in a local variable
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32));
|
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkReaderReadPacked32));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
|
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
|
||||||
}
|
|
||||||
else
|
if (foundMethod != null)
|
||||||
{
|
{
|
||||||
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
|
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
|
||||||
// because we send/receive the netID, not the GameObject/NetworkIdentity
|
// because we send/receive the netID, not the GameObject/NetworkIdentity
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
|
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
|
||||||
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
|
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
|
||||||
@ -480,6 +480,10 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker)
|
|||||||
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
|
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
|
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
|
||||||
}
|
}
|
||||||
|
// set the netid field
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -489,23 +493,25 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker)
|
|||||||
Weaver.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types.");
|
Weaver.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType);
|
||||||
|
deserialize.Body.Variables.Add(tmpValue);
|
||||||
|
|
||||||
if (foundMethod == null)
|
// read value and put it in a local variable
|
||||||
{
|
|
||||||
// just assign value
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
|
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
|
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
|
||||||
}
|
|
||||||
else
|
if (foundMethod != null)
|
||||||
{
|
{
|
||||||
// call hook instead
|
// call hook
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
|
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
|
||||||
}
|
}
|
||||||
|
// set the property
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -533,6 +539,10 @@ void GenerateDeSerialization()
|
|||||||
serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
|
serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
|
||||||
serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
|
serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
|
||||||
ILProcessor serWorker = serialize.Body.GetILProcessor();
|
ILProcessor serWorker = serialize.Body.GetILProcessor();
|
||||||
|
// setup local for dirty bits
|
||||||
|
serialize.Body.InitLocals = true;
|
||||||
|
VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
|
||||||
|
serialize.Body.Variables.Add(dirtyBitsLocal);
|
||||||
|
|
||||||
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.CurrentAssembly, "OnDeserialize");
|
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.CurrentAssembly, "OnDeserialize");
|
||||||
if (baseDeserialize != null)
|
if (baseDeserialize != null)
|
||||||
@ -551,7 +561,7 @@ void GenerateDeSerialization()
|
|||||||
|
|
||||||
foreach (FieldDefinition syncVar in m_SyncVars)
|
foreach (FieldDefinition syncVar in m_SyncVars)
|
||||||
{
|
{
|
||||||
DeserializeField(syncVar, serWorker);
|
DeserializeField(syncVar, serWorker, serialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
||||||
@ -559,10 +569,6 @@ void GenerateDeSerialization()
|
|||||||
// Generates: end if (initialState);
|
// Generates: end if (initialState);
|
||||||
serWorker.Append(initialStateLabel);
|
serWorker.Append(initialStateLabel);
|
||||||
|
|
||||||
// setup local for dirty bits
|
|
||||||
serialize.Body.InitLocals = true;
|
|
||||||
VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
|
|
||||||
serialize.Body.Variables.Add(dirtyBitsLocal);
|
|
||||||
|
|
||||||
// get dirty bits
|
// get dirty bits
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
||||||
@ -581,7 +587,7 @@ void GenerateDeSerialization()
|
|||||||
serWorker.Append(serWorker.Create(OpCodes.And));
|
serWorker.Append(serWorker.Create(OpCodes.And));
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
|
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
|
||||||
|
|
||||||
DeserializeField(syncVar, serWorker);
|
DeserializeField(syncVar, serWorker, serialize);
|
||||||
|
|
||||||
serWorker.Append(varLabel);
|
serWorker.Append(varLabel);
|
||||||
dirtyBit += 1;
|
dirtyBit += 1;
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
// this class generates OnSerialize/OnDeserialize for SyncLists
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
namespace Mirror.Weaver
|
||||||
|
{
|
||||||
|
static class SyncDictionaryProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates serialization methods for synclists
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="td">The synclist class</param>
|
||||||
|
public static void Process(TypeDefinition td)
|
||||||
|
{
|
||||||
|
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
|
||||||
|
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 5c4d04450e91c438385de7300abef1b6
|
guid: 29e4a45f69822462ab0b15adda962a29
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
18
Assets/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
Normal file
18
Assets/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// this class generates OnSerialize/OnDeserialize for SyncLists
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
namespace Mirror.Weaver
|
||||||
|
{
|
||||||
|
static class SyncListProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates serialization methods for synclists
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="td">The synclist class</param>
|
||||||
|
public static void Process(TypeDefinition td)
|
||||||
|
{
|
||||||
|
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeItem", "DeserializeItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 9589e903d4e98490fb1157762a307fd7
|
guid: 4f3445268e45d437fac325837aff3246
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
@ -1,158 +0,0 @@
|
|||||||
// this class generates OnSerialize/OnDeserialize for SyncListStructs
|
|
||||||
using Mono.Cecil;
|
|
||||||
using Mono.Cecil.Cil;
|
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
|
||||||
{
|
|
||||||
static class SyncListStructProcessor
|
|
||||||
{
|
|
||||||
public static void Process(TypeDefinition td)
|
|
||||||
{
|
|
||||||
// find item type
|
|
||||||
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
|
|
||||||
if (gt.GenericArguments.Count == 0)
|
|
||||||
{
|
|
||||||
Weaver.Error("SyncListStructProcessor no generic args");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[0]);
|
|
||||||
|
|
||||||
Weaver.DLog(td, "SyncListStructProcessor Start item:" + itemType.FullName);
|
|
||||||
|
|
||||||
Weaver.ResetRecursionCount();
|
|
||||||
MethodReference writeItemFunc = GenerateSerialization(td, itemType);
|
|
||||||
if (Weaver.WeavingFailed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodReference readItemFunc = GenerateDeserialization(td, itemType);
|
|
||||||
|
|
||||||
if (readItemFunc == null || writeItemFunc == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Weaver.DLog(td, "SyncListStructProcessor Done");
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialization of individual element
|
|
||||||
static MethodReference GenerateSerialization(TypeDefinition td, TypeReference itemType)
|
|
||||||
{
|
|
||||||
Weaver.DLog(td, " GenerateSerialization");
|
|
||||||
foreach (var m in td.Methods)
|
|
||||||
{
|
|
||||||
if (m.Name == "SerializeItem")
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodDefinition serializeFunc = new MethodDefinition("SerializeItem", MethodAttributes.Public |
|
|
||||||
MethodAttributes.Virtual |
|
|
||||||
MethodAttributes.Public |
|
|
||||||
MethodAttributes.HideBySig,
|
|
||||||
Weaver.voidType);
|
|
||||||
|
|
||||||
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
|
|
||||||
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
|
|
||||||
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
|
|
||||||
|
|
||||||
if (itemType.IsGenericInstance)
|
|
||||||
{
|
|
||||||
Weaver.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Struct passed into SyncListStruct<T> can't have generic parameters");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (FieldDefinition field in itemType.Resolve().Fields)
|
|
||||||
{
|
|
||||||
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field);
|
|
||||||
TypeDefinition ft = importedField.FieldType.Resolve();
|
|
||||||
|
|
||||||
if (ft.HasGenericParameters)
|
|
||||||
{
|
|
||||||
Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot have generic parameters.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ft.IsInterface)
|
|
||||||
{
|
|
||||||
Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot be an interface.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodReference writeFunc = Weaver.GetWriteFunc(field.FieldType);
|
|
||||||
if (writeFunc != null)
|
|
||||||
{
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldfld, importedField));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member variables must be basic types.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
|
||||||
|
|
||||||
td.Methods.Add(serializeFunc);
|
|
||||||
return serializeFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MethodReference GenerateDeserialization(TypeDefinition td, TypeReference itemType)
|
|
||||||
{
|
|
||||||
Weaver.DLog(td, " GenerateDeserialization");
|
|
||||||
foreach (var m in td.Methods)
|
|
||||||
{
|
|
||||||
if (m.Name == "DeserializeItem")
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodDefinition serializeFunc = new MethodDefinition("DeserializeItem", MethodAttributes.Public |
|
|
||||||
MethodAttributes.Virtual |
|
|
||||||
MethodAttributes.Public |
|
|
||||||
MethodAttributes.HideBySig,
|
|
||||||
itemType);
|
|
||||||
|
|
||||||
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
|
|
||||||
|
|
||||||
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
|
|
||||||
|
|
||||||
serWorker.Body.InitLocals = true;
|
|
||||||
serWorker.Body.Variables.Add(new VariableDefinition(itemType));
|
|
||||||
|
|
||||||
// init item instance
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldloca, 0));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Initobj, itemType));
|
|
||||||
|
|
||||||
foreach (FieldDefinition field in itemType.Resolve().Fields)
|
|
||||||
{
|
|
||||||
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field);
|
|
||||||
TypeDefinition ft = importedField.FieldType.Resolve();
|
|
||||||
|
|
||||||
MethodReference readerFunc = Weaver.GetReadFunc(field.FieldType);
|
|
||||||
if (readerFunc != null)
|
|
||||||
{
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldloca, 0));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Stfld, importedField));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + ft + "]. [SyncListStruct] member variables must be basic types.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
|
|
||||||
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
|
||||||
|
|
||||||
td.Methods.Add(serializeFunc);
|
|
||||||
return serializeFunc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
124
Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
Normal file
124
Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
namespace Mirror.Weaver
|
||||||
|
{
|
||||||
|
public static class SyncObjectProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the serialization and deserialization methods for a specified generic argument
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="td">The type of the class that needs serialization methods</param>
|
||||||
|
/// <param name="genericArgument">Which generic argument to serialize, 0 is the first one</param>
|
||||||
|
/// <param name="serializeMethod">The name of the serialize method</param>
|
||||||
|
/// <param name="deserializeMethod">The name of the deserialize method</param>
|
||||||
|
public static void GenerateSerialization(TypeDefinition td, int genericArgument, string serializeMethod, string deserializeMethod)
|
||||||
|
{
|
||||||
|
// find item type
|
||||||
|
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
|
||||||
|
if (gt.GenericArguments.Count <= genericArgument)
|
||||||
|
{
|
||||||
|
Weaver.Error("SyncObjectProcessor no generic args");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[genericArgument]);
|
||||||
|
|
||||||
|
Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);
|
||||||
|
|
||||||
|
Weaver.ResetRecursionCount();
|
||||||
|
MethodReference writeItemFunc = GenerateSerialization(serializeMethod, td, itemType);
|
||||||
|
if (Weaver.WeavingFailed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodReference readItemFunc = GenerateDeserialization(deserializeMethod, td, itemType);
|
||||||
|
|
||||||
|
if (readItemFunc == null || writeItemFunc == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Weaver.DLog(td, "SyncObjectProcessor Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialization of individual element
|
||||||
|
static MethodReference GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType)
|
||||||
|
{
|
||||||
|
Weaver.DLog(td, " GenerateSerialization");
|
||||||
|
foreach (var m in td.Methods)
|
||||||
|
{
|
||||||
|
if (m.Name == methodName)
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
|
||||||
|
MethodAttributes.Virtual |
|
||||||
|
MethodAttributes.Public |
|
||||||
|
MethodAttributes.HideBySig,
|
||||||
|
Weaver.voidType);
|
||||||
|
|
||||||
|
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
|
||||||
|
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
|
||||||
|
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
|
||||||
|
|
||||||
|
if (itemType.IsGenericInstance)
|
||||||
|
{
|
||||||
|
Weaver.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Can't have generic parameters");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodReference writeFunc = Weaver.GetWriteFunc(itemType);
|
||||||
|
if (writeFunc != null)
|
||||||
|
{
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + itemType + "/" + itemType.FullName + "]. Member variables must be basic types.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
||||||
|
|
||||||
|
td.Methods.Add(serializeFunc);
|
||||||
|
return serializeFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType)
|
||||||
|
{
|
||||||
|
Weaver.DLog(td, " GenerateDeserialization");
|
||||||
|
foreach (var m in td.Methods)
|
||||||
|
{
|
||||||
|
if (m.Name == methodName)
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
|
||||||
|
MethodAttributes.Virtual |
|
||||||
|
MethodAttributes.Public |
|
||||||
|
MethodAttributes.HideBySig,
|
||||||
|
itemType);
|
||||||
|
|
||||||
|
deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
|
||||||
|
|
||||||
|
ILProcessor serWorker = deserializeFunction.Body.GetILProcessor();
|
||||||
|
|
||||||
|
MethodReference readerFunc = Weaver.GetReadFunc(itemType);
|
||||||
|
if (readerFunc != null)
|
||||||
|
{
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
|
||||||
|
serWorker.Append(serWorker.Create(OpCodes.Ret));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + itemType + "]. Member variables must be basic types.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.Methods.Add(deserializeFunction);
|
||||||
|
return deserializeFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 93397916cae0248bc9294f863fa49f81
|
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
@ -110,19 +110,6 @@ public static MethodDefinition ProcessSyncVarSet(TypeDefinition td, FieldDefinit
|
|||||||
|
|
||||||
ILProcessor setWorker = set.Body.GetILProcessor();
|
ILProcessor setWorker = set.Body.GetILProcessor();
|
||||||
|
|
||||||
// this
|
|
||||||
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
|
|
||||||
|
|
||||||
// new value to set
|
|
||||||
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
|
|
||||||
|
|
||||||
// reference to field to set
|
|
||||||
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
|
|
||||||
setWorker.Append(setWorker.Create(OpCodes.Ldflda, fd));
|
|
||||||
|
|
||||||
// dirty bit
|
|
||||||
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit)); // 8 byte integer aka long
|
|
||||||
|
|
||||||
MethodDefinition hookFunctionMethod;
|
MethodDefinition hookFunctionMethod;
|
||||||
CheckForHookFunction(td, fd, out hookFunctionMethod);
|
CheckForHookFunction(td, fd, out hookFunctionMethod);
|
||||||
|
|
||||||
@ -154,6 +141,20 @@ public static MethodDefinition ProcessSyncVarSet(TypeDefinition td, FieldDefinit
|
|||||||
setWorker.Append(label);
|
setWorker.Append(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this
|
||||||
|
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
|
||||||
|
|
||||||
|
// new value to set
|
||||||
|
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
|
||||||
|
|
||||||
|
// reference to field to set
|
||||||
|
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
|
||||||
|
setWorker.Append(setWorker.Create(OpCodes.Ldflda, fd));
|
||||||
|
|
||||||
|
// dirty bit
|
||||||
|
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit)); // 8 byte integer aka long
|
||||||
|
|
||||||
|
|
||||||
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
|
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
|
||||||
{
|
{
|
||||||
// reference to netId Field to set
|
// reference to netId Field to set
|
||||||
|
@ -63,7 +63,8 @@ class Weaver
|
|||||||
public static TypeReference NetworkConnectionType;
|
public static TypeReference NetworkConnectionType;
|
||||||
|
|
||||||
public static TypeReference MessageBaseType;
|
public static TypeReference MessageBaseType;
|
||||||
public static TypeReference SyncListStructType;
|
public static TypeReference SyncListType;
|
||||||
|
public static TypeReference SyncDictionaryType;
|
||||||
|
|
||||||
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
||||||
public static TypeReference NetworkClientType;
|
public static TypeReference NetworkClientType;
|
||||||
@ -138,6 +139,8 @@ class Weaver
|
|||||||
public static TypeReference vector2Type;
|
public static TypeReference vector2Type;
|
||||||
public static TypeReference vector3Type;
|
public static TypeReference vector3Type;
|
||||||
public static TypeReference vector4Type;
|
public static TypeReference vector4Type;
|
||||||
|
public static TypeReference vector2IntType;
|
||||||
|
public static TypeReference vector3IntType;
|
||||||
public static TypeReference colorType;
|
public static TypeReference colorType;
|
||||||
public static TypeReference color32Type;
|
public static TypeReference color32Type;
|
||||||
public static TypeReference quaternionType;
|
public static TypeReference quaternionType;
|
||||||
@ -1013,6 +1016,8 @@ static void SetupUnityTypes()
|
|||||||
vector2Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector2");
|
vector2Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector2");
|
||||||
vector3Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector3");
|
vector3Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector3");
|
||||||
vector4Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector4");
|
vector4Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector4");
|
||||||
|
vector2IntType = UnityAssembly.MainModule.GetType("UnityEngine.Vector2Int");
|
||||||
|
vector3IntType = UnityAssembly.MainModule.GetType("UnityEngine.Vector3Int");
|
||||||
colorType = UnityAssembly.MainModule.GetType("UnityEngine.Color");
|
colorType = UnityAssembly.MainModule.GetType("UnityEngine.Color");
|
||||||
color32Type = UnityAssembly.MainModule.GetType("UnityEngine.Color32");
|
color32Type = UnityAssembly.MainModule.GetType("UnityEngine.Color32");
|
||||||
quaternionType = UnityAssembly.MainModule.GetType("UnityEngine.Quaternion");
|
quaternionType = UnityAssembly.MainModule.GetType("UnityEngine.Quaternion");
|
||||||
@ -1124,7 +1129,8 @@ static void SetupTargetTypes()
|
|||||||
NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);
|
NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);
|
||||||
|
|
||||||
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
|
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
|
||||||
SyncListStructType = NetAssembly.MainModule.GetType("Mirror.SyncListSTRUCT`1");
|
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
|
||||||
|
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
|
||||||
|
|
||||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
|
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
|
||||||
|
|
||||||
@ -1176,15 +1182,17 @@ static void SetupReadFunctions()
|
|||||||
{ uint64Type.FullName, NetworkReaderReadPacked64 },
|
{ uint64Type.FullName, NetworkReaderReadPacked64 },
|
||||||
{ int32Type.FullName, NetworkReaderReadPacked32 },
|
{ int32Type.FullName, NetworkReaderReadPacked32 },
|
||||||
{ uint32Type.FullName, NetworkReaderReadPacked32 },
|
{ uint32Type.FullName, NetworkReaderReadPacked32 },
|
||||||
{ int16Type.FullName, NetworkReaderReadPacked32 },
|
{ int16Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadInt16") },
|
||||||
{ uint16Type.FullName, NetworkReaderReadPacked32 },
|
{ uint16Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadUInt16") },
|
||||||
{ byteType.FullName, NetworkReaderReadPacked32 },
|
{ byteType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadByte") },
|
||||||
{ sbyteType.FullName, NetworkReaderReadPacked32 },
|
{ sbyteType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadSByte") },
|
||||||
{ charType.FullName, NetworkReaderReadPacked32 },
|
{ charType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadChar") },
|
||||||
{ decimalType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadDecimal") },
|
{ decimalType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadDecimal") },
|
||||||
{ vector2Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector2") },
|
{ vector2Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector2") },
|
||||||
{ vector3Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector3") },
|
{ vector3Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector3") },
|
||||||
{ vector4Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector4") },
|
{ vector4Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector4") },
|
||||||
|
{ vector2IntType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector2Int") },
|
||||||
|
{ vector3IntType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector3Int") },
|
||||||
{ colorType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor") },
|
{ colorType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor") },
|
||||||
{ color32Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor32") },
|
{ color32Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor32") },
|
||||||
{ quaternionType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadQuaternion") },
|
{ quaternionType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadQuaternion") },
|
||||||
@ -1212,15 +1220,17 @@ static void SetupWriteFunctions()
|
|||||||
{ uint64Type.FullName, NetworkWriterWritePacked64 },
|
{ uint64Type.FullName, NetworkWriterWritePacked64 },
|
||||||
{ int32Type.FullName, NetworkWriterWritePacked32 },
|
{ int32Type.FullName, NetworkWriterWritePacked32 },
|
||||||
{ uint32Type.FullName, NetworkWriterWritePacked32 },
|
{ uint32Type.FullName, NetworkWriterWritePacked32 },
|
||||||
{ int16Type.FullName, NetworkWriterWritePacked32 },
|
{ int16Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", int16Type) },
|
||||||
{ uint16Type.FullName, NetworkWriterWritePacked32 },
|
{ uint16Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", uint16Type) },
|
||||||
{ byteType.FullName, NetworkWriterWritePacked32 },
|
{ byteType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", byteType) },
|
||||||
{ sbyteType.FullName, NetworkWriterWritePacked32 },
|
{ sbyteType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", sbyteType) },
|
||||||
{ charType.FullName, NetworkWriterWritePacked32 },
|
{ charType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", charType) },
|
||||||
{ decimalType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", decimalType) },
|
{ decimalType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", decimalType) },
|
||||||
{ vector2Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector2Type) },
|
{ vector2Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector2Type) },
|
||||||
{ vector3Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector3Type) },
|
{ vector3Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector3Type) },
|
||||||
{ vector4Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector4Type) },
|
{ vector4Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector4Type) },
|
||||||
|
{ vector2IntType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector2IntType) },
|
||||||
|
{ vector3IntType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector3IntType) },
|
||||||
{ colorType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", colorType) },
|
{ colorType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", colorType) },
|
||||||
{ color32Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", color32Type) },
|
{ color32Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", color32Type) },
|
||||||
{ quaternionType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", quaternionType) },
|
{ quaternionType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", quaternionType) },
|
||||||
@ -1347,7 +1357,7 @@ static bool CheckMessageBase(TypeDefinition td)
|
|||||||
return didWork;
|
return didWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool CheckSyncListStruct(TypeDefinition td)
|
static bool CheckSyncList(TypeDefinition td)
|
||||||
{
|
{
|
||||||
if (!td.IsClass)
|
if (!td.IsClass)
|
||||||
return false;
|
return false;
|
||||||
@ -1358,9 +1368,15 @@ static bool CheckSyncListStruct(TypeDefinition td)
|
|||||||
TypeReference parent = td.BaseType;
|
TypeReference parent = td.BaseType;
|
||||||
while (parent != null)
|
while (parent != null)
|
||||||
{
|
{
|
||||||
if (parent.FullName.StartsWith(SyncListStructType.FullName))
|
if (parent.FullName.StartsWith(SyncListType.FullName))
|
||||||
{
|
{
|
||||||
SyncListStructProcessor.Process(td);
|
SyncListProcessor.Process(td);
|
||||||
|
didWork = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (parent.FullName.StartsWith(SyncDictionaryType.FullName))
|
||||||
|
{
|
||||||
|
SyncDictionaryProcessor.Process(td);
|
||||||
didWork = true;
|
didWork = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1379,7 +1395,7 @@ static bool CheckSyncListStruct(TypeDefinition td)
|
|||||||
// check for embedded types
|
// check for embedded types
|
||||||
foreach (TypeDefinition embedded in td.NestedTypes)
|
foreach (TypeDefinition embedded in td.NestedTypes)
|
||||||
{
|
{
|
||||||
didWork |= CheckSyncListStruct(embedded);
|
didWork |= CheckSyncList(embedded);
|
||||||
}
|
}
|
||||||
|
|
||||||
return didWork;
|
return didWork;
|
||||||
@ -1414,7 +1430,7 @@ static bool Weave(string assName, IEnumerable<string> dependencies, IAssemblyRes
|
|||||||
{
|
{
|
||||||
if (pass == 0)
|
if (pass == 0)
|
||||||
{
|
{
|
||||||
didWork |= CheckSyncListStruct(td);
|
didWork |= CheckSyncList(td);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: f95b8a8cb007dd64980ee3c5d4258f95
|
guid: e192f90e0acbb41f88dfe3dba300a5c9
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
307
Assets/Mirror/Examples/ListServer/ListServer.cs
Normal file
307
Assets/Mirror/Examples/ListServer/ListServer.cs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
// add this component to the NetworkManager
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Mirror.Examples.ListServer
|
||||||
|
{
|
||||||
|
public class ServerStatus
|
||||||
|
{
|
||||||
|
public string ip;
|
||||||
|
//public ushort port; // <- not all transports use a port. assume default port. feel free to also send a port if needed.
|
||||||
|
public string title;
|
||||||
|
public ushort players;
|
||||||
|
public ushort capacity;
|
||||||
|
|
||||||
|
public int lastLatency = -1;
|
||||||
|
public Ping ping;
|
||||||
|
|
||||||
|
public ServerStatus(string ip, /*ushort port,*/ string title, ushort players, ushort capacity)
|
||||||
|
{
|
||||||
|
this.ip = ip;
|
||||||
|
//this.port = port;
|
||||||
|
this.title = title;
|
||||||
|
this.players = players;
|
||||||
|
this.capacity = capacity;
|
||||||
|
ping = new Ping(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireComponent(typeof(NetworkManager))]
|
||||||
|
public class ListServer : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Listen Server Connection")]
|
||||||
|
public string listServerIp = "127.0.0.1";
|
||||||
|
public ushort gameServerToListenPort = 8887;
|
||||||
|
public ushort clientToListenPort = 8888;
|
||||||
|
public string gameServerTitle = "Deathmatch";
|
||||||
|
|
||||||
|
Telepathy.Client gameServerToListenConnection = new Telepathy.Client();
|
||||||
|
Telepathy.Client clientToListenConnection = new Telepathy.Client();
|
||||||
|
|
||||||
|
[Header("UI")]
|
||||||
|
public GameObject mainPanel;
|
||||||
|
public Transform content;
|
||||||
|
public UIServerStatusSlot slotPrefab;
|
||||||
|
public Button serverAndPlayButton;
|
||||||
|
public Button serverOnlyButton;
|
||||||
|
public GameObject connectingPanel;
|
||||||
|
public Text connectingText;
|
||||||
|
public Button connectingCancelButton;
|
||||||
|
int connectingDots = 0;
|
||||||
|
|
||||||
|
// all the servers, stored as dict with unique ip key so we can
|
||||||
|
// update them more easily
|
||||||
|
// (use "ip:port" if port is needed)
|
||||||
|
Dictionary<string, ServerStatus> list = new Dictionary<string, ServerStatus>();
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
// examples
|
||||||
|
//list["127.0.0.1"] = new ServerStatus("127.0.0.1", "Deathmatch", 3, 10);
|
||||||
|
//list["192.168.0.1"] = new ServerStatus("192.168.0.1", "Free for all", 7, 10);
|
||||||
|
//list["172.217.22.3"] = new ServerStatus("172.217.22.3", "5vs5", 10, 10);
|
||||||
|
//list["172.217.16.142"] = new ServerStatus("172.217.16.142", "Hide & Seek Mod", 0, 10);
|
||||||
|
|
||||||
|
// Update once a second. no need to try to reconnect or read data
|
||||||
|
// in each Update call
|
||||||
|
// -> calling it more than 1/second would also cause significantly
|
||||||
|
// more broadcasts in the list server.
|
||||||
|
InvokeRepeating(nameof(Tick), 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsConnecting() => NetworkClient.active && !ClientScene.ready;
|
||||||
|
bool FullyConnected() => NetworkClient.active && ClientScene.ready;
|
||||||
|
|
||||||
|
// should we use the client to listen connection?
|
||||||
|
bool UseClientToListen()
|
||||||
|
{
|
||||||
|
return !NetworkManager.IsHeadless() && !NetworkServer.active && !FullyConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
// should we use the game server to listen connection?
|
||||||
|
bool UseGameServerToListen()
|
||||||
|
{
|
||||||
|
return NetworkServer.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tick()
|
||||||
|
{
|
||||||
|
TickGameServer();
|
||||||
|
TickClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// send server status to list server
|
||||||
|
void SendStatus()
|
||||||
|
{
|
||||||
|
BinaryWriter writer = new BinaryWriter(new MemoryStream());
|
||||||
|
|
||||||
|
// create message
|
||||||
|
// NOTE: you can send anything that you want, as long as you also
|
||||||
|
// receive it in ParseMessage
|
||||||
|
char[] titleChars = gameServerTitle.ToCharArray();
|
||||||
|
writer.Write((ushort)titleChars.Length);
|
||||||
|
writer.Write(titleChars);
|
||||||
|
writer.Write((ushort)NetworkServer.connections.Count);
|
||||||
|
writer.Write((ushort)NetworkManager.singleton.maxConnections);
|
||||||
|
|
||||||
|
// send it
|
||||||
|
writer.Flush();
|
||||||
|
gameServerToListenConnection.Send(((MemoryStream)writer.BaseStream).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TickGameServer()
|
||||||
|
{
|
||||||
|
// send server data to listen
|
||||||
|
if (UseGameServerToListen())
|
||||||
|
{
|
||||||
|
// connected yet?
|
||||||
|
if (gameServerToListenConnection.Connected)
|
||||||
|
{
|
||||||
|
SendStatus();
|
||||||
|
}
|
||||||
|
// otherwise try to connect
|
||||||
|
// (we may have just started the game)
|
||||||
|
else if (!gameServerToListenConnection.Connecting)
|
||||||
|
{
|
||||||
|
Debug.Log("Establishing game server to listen connection...");
|
||||||
|
gameServerToListenConnection.Connect(listServerIp, gameServerToListenPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shouldn't use game server, but still using it?
|
||||||
|
else if (gameServerToListenConnection.Connected)
|
||||||
|
{
|
||||||
|
gameServerToListenConnection.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseMessage(byte[] bytes)
|
||||||
|
{
|
||||||
|
// use binary reader because our NetworkReader uses custom string reading with bools
|
||||||
|
// => we don't use ReadString here because the listen server doesn't
|
||||||
|
// know C#'s '7-bit-length + utf8' encoding for strings
|
||||||
|
BinaryReader reader = new BinaryReader(new MemoryStream(bytes, false), Encoding.UTF8);
|
||||||
|
ushort ipLength = reader.ReadUInt16();
|
||||||
|
string ip = new string(reader.ReadChars(ipLength));
|
||||||
|
//ushort port = reader.ReadUInt16(); <- not all Transports use a port. assume default.
|
||||||
|
ushort titleLength = reader.ReadUInt16();
|
||||||
|
string title = new string(reader.ReadChars(titleLength));
|
||||||
|
ushort players = reader.ReadUInt16();
|
||||||
|
ushort capacity = reader.ReadUInt16();
|
||||||
|
//Debug.Log("PARSED: ip=" + ip + /*" port=" + port +*/ " title=" + title + " players=" + players + " capacity= " + capacity);
|
||||||
|
|
||||||
|
// build key
|
||||||
|
string key = ip/* + ":" + port*/;
|
||||||
|
|
||||||
|
// find existing or create new one
|
||||||
|
ServerStatus server;
|
||||||
|
if (list.TryGetValue(key, out server))
|
||||||
|
{
|
||||||
|
// refresh
|
||||||
|
server.title = title;
|
||||||
|
server.players = players;
|
||||||
|
server.capacity = capacity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create
|
||||||
|
server = new ServerStatus(ip, /*port,*/ title, players, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save
|
||||||
|
list[key] = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TickClient()
|
||||||
|
{
|
||||||
|
// receive client data from listen
|
||||||
|
if (UseClientToListen())
|
||||||
|
{
|
||||||
|
// connected yet?
|
||||||
|
if (clientToListenConnection.Connected)
|
||||||
|
{
|
||||||
|
// receive latest game server info
|
||||||
|
while (clientToListenConnection.GetNextMessage(out Telepathy.Message message))
|
||||||
|
{
|
||||||
|
// data message?
|
||||||
|
if (message.eventType == Telepathy.EventType.Data)
|
||||||
|
ParseMessage(message.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping again if previous ping finished
|
||||||
|
foreach (ServerStatus server in list.Values)
|
||||||
|
{
|
||||||
|
if (server.ping.isDone)
|
||||||
|
{
|
||||||
|
server.lastLatency = server.ping.time;
|
||||||
|
server.ping = new Ping(server.ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise try to connect
|
||||||
|
// (we may have just joined the menu/disconnect from game server)
|
||||||
|
else if (!clientToListenConnection.Connecting)
|
||||||
|
{
|
||||||
|
Debug.Log("Establishing client to listen connection...");
|
||||||
|
clientToListenConnection.Connect(listServerIp, clientToListenPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shouldn't use client, but still using it? (e.g. after joining)
|
||||||
|
else if (clientToListenConnection.Connected)
|
||||||
|
{
|
||||||
|
clientToListenConnection.Disconnect();
|
||||||
|
list.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh UI afterwards
|
||||||
|
OnUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiate/remove enough prefabs to match amount
|
||||||
|
public static void BalancePrefabs(GameObject prefab, int amount, Transform parent)
|
||||||
|
{
|
||||||
|
// instantiate until amount
|
||||||
|
for (int i = parent.childCount; i < amount; ++i)
|
||||||
|
{
|
||||||
|
GameObject go = GameObject.Instantiate(prefab);
|
||||||
|
go.transform.SetParent(parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete everything that's too much
|
||||||
|
// (backwards loop because Destroy changes childCount)
|
||||||
|
for (int i = parent.childCount-1; i >= amount; --i)
|
||||||
|
GameObject.Destroy(parent.GetChild(i).gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUI()
|
||||||
|
{
|
||||||
|
// only show while client not connected and server not started
|
||||||
|
if (!NetworkManager.singleton.isNetworkActive || IsConnecting())
|
||||||
|
{
|
||||||
|
mainPanel.SetActive(true);
|
||||||
|
|
||||||
|
// instantiate/destroy enough slots
|
||||||
|
BalancePrefabs(slotPrefab.gameObject, list.Count, content);
|
||||||
|
|
||||||
|
// refresh all members
|
||||||
|
for (int i = 0; i < list.Values.Count; ++i)
|
||||||
|
{
|
||||||
|
UIServerStatusSlot slot = content.GetChild(i).GetComponent<UIServerStatusSlot>();
|
||||||
|
ServerStatus server = list.Values.ToList()[i];
|
||||||
|
slot.titleText.text = server.title;
|
||||||
|
slot.playersText.text = server.players + "/" + server.capacity;
|
||||||
|
slot.latencyText.text = server.lastLatency != -1 ? server.lastLatency.ToString() : "...";
|
||||||
|
slot.addressText.text = server.ip;
|
||||||
|
slot.joinButton.interactable = !IsConnecting();
|
||||||
|
slot.joinButton.gameObject.SetActive(server.players < server.capacity);
|
||||||
|
slot.joinButton.onClick.RemoveAllListeners();
|
||||||
|
slot.joinButton.onClick.AddListener(() => {
|
||||||
|
NetworkManager.singleton.networkAddress = server.ip;
|
||||||
|
NetworkManager.singleton.StartClient();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// server buttons
|
||||||
|
serverAndPlayButton.interactable = !IsConnecting();
|
||||||
|
serverAndPlayButton.onClick.RemoveAllListeners();
|
||||||
|
serverAndPlayButton.onClick.AddListener(() => {
|
||||||
|
NetworkManager.singleton.StartHost();
|
||||||
|
});
|
||||||
|
|
||||||
|
serverOnlyButton.interactable = !IsConnecting();
|
||||||
|
serverOnlyButton.onClick.RemoveAllListeners();
|
||||||
|
serverOnlyButton.onClick.AddListener(() => {
|
||||||
|
NetworkManager.singleton.StartServer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else mainPanel.SetActive(false);
|
||||||
|
|
||||||
|
// show connecting panel while connecting
|
||||||
|
if (IsConnecting())
|
||||||
|
{
|
||||||
|
connectingPanel.SetActive(true);
|
||||||
|
|
||||||
|
// . => .. => ... => ....
|
||||||
|
connectingDots = ((connectingDots + 1) % 4);
|
||||||
|
connectingText.text = "Connecting" + new string('.', connectingDots);
|
||||||
|
|
||||||
|
// cancel button
|
||||||
|
connectingCancelButton.onClick.RemoveAllListeners();
|
||||||
|
connectingCancelButton.onClick.AddListener(NetworkManager.singleton.StopClient);
|
||||||
|
}
|
||||||
|
else connectingPanel.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnect everything when pressing Stop in the Editor
|
||||||
|
void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
if (gameServerToListenConnection.Connected)
|
||||||
|
gameServerToListenConnection.Disconnect();
|
||||||
|
if (clientToListenConnection.Connected)
|
||||||
|
clientToListenConnection.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Examples/ListServer/ListServer.cs.meta
Normal file
11
Assets/Mirror/Examples/ListServer/ListServer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 69f796b44735c414783d66f47b150c5f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Mirror/Examples/ListServer/Scenes.meta
Normal file
8
Assets/Mirror/Examples/ListServer/Scenes.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9826d673f42ad4c59b8fc27ae86729e1
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
BIN
Assets/Mirror/Examples/ListServer/Scenes/NavMesh.asset
Normal file
BIN
Assets/Mirror/Examples/ListServer/Scenes/NavMesh.asset
Normal file
Binary file not shown.
@ -1,8 +1,8 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 83612f89e0d5b404fbd99891bda78df4
|
guid: b7816657e55c04901be36ace7275ad85
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
mainObjectFileID: 25800000
|
mainObjectFileID: 23800000
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
3387
Assets/Mirror/Examples/ListServer/Scenes/Scene.unity
Normal file
3387
Assets/Mirror/Examples/ListServer/Scenes/Scene.unity
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ccdea83d270df4b078963a0243e41ac0
|
||||||
|
timeCreated: 1426587410
|
||||||
|
licenseType: Free
|
||||||
|
DefaultImporter:
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Mirror/Examples/ListServer/UI.meta
Normal file
8
Assets/Mirror/Examples/ListServer/UI.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d7b75cc57dee4425af050c10508257a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
651
Assets/Mirror/Examples/ListServer/UI/ServerStatusSlot.prefab
Normal file
651
Assets/Mirror/Examples/ListServer/UI/ServerStatusSlot.prefab
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &1009505356919436
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224428085307354514}
|
||||||
|
- component: {fileID: 222534217462828066}
|
||||||
|
- component: {fileID: 114077992825021022}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: TextPlayers
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224428085307354514
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1009505356919436}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 224813910264502154}
|
||||||
|
m_RootOrder: 1
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 60, y: 16}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222534217462828066
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1009505356919436}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114077992825021022
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1009505356919436}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_FontSize: 12
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 5
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: '[Players]'
|
||||||
|
--- !u!1 &1165846264137056
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224595697833923954}
|
||||||
|
- component: {fileID: 222565095748274674}
|
||||||
|
- component: {fileID: 114410999938197916}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: TextLatency
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224595697833923954
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1165846264137056}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 224813910264502154}
|
||||||
|
m_RootOrder: 2
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 60, y: 16}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222565095748274674
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1165846264137056}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114410999938197916
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1165846264137056}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_FontSize: 12
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 5
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: '[Latency]'
|
||||||
|
--- !u!1 &1259177336467004
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224813910264502154}
|
||||||
|
- component: {fileID: 222443701544564472}
|
||||||
|
- component: {fileID: 114424214131292870}
|
||||||
|
- component: {fileID: 114617214395066132}
|
||||||
|
- component: {fileID: 114618400118094574}
|
||||||
|
- component: {fileID: 114611570997501482}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: ServerStatusSlot
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224813910264502154
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 224596263305646570}
|
||||||
|
- {fileID: 224428085307354514}
|
||||||
|
- {fileID: 224595697833923954}
|
||||||
|
- {fileID: 2588874080615256095}
|
||||||
|
- {fileID: 224530003443674914}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222443701544564472
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114424214131292870
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 0.2509804, g: 0.2509804, b: 0.2509804, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
--- !u!114 &114617214395066132
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 1741964061, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_HorizontalFit: 0
|
||||||
|
m_VerticalFit: 2
|
||||||
|
--- !u!114 &114618400118094574
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: -405508275, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Padding:
|
||||||
|
m_Left: 4
|
||||||
|
m_Right: 2
|
||||||
|
m_Top: 1
|
||||||
|
m_Bottom: 1
|
||||||
|
m_ChildAlignment: 0
|
||||||
|
m_Spacing: 0
|
||||||
|
m_ChildForceExpandWidth: 0
|
||||||
|
m_ChildForceExpandHeight: 0
|
||||||
|
m_ChildControlWidth: 0
|
||||||
|
m_ChildControlHeight: 0
|
||||||
|
--- !u!114 &114611570997501482
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1259177336467004}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 7f24d39c9182b4098bcfd0c2546d0bf2, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
titleText: {fileID: 114105517410707240}
|
||||||
|
playersText: {fileID: 114077992825021022}
|
||||||
|
latencyText: {fileID: 114410999938197916}
|
||||||
|
addressText: {fileID: 1711666012325280996}
|
||||||
|
joinButton: {fileID: 114358377111651776}
|
||||||
|
--- !u!1 &1368168976437814
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224596263305646570}
|
||||||
|
- component: {fileID: 222703717971346548}
|
||||||
|
- component: {fileID: 114105517410707240}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: TextTitle
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224596263305646570
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1368168976437814}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 224813910264502154}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 218, y: 16}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222703717971346548
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1368168976437814}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114105517410707240
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1368168976437814}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_FontSize: 12
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 3
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: '[Title]'
|
||||||
|
--- !u!1 &1462871638010074
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224530003443674914}
|
||||||
|
- component: {fileID: 222322555260831376}
|
||||||
|
- component: {fileID: 114653458098104780}
|
||||||
|
- component: {fileID: 114358377111651776}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: ButtonJoin
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224530003443674914
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1462871638010074}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 224615151935076648}
|
||||||
|
m_Father: {fileID: 224813910264502154}
|
||||||
|
m_RootOrder: 4
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 40, y: 18}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222322555260831376
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1462871638010074}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114653458098104780
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1462871638010074}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
--- !u!114 &114358377111651776
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1462871638010074}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Navigation:
|
||||||
|
m_Mode: 3
|
||||||
|
m_SelectOnUp: {fileID: 0}
|
||||||
|
m_SelectOnDown: {fileID: 0}
|
||||||
|
m_SelectOnLeft: {fileID: 0}
|
||||||
|
m_SelectOnRight: {fileID: 0}
|
||||||
|
m_Transition: 1
|
||||||
|
m_Colors:
|
||||||
|
m_NormalColor: {r: 0.32156864, g: 0.32156864, b: 0.32156864, a: 1}
|
||||||
|
m_HighlightedColor: {r: 0.3529412, g: 0.3529412, b: 0.3529412, a: 1}
|
||||||
|
m_PressedColor: {r: 0.3882353, g: 0.3882353, b: 0.3882353, a: 1}
|
||||||
|
m_DisabledColor: {r: 0.3882353, g: 0.3882353, b: 0.3882353, a: 0.37254903}
|
||||||
|
m_ColorMultiplier: 1
|
||||||
|
m_FadeDuration: 0.1
|
||||||
|
m_SpriteState:
|
||||||
|
m_HighlightedSprite: {fileID: 0}
|
||||||
|
m_PressedSprite: {fileID: 0}
|
||||||
|
m_DisabledSprite: {fileID: 0}
|
||||||
|
m_AnimationTriggers:
|
||||||
|
m_NormalTrigger: Normal
|
||||||
|
m_HighlightedTrigger: Highlighted
|
||||||
|
m_PressedTrigger: Pressed
|
||||||
|
m_DisabledTrigger: Disabled
|
||||||
|
m_Interactable: 1
|
||||||
|
m_TargetGraphic: {fileID: 114653458098104780}
|
||||||
|
m_OnClick:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0,
|
||||||
|
Culture=neutral, PublicKeyToken=null
|
||||||
|
--- !u!1 &1575165076438694
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 224615151935076648}
|
||||||
|
- component: {fileID: 222903696298421472}
|
||||||
|
- component: {fileID: 114447744505293664}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Text
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &224615151935076648
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1575165076438694}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 224530003443674914}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &222903696298421472
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1575165076438694}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &114447744505293664
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1575165076438694}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_FontSize: 12
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 4
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: Join
|
||||||
|
--- !u!1 &2231260898927249423
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 2588874080615256095}
|
||||||
|
- component: {fileID: 2389080155505640677}
|
||||||
|
- component: {fileID: 1711666012325280996}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: TextAddress
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &2588874080615256095
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2231260898927249423}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 224813910264502154}
|
||||||
|
m_RootOrder: 3
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 130, y: 16}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &2389080155505640677
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2231260898927249423}
|
||||||
|
m_CullTransparentMesh: 0
|
||||||
|
--- !u!114 &1711666012325280996
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2231260898927249423}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||||
|
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_FontSize: 12
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 5
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: '[Address]'
|
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 611330311f6094567ad2f8012b470129
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
13
Assets/Mirror/Examples/ListServer/UI/UIServerStatusSlot.cs
Normal file
13
Assets/Mirror/Examples/ListServer/UI/UIServerStatusSlot.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Attach to the prefab for easier component access by the UI Scripts.
|
||||||
|
// Otherwise we would need slot.GetChild(0).GetComponentInChildren<Text> etc.
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
public class UIServerStatusSlot : MonoBehaviour
|
||||||
|
{
|
||||||
|
public Text titleText;
|
||||||
|
public Text playersText;
|
||||||
|
public Text latencyText;
|
||||||
|
public Text addressText;
|
||||||
|
public Button joinButton;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f24d39c9182b4098bcfd0c2546d0bf2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -51,8 +51,8 @@ LightmapSettings:
|
|||||||
m_IndirectOutputScale: 1
|
m_IndirectOutputScale: 1
|
||||||
m_AlbedoBoost: 1
|
m_AlbedoBoost: 1
|
||||||
m_EnvironmentLightingMode: 0
|
m_EnvironmentLightingMode: 0
|
||||||
m_EnableBakedLightmaps: 1
|
m_EnableBakedLightmaps: 0
|
||||||
m_EnableRealtimeLightmaps: 1
|
m_EnableRealtimeLightmaps: 0
|
||||||
m_LightmapEditorSettings:
|
m_LightmapEditorSettings:
|
||||||
serializedVersion: 10
|
serializedVersion: 10
|
||||||
m_Resolution: 2
|
m_Resolution: 2
|
||||||
@ -295,6 +295,7 @@ MonoBehaviour:
|
|||||||
m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
port: 7780
|
port: 7780
|
||||||
NoDelay: 1
|
NoDelay: 1
|
||||||
|
MaxMessageSize: 16384
|
||||||
--- !u!114 &2008127831
|
--- !u!114 &2008127831
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@ -329,6 +330,7 @@ MonoBehaviour:
|
|||||||
type: 3}
|
type: 3}
|
||||||
LobbyScene: LobbyScene
|
LobbyScene: LobbyScene
|
||||||
GameplayScene: OnlineScene
|
GameplayScene: OnlineScene
|
||||||
|
lobbySlots: []
|
||||||
allPlayersReady: 0
|
allPlayersReady: 0
|
||||||
--- !u!4 &2008127832
|
--- !u!4 &2008127832
|
||||||
Transform:
|
Transform:
|
||||||
|
@ -44,15 +44,15 @@ RenderSettings:
|
|||||||
LightmapSettings:
|
LightmapSettings:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
serializedVersion: 11
|
serializedVersion: 11
|
||||||
m_GIWorkflowMode: 1
|
m_GIWorkflowMode: 0
|
||||||
m_GISettings:
|
m_GISettings:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_BounceScale: 1
|
m_BounceScale: 1
|
||||||
m_IndirectOutputScale: 1
|
m_IndirectOutputScale: 1
|
||||||
m_AlbedoBoost: 1
|
m_AlbedoBoost: 1
|
||||||
m_EnvironmentLightingMode: 0
|
m_EnvironmentLightingMode: 0
|
||||||
m_EnableBakedLightmaps: 1
|
m_EnableBakedLightmaps: 0
|
||||||
m_EnableRealtimeLightmaps: 1
|
m_EnableRealtimeLightmaps: 0
|
||||||
m_LightmapEditorSettings:
|
m_LightmapEditorSettings:
|
||||||
serializedVersion: 10
|
serializedVersion: 10
|
||||||
m_Resolution: 2
|
m_Resolution: 2
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -1,89 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: ba6c3cbc7dcdf184a99a7d73e8762759
|
|
||||||
TextureImporter:
|
|
||||||
fileIDToRecycleName:
|
|
||||||
8900000: generatedCubemap
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 7
|
|
||||||
mipmaps:
|
|
||||||
mipMapMode: 0
|
|
||||||
enableMipMap: 1
|
|
||||||
sRGBTexture: 1
|
|
||||||
linearTexture: 0
|
|
||||||
fadeOut: 0
|
|
||||||
borderMipMap: 0
|
|
||||||
mipMapsPreserveCoverage: 0
|
|
||||||
alphaTestReferenceValue: 0.5
|
|
||||||
mipMapFadeDistanceStart: 1
|
|
||||||
mipMapFadeDistanceEnd: 3
|
|
||||||
bumpmap:
|
|
||||||
convertToNormalMap: 0
|
|
||||||
externalNormalMap: 0
|
|
||||||
heightScale: 0.25
|
|
||||||
normalMapFilter: 0
|
|
||||||
isReadable: 0
|
|
||||||
streamingMipmaps: 0
|
|
||||||
streamingMipmapsPriority: 0
|
|
||||||
grayScaleToAlpha: 0
|
|
||||||
generateCubemap: 6
|
|
||||||
cubemapConvolution: 1
|
|
||||||
seamlessCubemap: 1
|
|
||||||
textureFormat: 1
|
|
||||||
maxTextureSize: 2048
|
|
||||||
textureSettings:
|
|
||||||
serializedVersion: 2
|
|
||||||
filterMode: 2
|
|
||||||
aniso: 0
|
|
||||||
mipBias: 0
|
|
||||||
wrapU: 1
|
|
||||||
wrapV: 1
|
|
||||||
wrapW: 1
|
|
||||||
nPOTScale: 1
|
|
||||||
lightmap: 0
|
|
||||||
compressionQuality: 50
|
|
||||||
spriteMode: 0
|
|
||||||
spriteExtrude: 1
|
|
||||||
spriteMeshType: 1
|
|
||||||
alignment: 0
|
|
||||||
spritePivot: {x: 0.5, y: 0.5}
|
|
||||||
spritePixelsToUnits: 100
|
|
||||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
spriteGenerateFallbackPhysicsShape: 1
|
|
||||||
alphaUsage: 1
|
|
||||||
alphaIsTransparency: 0
|
|
||||||
spriteTessellationDetail: -1
|
|
||||||
textureType: 0
|
|
||||||
textureShape: 2
|
|
||||||
singleChannelComponent: 0
|
|
||||||
maxTextureSizeSet: 0
|
|
||||||
compressionQualitySet: 0
|
|
||||||
textureFormatSet: 0
|
|
||||||
platformSettings:
|
|
||||||
- serializedVersion: 2
|
|
||||||
buildTarget: DefaultTexturePlatform
|
|
||||||
maxTextureSize: 2048
|
|
||||||
resizeAlgorithm: 0
|
|
||||||
textureFormat: -1
|
|
||||||
textureCompression: 1
|
|
||||||
compressionQuality: 100
|
|
||||||
crunchedCompression: 0
|
|
||||||
allowsAlphaSplitting: 0
|
|
||||||
overridden: 0
|
|
||||||
androidETC2FallbackOverride: 0
|
|
||||||
spriteSheet:
|
|
||||||
serializedVersion: 2
|
|
||||||
sprites: []
|
|
||||||
outline: []
|
|
||||||
physicsShape: []
|
|
||||||
bones: []
|
|
||||||
spriteID:
|
|
||||||
vertices: []
|
|
||||||
indices:
|
|
||||||
edges: []
|
|
||||||
weights: []
|
|
||||||
spritePackingTag:
|
|
||||||
pSDRemoveMatte: 0
|
|
||||||
pSDShowRemoveMatteOption: 0
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
@ -38,21 +38,21 @@ RenderSettings:
|
|||||||
m_ReflectionIntensity: 1
|
m_ReflectionIntensity: 1
|
||||||
m_CustomReflection: {fileID: 0}
|
m_CustomReflection: {fileID: 0}
|
||||||
m_Sun: {fileID: 0}
|
m_Sun: {fileID: 0}
|
||||||
m_IndirectSpecularColor: {r: 0.4616949, g: 0.5124162, b: 0.5899328, a: 1}
|
m_IndirectSpecularColor: {r: 0.17276844, g: 0.21589246, b: 0.2978263, a: 1}
|
||||||
m_UseRadianceAmbientProbe: 0
|
m_UseRadianceAmbientProbe: 0
|
||||||
--- !u!157 &3
|
--- !u!157 &3
|
||||||
LightmapSettings:
|
LightmapSettings:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
serializedVersion: 11
|
serializedVersion: 11
|
||||||
m_GIWorkflowMode: 1
|
m_GIWorkflowMode: 0
|
||||||
m_GISettings:
|
m_GISettings:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_BounceScale: 1
|
m_BounceScale: 1
|
||||||
m_IndirectOutputScale: 1
|
m_IndirectOutputScale: 1
|
||||||
m_AlbedoBoost: 1
|
m_AlbedoBoost: 1
|
||||||
m_EnvironmentLightingMode: 0
|
m_EnvironmentLightingMode: 0
|
||||||
m_EnableBakedLightmaps: 1
|
m_EnableBakedLightmaps: 0
|
||||||
m_EnableRealtimeLightmaps: 1
|
m_EnableRealtimeLightmaps: 0
|
||||||
m_LightmapEditorSettings:
|
m_LightmapEditorSettings:
|
||||||
serializedVersion: 10
|
serializedVersion: 10
|
||||||
m_Resolution: 2
|
m_Resolution: 2
|
||||||
@ -203,7 +203,7 @@ Light:
|
|||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
serializedVersion: 8
|
serializedVersion: 8
|
||||||
m_Type: 1
|
m_Type: 1
|
||||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
m_Color: {r: 0.990566, g: 0.9496818, b: 0.82702917, a: 1}
|
||||||
m_Intensity: 1
|
m_Intensity: 1
|
||||||
m_Range: 10
|
m_Range: 10
|
||||||
m_SpotAngle: 30
|
m_SpotAngle: 30
|
||||||
@ -212,7 +212,7 @@ Light:
|
|||||||
m_Type: 2
|
m_Type: 2
|
||||||
m_Resolution: -1
|
m_Resolution: -1
|
||||||
m_CustomResolution: -1
|
m_CustomResolution: -1
|
||||||
m_Strength: 1
|
m_Strength: 0.7
|
||||||
m_Bias: 0.05
|
m_Bias: 0.05
|
||||||
m_NormalBias: 0.4
|
m_NormalBias: 0.4
|
||||||
m_NearPlane: 0.2
|
m_NearPlane: 0.2
|
||||||
|
@ -6,10 +6,11 @@ public class Ball : NetworkBehaviour
|
|||||||
{
|
{
|
||||||
public float speed = 30;
|
public float speed = 30;
|
||||||
|
|
||||||
public override void OnStartServer()
|
public void Start()
|
||||||
{
|
{
|
||||||
// only simulate ball physics on server
|
// only simulate ball physics on server
|
||||||
GetComponent<Rigidbody2D>().simulated = true;
|
GetComponent<Rigidbody2D>().simulated = isServer;
|
||||||
|
if (isServer)
|
||||||
GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
|
GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Mirror;
|
|
||||||
|
|
||||||
|
namespace Mirror.Examples.Tanks
|
||||||
|
{
|
||||||
public class Projectile : NetworkBehaviour
|
public class Projectile : NetworkBehaviour
|
||||||
{
|
{
|
||||||
public float destroyAfter = 5;
|
public float destroyAfter = 5;
|
||||||
@ -34,3 +35,4 @@ void OnTriggerEnter(Collider co)
|
|||||||
NetworkServer.Destroy(gameObject);
|
NetworkServer.Destroy(gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AI;
|
using UnityEngine.AI;
|
||||||
|
|
||||||
namespace Mirror.Examples.Movement
|
namespace Mirror.Examples.Tanks
|
||||||
{
|
{
|
||||||
public class Tank : NetworkBehaviour
|
public class Tank : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,9 @@ namespace Mirror
|
|||||||
{
|
{
|
||||||
public static class ClientScene
|
public static class ClientScene
|
||||||
{
|
{
|
||||||
static bool s_IsSpawnFinished;
|
static bool isSpawnFinished;
|
||||||
|
|
||||||
static List<uint> s_PendingOwnerNetIds = new List<uint>();
|
static HashSet<uint> pendingOwnerNetIds = new HashSet<uint>();
|
||||||
|
|
||||||
public static NetworkIdentity localPlayer { get; private set; }
|
public static NetworkIdentity localPlayer { get; private set; }
|
||||||
public static bool ready { get; internal set; }
|
public static bool ready { get; internal set; }
|
||||||
@ -22,18 +22,20 @@ public static class ClientScene
|
|||||||
public static Dictionary<ulong, NetworkIdentity> spawnableObjects;
|
public static Dictionary<ulong, NetworkIdentity> spawnableObjects;
|
||||||
|
|
||||||
// spawn handlers
|
// spawn handlers
|
||||||
internal static Dictionary<Guid, SpawnDelegate> spawnHandlers = new Dictionary<Guid, SpawnDelegate>();
|
static Dictionary<Guid, SpawnDelegate> spawnHandlers = new Dictionary<Guid, SpawnDelegate>();
|
||||||
internal static Dictionary<Guid, UnSpawnDelegate> unspawnHandlers = new Dictionary<Guid, UnSpawnDelegate>();
|
static Dictionary<Guid, UnSpawnDelegate> unspawnHandlers = new Dictionary<Guid, UnSpawnDelegate>();
|
||||||
|
|
||||||
|
// this is never called, and if we do call it in NetworkClient.Shutdown
|
||||||
|
// then the client's player object won't be removed after disconnecting!
|
||||||
internal static void Shutdown()
|
internal static void Shutdown()
|
||||||
{
|
{
|
||||||
NetworkIdentity.spawned.Clear();
|
NetworkIdentity.spawned.Clear();
|
||||||
ClearSpawners();
|
ClearSpawners();
|
||||||
s_PendingOwnerNetIds.Clear();
|
pendingOwnerNetIds.Clear();
|
||||||
spawnableObjects = null;
|
spawnableObjects = null;
|
||||||
readyConnection = null;
|
readyConnection = null;
|
||||||
ready = false;
|
ready = false;
|
||||||
s_IsSpawnFinished = false;
|
isSpawnFinished = false;
|
||||||
|
|
||||||
Transport.activeTransport.ClientDisconnect();
|
Transport.activeTransport.ClientDisconnect();
|
||||||
}
|
}
|
||||||
@ -48,7 +50,7 @@ internal static void InternalAddPlayer(NetworkIdentity identity)
|
|||||||
localPlayer = identity;
|
localPlayer = identity;
|
||||||
if (readyConnection != null)
|
if (readyConnection != null)
|
||||||
{
|
{
|
||||||
readyConnection.SetPlayerController(identity);
|
readyConnection.playerController = identity;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -106,7 +108,7 @@ public static bool RemovePlayer()
|
|||||||
|
|
||||||
Object.Destroy(readyConnection.playerController.gameObject);
|
Object.Destroy(readyConnection.playerController.gameObject);
|
||||||
|
|
||||||
readyConnection.RemovePlayerController();
|
readyConnection.playerController = null;
|
||||||
localPlayer = null;
|
localPlayer = null;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -136,14 +138,6 @@ public static bool Ready(NetworkConnection conn)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NetworkClient ConnectLocalServer()
|
|
||||||
{
|
|
||||||
LocalClient newClient = new LocalClient();
|
|
||||||
NetworkServer.ActivateLocalClientScene();
|
|
||||||
newClient.InternalConnectLocalServer();
|
|
||||||
return newClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void HandleClientDisconnect(NetworkConnection conn)
|
internal static void HandleClientDisconnect(NetworkConnection conn)
|
||||||
{
|
{
|
||||||
if (readyConnection == conn && ready)
|
if (readyConnection == conn && ready)
|
||||||
@ -153,7 +147,7 @@ internal static void HandleClientDisconnect(NetworkConnection conn)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool ConsiderForSpawning(NetworkIdentity identity)
|
static bool ConsiderForSpawning(NetworkIdentity identity)
|
||||||
{
|
{
|
||||||
// not spawned yet, not hidden, etc.?
|
// not spawned yet, not hidden, etc.?
|
||||||
return !identity.gameObject.activeSelf &&
|
return !identity.gameObject.activeSelf &&
|
||||||
@ -172,7 +166,7 @@ public static void PrepareToSpawnSceneObjects()
|
|||||||
.ToDictionary(identity => identity.sceneId, identity => identity);
|
.ToDictionary(identity => identity.sceneId, identity => identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static NetworkIdentity SpawnSceneObject(ulong sceneId)
|
static NetworkIdentity SpawnSceneObject(ulong sceneId)
|
||||||
{
|
{
|
||||||
if (spawnableObjects.TryGetValue(sceneId, out NetworkIdentity identity))
|
if (spawnableObjects.TryGetValue(sceneId, out NetworkIdentity identity))
|
||||||
{
|
{
|
||||||
@ -184,7 +178,7 @@ internal static NetworkIdentity SpawnSceneObject(ulong sceneId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spawn handlers and prefabs
|
// spawn handlers and prefabs
|
||||||
internal static bool GetPrefab(Guid assetId, out GameObject prefab)
|
static bool GetPrefab(Guid assetId, out GameObject prefab)
|
||||||
{
|
{
|
||||||
prefab = null;
|
prefab = null;
|
||||||
return assetId != Guid.Empty &&
|
return assetId != Guid.Empty &&
|
||||||
@ -295,7 +289,7 @@ public static void ClearSpawners()
|
|||||||
unspawnHandlers.Clear();
|
unspawnHandlers.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool InvokeUnSpawnHandler(Guid assetId, GameObject obj)
|
static bool InvokeUnSpawnHandler(Guid assetId, GameObject obj)
|
||||||
{
|
{
|
||||||
if (unspawnHandlers.TryGetValue(assetId, out UnSpawnDelegate handler) && handler != null)
|
if (unspawnHandlers.TryGetValue(assetId, out UnSpawnDelegate handler) && handler != null)
|
||||||
{
|
{
|
||||||
@ -307,9 +301,8 @@ internal static bool InvokeUnSpawnHandler(Guid assetId, GameObject obj)
|
|||||||
|
|
||||||
public static void DestroyAllClientObjects()
|
public static void DestroyAllClientObjects()
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<uint, NetworkIdentity> kvp in NetworkIdentity.spawned)
|
foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
|
||||||
{
|
{
|
||||||
NetworkIdentity identity = kvp.Value;
|
|
||||||
if (identity != null && identity.gameObject != null)
|
if (identity != null && identity.gameObject != null)
|
||||||
{
|
{
|
||||||
if (!InvokeUnSpawnHandler(identity.assetId, identity.gameObject))
|
if (!InvokeUnSpawnHandler(identity.assetId, identity.gameObject))
|
||||||
@ -358,7 +351,7 @@ static void ApplySpawnPayload(NetworkIdentity identity, Vector3 position, Quater
|
|||||||
NetworkIdentity.spawned[netId] = identity;
|
NetworkIdentity.spawned[netId] = identity;
|
||||||
|
|
||||||
// objects spawned as part of initial state are started on a second pass
|
// objects spawned as part of initial state are started on a second pass
|
||||||
if (s_IsSpawnFinished)
|
if (isSpawnFinished)
|
||||||
{
|
{
|
||||||
identity.isClient = true;
|
identity.isClient = true;
|
||||||
identity.OnStartClient();
|
identity.OnStartClient();
|
||||||
@ -457,7 +450,7 @@ internal static void OnObjectSpawnStarted(NetworkConnection conn, ObjectSpawnSta
|
|||||||
if (LogFilter.Debug) Debug.Log("SpawnStarted");
|
if (LogFilter.Debug) Debug.Log("SpawnStarted");
|
||||||
|
|
||||||
PrepareToSpawnSceneObjects();
|
PrepareToSpawnSceneObjects();
|
||||||
s_IsSpawnFinished = false;
|
isSpawnFinished = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void OnObjectSpawnFinished(NetworkConnection conn, ObjectSpawnFinishedMessage msg)
|
internal static void OnObjectSpawnFinished(NetworkConnection conn, ObjectSpawnFinishedMessage msg)
|
||||||
@ -475,7 +468,7 @@ internal static void OnObjectSpawnFinished(NetworkConnection conn, ObjectSpawnFi
|
|||||||
CheckForOwner(identity);
|
CheckForOwner(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s_IsSpawnFinished = true;
|
isSpawnFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void OnObjectHide(NetworkConnection conn, ObjectHideMessage msg)
|
internal static void OnObjectHide(NetworkConnection conn, ObjectHideMessage msg)
|
||||||
@ -616,16 +609,13 @@ internal static void OnOwnerMessage(NetworkConnection conn, OwnerMessage msg)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
s_PendingOwnerNetIds.Add(msg.netId);
|
pendingOwnerNetIds.Add(msg.netId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CheckForOwner(NetworkIdentity identity)
|
static void CheckForOwner(NetworkIdentity identity)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < s_PendingOwnerNetIds.Count; i++)
|
if (pendingOwnerNetIds.Contains(identity.netId))
|
||||||
{
|
|
||||||
uint pendingOwnerNetId = s_PendingOwnerNetIds[i];
|
|
||||||
if (pendingOwnerNetId == identity.netId)
|
|
||||||
{
|
{
|
||||||
// found owner, turn into a local player
|
// found owner, turn into a local player
|
||||||
|
|
||||||
@ -641,9 +631,7 @@ static void CheckForOwner(NetworkIdentity identity)
|
|||||||
}
|
}
|
||||||
InternalAddPlayer(identity);
|
InternalAddPlayer(identity);
|
||||||
|
|
||||||
s_PendingOwnerNetIds.RemoveAt(i);
|
pendingOwnerNetIds.Remove(identity.netId);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Mirror
|
|
||||||
{
|
|
||||||
sealed class LocalClient : NetworkClient
|
|
||||||
{
|
|
||||||
// local client in host mode might call Cmds/Rpcs during Update, but we
|
|
||||||
// want to apply them in LateUpdate like all other Transport messages
|
|
||||||
// to avoid race conditions. keep packets in Queue until LateUpdate.
|
|
||||||
internal Queue<byte[]> packetQueue = new Queue<byte[]>();
|
|
||||||
|
|
||||||
internal void InternalConnectLocalServer()
|
|
||||||
{
|
|
||||||
connection = new ULocalConnectionToServer();
|
|
||||||
SetHandlers(connection);
|
|
||||||
connection.connectionId = NetworkServer.AddLocalClient(this);
|
|
||||||
connectState = ConnectState.Connected;
|
|
||||||
|
|
||||||
active = true;
|
|
||||||
RegisterSystemHandlers(true);
|
|
||||||
|
|
||||||
packetQueue.Enqueue(MessagePacker.Pack(new ConnectMessage()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Disconnect()
|
|
||||||
{
|
|
||||||
connectState = ConnectState.Disconnected;
|
|
||||||
ClientScene.HandleClientDisconnect(connection);
|
|
||||||
if (isConnected)
|
|
||||||
{
|
|
||||||
packetQueue.Enqueue(MessagePacker.Pack(new DisconnectMessage()));
|
|
||||||
}
|
|
||||||
NetworkServer.RemoveLocalClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Update()
|
|
||||||
{
|
|
||||||
// process internal messages so they are applied at the correct time
|
|
||||||
while (packetQueue.Count > 0)
|
|
||||||
{
|
|
||||||
byte[] packet = packetQueue.Dequeue();
|
|
||||||
OnDataReceived(packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the server to set the LocalClient's LocalPlayer object during NetworkServer.AddPlayer()
|
|
||||||
internal void AddLocalPlayer(NetworkIdentity localPlayer)
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + connection.connectionId);
|
|
||||||
connection.isReady = true;
|
|
||||||
connection.SetPlayerController(localPlayer);
|
|
||||||
if (localPlayer != null)
|
|
||||||
{
|
|
||||||
localPlayer.isClient = true;
|
|
||||||
NetworkIdentity.spawned[localPlayer.netId] = localPlayer;
|
|
||||||
localPlayer.connectionToServer = connection;
|
|
||||||
}
|
|
||||||
// there is no SystemOwnerMessage for local client. add to ClientScene here instead
|
|
||||||
ClientScene.InternalAddPlayer(localPlayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,16 +6,15 @@ namespace Mirror
|
|||||||
// sending messages on this connection causes the client's handler function to be invoked directly
|
// sending messages on this connection causes the client's handler function to be invoked directly
|
||||||
class ULocalConnectionToClient : NetworkConnection
|
class ULocalConnectionToClient : NetworkConnection
|
||||||
{
|
{
|
||||||
public LocalClient localClient;
|
public ULocalConnectionToClient() : base ("localClient")
|
||||||
|
|
||||||
public ULocalConnectionToClient(LocalClient localClient) : base ("localClient")
|
|
||||||
{
|
{
|
||||||
this.localClient = localClient;
|
// local player always has connectionId == 0
|
||||||
|
connectionId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
|
internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
|
||||||
{
|
{
|
||||||
localClient.packetQueue.Enqueue(bytes);
|
NetworkClient.localClientPacketQueue.Enqueue(bytes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,7 +23,11 @@ internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultR
|
|||||||
// send messages on this connection causes the server's handler function to be invoked directly.
|
// send messages on this connection causes the server's handler function to be invoked directly.
|
||||||
internal class ULocalConnectionToServer : NetworkConnection
|
internal class ULocalConnectionToServer : NetworkConnection
|
||||||
{
|
{
|
||||||
public ULocalConnectionToServer() : base("localServer") {}
|
public ULocalConnectionToServer() : base("localServer")
|
||||||
|
{
|
||||||
|
// local player always has connectionId == 0
|
||||||
|
connectionId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
|
internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ public static class MessagePacker
|
|||||||
// avoid large amounts of allocations.
|
// avoid large amounts of allocations.
|
||||||
static NetworkWriter packWriter = new NetworkWriter();
|
static NetworkWriter packWriter = new NetworkWriter();
|
||||||
|
|
||||||
public static int GetId<T>() where T : MessageBase
|
public static int GetId<T>() where T : IMessageBase
|
||||||
{
|
{
|
||||||
// paul: 16 bits is enough to avoid collisions
|
// paul: 16 bits is enough to avoid collisions
|
||||||
// - keeps the message size small because it gets varinted
|
// - keeps the message size small because it gets varinted
|
||||||
@ -46,7 +46,7 @@ public static byte[] PackMessage(int msgType, MessageBase msg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pack message before sending
|
// pack message before sending
|
||||||
public static byte[] Pack<T>(T message) where T : MessageBase
|
public static byte[] Pack<T>(T message) where T : IMessageBase
|
||||||
{
|
{
|
||||||
// reset cached writer length and position
|
// reset cached writer length and position
|
||||||
packWriter.SetLength(0);
|
packWriter.SetLength(0);
|
||||||
@ -63,7 +63,7 @@ public static byte[] Pack<T>(T message) where T : MessageBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unpack a message we received
|
// unpack a message we received
|
||||||
public static T Unpack<T>(byte[] data) where T : MessageBase, new()
|
public static T Unpack<T>(byte[] data) where T : IMessageBase, new()
|
||||||
{
|
{
|
||||||
NetworkReader reader = new NetworkReader(data);
|
NetworkReader reader = new NetworkReader(data);
|
||||||
|
|
||||||
|
@ -3,9 +3,14 @@
|
|||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
// This can't be an interface because users don't need to implement the
|
public interface IMessageBase
|
||||||
// serialization functions, we'll code generate it for them when they omit it.
|
{
|
||||||
public abstract class MessageBase
|
void Deserialize(NetworkReader reader);
|
||||||
|
|
||||||
|
void Serialize(NetworkWriter writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MessageBase : IMessageBase
|
||||||
{
|
{
|
||||||
// De-serialize the contents of the reader into this message
|
// De-serialize the contents of the reader into this message
|
||||||
public virtual void Deserialize(NetworkReader reader) {}
|
public virtual void Deserialize(NetworkReader reader) {}
|
||||||
|
@ -4,72 +4,105 @@
|
|||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
public class NetworkClient
|
public enum ConnectState
|
||||||
{
|
|
||||||
// the client (can be a regular NetworkClient or a LocalClient)
|
|
||||||
public static NetworkClient singleton;
|
|
||||||
|
|
||||||
[Obsolete("Use NetworkClient.singleton instead. There is always exactly one client.")]
|
|
||||||
public static List<NetworkClient> allClients => new List<NetworkClient>{singleton};
|
|
||||||
|
|
||||||
public readonly Dictionary<int, NetworkMessageDelegate> handlers = new Dictionary<int, NetworkMessageDelegate>();
|
|
||||||
|
|
||||||
public NetworkConnection connection { get; protected set; }
|
|
||||||
|
|
||||||
protected enum ConnectState
|
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Connecting,
|
Connecting,
|
||||||
Connected,
|
Connected,
|
||||||
Disconnected
|
Disconnected
|
||||||
}
|
}
|
||||||
protected ConnectState connectState = ConnectState.None;
|
|
||||||
|
|
||||||
public string serverIp { get; private set; } = "";
|
// TODO make fully static after removing obsoleted singleton!
|
||||||
|
public class NetworkClient
|
||||||
|
{
|
||||||
|
[Obsolete("Use NetworkClient directly. Singleton isn't needed anymore, all functions are static now. For example: NetworkClient.Send(message) instead of NetworkClient.singleton.Send(message).")]
|
||||||
|
public static NetworkClient singleton = new NetworkClient();
|
||||||
|
|
||||||
|
[Obsolete("Use NetworkClient directly instead. There is always exactly one client.")]
|
||||||
|
public static List<NetworkClient> allClients => new List<NetworkClient>{singleton};
|
||||||
|
|
||||||
|
public static readonly Dictionary<int, NetworkMessageDelegate> handlers = new Dictionary<int, NetworkMessageDelegate>();
|
||||||
|
|
||||||
|
public static NetworkConnection connection { get; internal set; }
|
||||||
|
|
||||||
|
internal static ConnectState connectState = ConnectState.None;
|
||||||
|
|
||||||
|
public static string serverIp => connection.address;
|
||||||
|
|
||||||
// active is true while a client is connecting/connected
|
// active is true while a client is connecting/connected
|
||||||
// (= while the network is active)
|
// (= while the network is active)
|
||||||
public static bool active { get; protected set; }
|
public static bool active { get; internal set; }
|
||||||
|
|
||||||
public bool isConnected => connectState == ConnectState.Connected;
|
public static bool isConnected => connectState == ConnectState.Connected;
|
||||||
|
|
||||||
public NetworkClient()
|
// NetworkClient can connect to local server in host mode too
|
||||||
{
|
public static bool isLocalClient => connection is ULocalConnectionToServer;
|
||||||
if (LogFilter.Debug) Debug.Log("Client created version " + Version.Current);
|
|
||||||
|
|
||||||
if (singleton != null)
|
// local client in host mode might call Cmds/Rpcs during Update, but we
|
||||||
{
|
// want to apply them in LateUpdate like all other Transport messages
|
||||||
Debug.LogError("NetworkClient: can only create one!");
|
// to avoid race conditions. keep packets in Queue until LateUpdate.
|
||||||
return;
|
internal static Queue<byte[]> localClientPacketQueue = new Queue<byte[]>();
|
||||||
}
|
|
||||||
singleton = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetHandlers(NetworkConnection conn)
|
internal static void SetHandlers(NetworkConnection conn)
|
||||||
{
|
{
|
||||||
conn.SetHandlers(handlers);
|
conn.SetHandlers(handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Connect(string ip)
|
// connect remote
|
||||||
|
public static void Connect(string address)
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("Client Connect: " + ip);
|
if (LogFilter.Debug) Debug.Log("Client Connect: " + address);
|
||||||
|
|
||||||
active = true;
|
active = true;
|
||||||
RegisterSystemHandlers(false);
|
RegisterSystemHandlers(false);
|
||||||
Transport.activeTransport.enabled = true;
|
Transport.activeTransport.enabled = true;
|
||||||
InitializeTransportHandlers();
|
InitializeTransportHandlers();
|
||||||
|
|
||||||
serverIp = ip;
|
|
||||||
|
|
||||||
connectState = ConnectState.Connecting;
|
connectState = ConnectState.Connecting;
|
||||||
Transport.activeTransport.ClientConnect(ip);
|
Transport.activeTransport.ClientConnect(address);
|
||||||
|
|
||||||
// setup all the handlers
|
// setup all the handlers
|
||||||
connection = new NetworkConnection(serverIp, 0);
|
connection = new NetworkConnection(address, 0);
|
||||||
connection.SetHandlers(handlers);
|
connection.SetHandlers(handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeTransportHandlers()
|
// connect host mode
|
||||||
|
internal static void ConnectLocalServer()
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("Client Connect Local Server");
|
||||||
|
active = true;
|
||||||
|
RegisterSystemHandlers(true);
|
||||||
|
|
||||||
|
connectState = ConnectState.Connected;
|
||||||
|
|
||||||
|
// create local connection to server
|
||||||
|
connection = new ULocalConnectionToServer();
|
||||||
|
SetHandlers(connection);
|
||||||
|
|
||||||
|
// create server connection to local client
|
||||||
|
ULocalConnectionToClient connectionToClient = new ULocalConnectionToClient();
|
||||||
|
NetworkServer.SetLocalConnection(connectionToClient);
|
||||||
|
|
||||||
|
localClientPacketQueue.Enqueue(MessagePacker.Pack(new ConnectMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by the server to set the LocalClient's LocalPlayer object during NetworkServer.AddPlayer()
|
||||||
|
internal static void AddLocalPlayer(NetworkIdentity localPlayer)
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + connection.connectionId);
|
||||||
|
connection.isReady = true;
|
||||||
|
connection.playerController = localPlayer;
|
||||||
|
if (localPlayer != null)
|
||||||
|
{
|
||||||
|
localPlayer.isClient = true;
|
||||||
|
NetworkIdentity.spawned[localPlayer.netId] = localPlayer;
|
||||||
|
localPlayer.connectionToServer = connection;
|
||||||
|
}
|
||||||
|
// there is no SystemOwnerMessage for local client. add to ClientScene here instead
|
||||||
|
ClientScene.InternalAddPlayer(localPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeTransportHandlers()
|
||||||
{
|
{
|
||||||
Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
|
Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
|
||||||
Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
|
Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
|
||||||
@ -77,12 +110,12 @@ private void InitializeTransportHandlers()
|
|||||||
Transport.activeTransport.OnClientError.AddListener(OnError);
|
Transport.activeTransport.OnClientError.AddListener(OnError);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnError(Exception exception)
|
static void OnError(Exception exception)
|
||||||
{
|
{
|
||||||
Debug.LogException(exception);
|
Debug.LogException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDisconnected()
|
static void OnDisconnected()
|
||||||
{
|
{
|
||||||
connectState = ConnectState.Disconnected;
|
connectState = ConnectState.Disconnected;
|
||||||
|
|
||||||
@ -91,7 +124,7 @@ void OnDisconnected()
|
|||||||
connection?.InvokeHandler(new DisconnectMessage());
|
connection?.InvokeHandler(new DisconnectMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnDataReceived(byte[] data)
|
internal static void OnDataReceived(byte[] data)
|
||||||
{
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
@ -100,7 +133,7 @@ protected void OnDataReceived(byte[] data)
|
|||||||
else Debug.LogError("Skipped Data message handling because m_Connection is null.");
|
else Debug.LogError("Skipped Data message handling because m_Connection is null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnConnected()
|
static void OnConnected()
|
||||||
{
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
@ -110,16 +143,28 @@ void OnConnected()
|
|||||||
// the handler may want to send messages to the client
|
// the handler may want to send messages to the client
|
||||||
// thus we should set the connected state before calling the handler
|
// thus we should set the connected state before calling the handler
|
||||||
connectState = ConnectState.Connected;
|
connectState = ConnectState.Connected;
|
||||||
NetworkTime.UpdateClient(this);
|
NetworkTime.UpdateClient();
|
||||||
connection.InvokeHandler(new ConnectMessage());
|
connection.InvokeHandler(new ConnectMessage());
|
||||||
}
|
}
|
||||||
else Debug.LogError("Skipped Connect message handling because m_Connection is null.");
|
else Debug.LogError("Skipped Connect message handling because m_Connection is null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Disconnect()
|
public static void Disconnect()
|
||||||
{
|
{
|
||||||
connectState = ConnectState.Disconnected;
|
connectState = ConnectState.Disconnected;
|
||||||
ClientScene.HandleClientDisconnect(connection);
|
ClientScene.HandleClientDisconnect(connection);
|
||||||
|
|
||||||
|
// local or remote connection?
|
||||||
|
if (isLocalClient)
|
||||||
|
{
|
||||||
|
if (isConnected)
|
||||||
|
{
|
||||||
|
localClientPacketQueue.Enqueue(MessagePacker.Pack(new DisconnectMessage()));
|
||||||
|
}
|
||||||
|
NetworkServer.RemoveLocalConnection();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
connection.Disconnect();
|
connection.Disconnect();
|
||||||
@ -131,8 +176,9 @@ public virtual void Disconnect()
|
|||||||
// the client's network is not active anymore.
|
// the client's network is not active anymore.
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RemoveTransportHandlers()
|
static void RemoveTransportHandlers()
|
||||||
{
|
{
|
||||||
// so that we don't register them more than once
|
// so that we don't register them more than once
|
||||||
Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
|
Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
|
||||||
@ -142,7 +188,7 @@ void RemoveTransportHandlers()
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use SendMessage<T> instead with no message id instead")]
|
[Obsolete("Use SendMessage<T> instead with no message id instead")]
|
||||||
public bool Send(short msgType, MessageBase msg)
|
public static bool Send(short msgType, MessageBase msg)
|
||||||
{
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
@ -157,7 +203,7 @@ public bool Send(short msgType, MessageBase msg)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Send<T>(T message) where T : MessageBase
|
public static bool Send<T>(T message) where T : IMessageBase
|
||||||
{
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
@ -172,12 +218,25 @@ public bool Send<T>(T message) where T : MessageBase
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void Update()
|
internal static void Update()
|
||||||
|
{
|
||||||
|
// local or remote connection?
|
||||||
|
if (isLocalClient)
|
||||||
|
{
|
||||||
|
// process internal messages so they are applied at the correct time
|
||||||
|
while (localClientPacketQueue.Count > 0)
|
||||||
|
{
|
||||||
|
byte[] packet = localClientPacketQueue.Dequeue();
|
||||||
|
OnDataReceived(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// only update things while connected
|
// only update things while connected
|
||||||
if (active && connectState == ConnectState.Connected)
|
if (active && connectState == ConnectState.Connected)
|
||||||
{
|
{
|
||||||
NetworkTime.UpdateClient(this);
|
NetworkTime.UpdateClient();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,12 +287,12 @@ void GenerateError(byte error)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[Obsolete("Use NetworkTime.rtt instead")]
|
[Obsolete("Use NetworkTime.rtt instead")]
|
||||||
public float GetRTT()
|
public static float GetRTT()
|
||||||
{
|
{
|
||||||
return (float)NetworkTime.rtt;
|
return (float)NetworkTime.rtt;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RegisterSystemHandlers(bool localClient)
|
internal static void RegisterSystemHandlers(bool localClient)
|
||||||
{
|
{
|
||||||
// local client / regular client react to some messages differently.
|
// local client / regular client react to some messages differently.
|
||||||
// but we still need to add handlers for all of them to avoid
|
// but we still need to add handlers for all of them to avoid
|
||||||
@ -268,27 +327,27 @@ internal void RegisterSystemHandlers(bool localClient)
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use RegisterHandler<T> instead")]
|
[Obsolete("Use RegisterHandler<T> instead")]
|
||||||
public void RegisterHandler(int msgType, NetworkMessageDelegate handler)
|
public static void RegisterHandler(int msgType, NetworkMessageDelegate handler)
|
||||||
{
|
{
|
||||||
if (handlers.ContainsKey(msgType))
|
if (handlers.ContainsKey(msgType))
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + msgType);
|
if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + handler.ToString() + " - " + msgType);
|
||||||
}
|
}
|
||||||
handlers[msgType] = handler;
|
handlers[msgType] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use RegisterHandler<T> instead")]
|
[Obsolete("Use RegisterHandler<T> instead")]
|
||||||
public void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler)
|
public static void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler)
|
||||||
{
|
{
|
||||||
RegisterHandler((int)msgType, handler);
|
RegisterHandler((int)msgType, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterHandler<T>(Action<NetworkConnection, T> handler) where T : MessageBase, new()
|
public static void RegisterHandler<T>(Action<NetworkConnection, T> handler) where T : IMessageBase, new()
|
||||||
{
|
{
|
||||||
int msgType = MessagePacker.GetId<T>();
|
int msgType = MessagePacker.GetId<T>();
|
||||||
if (handlers.ContainsKey(msgType))
|
if (handlers.ContainsKey(msgType))
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + msgType);
|
if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + handler.ToString() + " - " + msgType);
|
||||||
}
|
}
|
||||||
handlers[msgType] = (networkMessage) =>
|
handlers[msgType] = (networkMessage) =>
|
||||||
{
|
{
|
||||||
@ -297,42 +356,34 @@ public void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use UnregisterHandler<T> instead")]
|
[Obsolete("Use UnregisterHandler<T> instead")]
|
||||||
public void UnregisterHandler(int msgType)
|
public static void UnregisterHandler(int msgType)
|
||||||
{
|
{
|
||||||
handlers.Remove(msgType);
|
handlers.Remove(msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use UnregisterHandler<T> instead")]
|
[Obsolete("Use UnregisterHandler<T> instead")]
|
||||||
public void UnregisterHandler(MsgType msgType)
|
public static void UnregisterHandler(MsgType msgType)
|
||||||
{
|
{
|
||||||
UnregisterHandler((int)msgType);
|
UnregisterHandler((int)msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterHandler<T>() where T : MessageBase
|
public static void UnregisterHandler<T>() where T : IMessageBase
|
||||||
{
|
{
|
||||||
// use int to minimize collisions
|
// use int to minimize collisions
|
||||||
int msgType = MessagePacker.GetId<T>();
|
int msgType = MessagePacker.GetId<T>();
|
||||||
handlers.Remove(msgType);
|
handlers.Remove(msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void UpdateClient()
|
public static void Shutdown()
|
||||||
{
|
|
||||||
singleton?.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shutdown()
|
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("Shutting down client.");
|
if (LogFilter.Debug) Debug.Log("Shutting down client.");
|
||||||
singleton = null;
|
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Call NetworkClient.Shutdown() instead. There is only one client.")]
|
||||||
public static void ShutdownAll()
|
public static void ShutdownAll()
|
||||||
{
|
{
|
||||||
singleton?.Shutdown();
|
Shutdown();
|
||||||
singleton = null;
|
|
||||||
active = false;
|
|
||||||
ClientScene.Shutdown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ public class NetworkConnection : IDisposable
|
|||||||
public bool isReady;
|
public bool isReady;
|
||||||
public string address;
|
public string address;
|
||||||
public float lastMessageTime;
|
public float lastMessageTime;
|
||||||
public NetworkIdentity playerController { get; private set; }
|
public NetworkIdentity playerController { get; internal set; }
|
||||||
public HashSet<uint> clientOwnedObjects;
|
public HashSet<uint> clientOwnedObjects;
|
||||||
public bool logNetworkMessages;
|
public bool logNetworkMessages;
|
||||||
|
|
||||||
@ -93,7 +93,6 @@ public void Disconnect()
|
|||||||
Transport.activeTransport.ClientDisconnect();
|
Transport.activeTransport.ClientDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove observers
|
|
||||||
RemoveObservers();
|
RemoveObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,16 +115,6 @@ public void UnregisterHandler(short msgType)
|
|||||||
m_MessageHandlers.Remove(msgType);
|
m_MessageHandlers.Remove(msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetPlayerController(NetworkIdentity player)
|
|
||||||
{
|
|
||||||
playerController = player;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RemovePlayerController()
|
|
||||||
{
|
|
||||||
playerController = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("use Send<T> instead")]
|
[Obsolete("use Send<T> instead")]
|
||||||
public virtual bool Send(int msgType, MessageBase msg, int channelId = Channels.DefaultReliable)
|
public virtual bool Send(int msgType, MessageBase msg, int channelId = Channels.DefaultReliable)
|
||||||
{
|
{
|
||||||
@ -134,7 +123,7 @@ public virtual bool Send(int msgType, MessageBase msg, int channelId = Channels.
|
|||||||
return SendBytes(message, channelId);
|
return SendBytes(message, channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Send<T>(T msg, int channelId = Channels.DefaultReliable) where T: MessageBase
|
public virtual bool Send<T>(T msg, int channelId = Channels.DefaultReliable) where T: IMessageBase
|
||||||
{
|
{
|
||||||
// pack message and send
|
// pack message and send
|
||||||
byte[] message = MessagePacker.Pack(msg);
|
byte[] message = MessagePacker.Pack(msg);
|
||||||
@ -219,7 +208,7 @@ public bool InvokeHandler(int msgType, NetworkReader reader)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InvokeHandler<T>(T msg) where T : MessageBase
|
public bool InvokeHandler<T>(T msg) where T : IMessageBase
|
||||||
{
|
{
|
||||||
int msgType = MessagePacker.GetId<T>();
|
int msgType = MessagePacker.GetId<T>();
|
||||||
byte[] data = MessagePacker.Pack(msg);
|
byte[] data = MessagePacker.Pack(msg);
|
||||||
|
@ -127,6 +127,7 @@ internal void ForceAuthority(bool authority)
|
|||||||
|
|
||||||
static uint s_NextNetworkId = 1;
|
static uint s_NextNetworkId = 1;
|
||||||
internal static uint GetNextNetworkId() => s_NextNetworkId++;
|
internal static uint GetNextNetworkId() => s_NextNetworkId++;
|
||||||
|
public static void ResetNextNetworkId() => s_NextNetworkId = 1;
|
||||||
|
|
||||||
public delegate void ClientAuthorityCallback(NetworkConnection conn, NetworkIdentity identity, bool authorityState);
|
public delegate void ClientAuthorityCallback(NetworkConnection conn, NetworkIdentity identity, bool authorityState);
|
||||||
public static ClientAuthorityCallback clientAuthorityCallback;
|
public static ClientAuthorityCallback clientAuthorityCallback;
|
||||||
@ -150,6 +151,34 @@ internal void RemoveObserverInternal(NetworkConnection conn)
|
|||||||
observers?.Remove(conn.connectionId);
|
observers?.Remove(conn.connectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
// detect runtime sceneId duplicates, e.g. if a user tries to
|
||||||
|
// Instantiate a sceneId object at runtime. if we don't detect it,
|
||||||
|
// then the client won't know which of the two objects to use for a
|
||||||
|
// SpawnSceneObject message, and it's likely going to be the wrong
|
||||||
|
// object.
|
||||||
|
//
|
||||||
|
// This might happen if for example we have a Dungeon GameObject
|
||||||
|
// which contains a Skeleton monster as child, and when a player
|
||||||
|
// runs into the Dungeon we create a Dungeon Instance of that
|
||||||
|
// Dungeon, which would duplicate a scene object.
|
||||||
|
//
|
||||||
|
// see also: https://github.com/vis2k/Mirror/issues/384
|
||||||
|
if (Application.isPlaying && sceneId != 0)
|
||||||
|
{
|
||||||
|
if (sceneIds.TryGetValue(sceneId, out NetworkIdentity existing) && existing != this)
|
||||||
|
{
|
||||||
|
Debug.LogError(name + "'s sceneId: " + sceneId.ToString("X") + " is already taken by: " + existing.name + ". Don't call Instantiate for NetworkIdentities that were in the scene since the beginning (aka scene objects). Otherwise the client won't know which object to use for a SpawnSceneObject message.");
|
||||||
|
Destroy(gameObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sceneIds[sceneId] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OnValidate()
|
void OnValidate()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@ -329,6 +358,12 @@ void SetupIDs()
|
|||||||
|
|
||||||
void OnDestroy()
|
void OnDestroy()
|
||||||
{
|
{
|
||||||
|
// remove from sceneIds
|
||||||
|
// -> remove with (0xFFFFFFFFFFFFFFFF) and without (0x00000000FFFFFFFF)
|
||||||
|
// sceneHash to be 100% safe.
|
||||||
|
sceneIds.Remove(sceneId);
|
||||||
|
sceneIds.Remove(sceneId & 0x00000000FFFFFFFF);
|
||||||
|
|
||||||
if (m_IsServer && NetworkServer.active)
|
if (m_IsServer && NetworkServer.active)
|
||||||
{
|
{
|
||||||
NetworkServer.Destroy(gameObject);
|
NetworkServer.Destroy(gameObject);
|
||||||
@ -409,7 +444,7 @@ internal void OnStartClient()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnStartAuthority()
|
void OnStartAuthority()
|
||||||
{
|
{
|
||||||
if (m_NetworkBehaviours == null)
|
if (m_NetworkBehaviours == null)
|
||||||
{
|
{
|
||||||
@ -430,7 +465,7 @@ internal void OnStartAuthority()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnStopAuthority()
|
void OnStopAuthority()
|
||||||
{
|
{
|
||||||
foreach (NetworkBehaviour comp in NetworkBehaviours)
|
foreach (NetworkBehaviour comp in NetworkBehaviours)
|
||||||
{
|
{
|
||||||
@ -482,7 +517,7 @@ internal bool OnCheckObserver(NetworkConnection conn)
|
|||||||
// -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers
|
// -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers
|
||||||
// -> it will be impossible to read too many or too few bytes in OnDeserialize
|
// -> it will be impossible to read too many or too few bytes in OnDeserialize
|
||||||
// -> we can properly track down errors
|
// -> we can properly track down errors
|
||||||
internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
|
bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
|
||||||
{
|
{
|
||||||
// write placeholder length bytes
|
// write placeholder length bytes
|
||||||
// (jumping back later is WAY faster than allocating a temporary
|
// (jumping back later is WAY faster than allocating a temporary
|
||||||
@ -560,7 +595,7 @@ internal byte[] OnSerializeAllSafely(bool initialState)
|
|||||||
return onSerializeWriter.ToArray();
|
return onSerializeWriter.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong GetDirtyMask(NetworkBehaviour[] components, bool initialState)
|
ulong GetDirtyMask(NetworkBehaviour[] components, bool initialState)
|
||||||
{
|
{
|
||||||
// loop through all components only once and then write dirty+payload into the writer afterwards
|
// loop through all components only once and then write dirty+payload into the writer afterwards
|
||||||
ulong dirtyComponentsMask = 0L;
|
ulong dirtyComponentsMask = 0L;
|
||||||
@ -576,7 +611,7 @@ private ulong GetDirtyMask(NetworkBehaviour[] components, bool initialState)
|
|||||||
return dirtyComponentsMask;
|
return dirtyComponentsMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
|
void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
|
||||||
{
|
{
|
||||||
// read header as 4 bytes
|
// read header as 4 bytes
|
||||||
int contentSize = reader.ReadInt32();
|
int contentSize = reader.ReadInt32();
|
||||||
@ -605,7 +640,7 @@ internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnDeserializeAllSafely(NetworkBehaviour[] components, NetworkReader reader, bool initialState)
|
void OnDeserializeAllSafely(NetworkBehaviour[] components, NetworkReader reader, bool initialState)
|
||||||
{
|
{
|
||||||
// read component dirty mask
|
// read component dirty mask
|
||||||
ulong dirtyComponentsMask = reader.ReadPackedUInt64();
|
ulong dirtyComponentsMask = reader.ReadPackedUInt64();
|
||||||
@ -635,7 +670,7 @@ internal void HandleClientAuthority(bool authority)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper function to handle SyncEvent/Command/Rpc
|
// helper function to handle SyncEvent/Command/Rpc
|
||||||
internal void HandleRemoteCall(int componentIndex, int functionHash, MirrorInvokeType invokeType, NetworkReader reader)
|
void HandleRemoteCall(int componentIndex, int functionHash, MirrorInvokeType invokeType, NetworkReader reader)
|
||||||
{
|
{
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
@ -754,7 +789,7 @@ internal void AddObserver(NetworkConnection conn)
|
|||||||
conn.AddToVisList(this);
|
conn.AddToVisList(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RemoveObserver(NetworkConnection conn)
|
void RemoveObserver(NetworkConnection conn)
|
||||||
{
|
{
|
||||||
if (observers == null)
|
if (observers == null)
|
||||||
return;
|
return;
|
||||||
@ -782,9 +817,8 @@ public void RebuildObservers(bool initialize)
|
|||||||
// none of the behaviours rebuilt our observers, use built-in rebuild method
|
// none of the behaviours rebuilt our observers, use built-in rebuild method
|
||||||
if (initialize)
|
if (initialize)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<int, NetworkConnection> kvp in NetworkServer.connections)
|
foreach (NetworkConnection conn in NetworkServer.connections.Values)
|
||||||
{
|
{
|
||||||
NetworkConnection conn = kvp.Value;
|
|
||||||
if (conn.isReady)
|
if (conn.isReady)
|
||||||
AddObserver(conn);
|
AddObserver(conn);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,8 @@ public class NetworkManager : MonoBehaviour
|
|||||||
public static string networkSceneName = "";
|
public static string networkSceneName = "";
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public bool isNetworkActive;
|
public bool isNetworkActive;
|
||||||
public NetworkClient client;
|
[Obsolete("Use NetworkClient directly, it will be made static soon. For example, use NetworkClient.Send(message) instead of NetworkManager.client.Send(message)")]
|
||||||
|
public NetworkClient client => NetworkClient.singleton;
|
||||||
static int s_StartPositionIndex;
|
static int s_StartPositionIndex;
|
||||||
|
|
||||||
public static NetworkManager singleton;
|
public static NetworkManager singleton;
|
||||||
@ -75,13 +76,16 @@ public class NetworkManager : MonoBehaviour
|
|||||||
// virtual so that inheriting classes' Awake() can call base.Awake() too
|
// virtual so that inheriting classes' Awake() can call base.Awake() too
|
||||||
public virtual void Awake()
|
public virtual void Awake()
|
||||||
{
|
{
|
||||||
Debug.Log("Thank you for using Mirror! https://forum.unity.com/threads/mirror-networking-for-unity-aka-hlapi-community-edition.425437/");
|
Debug.Log("Thank you for using Mirror! https://mirror-networking.com");
|
||||||
|
|
||||||
// Set the networkSceneName to prevent a scene reload
|
// Set the networkSceneName to prevent a scene reload
|
||||||
// if client connection to server fails.
|
// if client connection to server fails.
|
||||||
networkSceneName = offlineScene;
|
networkSceneName = offlineScene;
|
||||||
|
|
||||||
InitializeSingleton();
|
InitializeSingleton();
|
||||||
|
|
||||||
|
// setup OnSceneLoaded callback
|
||||||
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// headless mode detection
|
// headless mode detection
|
||||||
@ -146,6 +150,33 @@ public virtual void Start()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// support additive scene loads:
|
||||||
|
// NetworkScenePostProcess disables all scene objects on load, and
|
||||||
|
// * NetworkServer.SpawnObjects enables them again on the server when
|
||||||
|
// calling OnStartServer
|
||||||
|
// * ClientScene.PrepareToSpawnSceneObjects enables them again on the
|
||||||
|
// client after the server sends ObjectSpawnStartedMessage to client
|
||||||
|
// in SpawnObserversForConnection. this is only called when the
|
||||||
|
// client joins, so we need to rebuild scene objects manually again
|
||||||
|
// TODO merge this with FinishLoadScene()?
|
||||||
|
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||||
|
{
|
||||||
|
if (mode == LoadSceneMode.Additive)
|
||||||
|
{
|
||||||
|
if (NetworkServer.active)
|
||||||
|
{
|
||||||
|
// TODO only respawn the server objects from that scene later!
|
||||||
|
NetworkServer.SpawnObjects();
|
||||||
|
Debug.Log("Respawned Server objects after additive scene load: " + scene.name);
|
||||||
|
}
|
||||||
|
if (NetworkClient.active)
|
||||||
|
{
|
||||||
|
ClientScene.PrepareToSpawnSceneObjects();
|
||||||
|
Debug.Log("Rebuild Client spawnableObjects after additive scene load: " + scene.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NetworkIdentity.UNetStaticUpdate is called from UnityEngine while LLAPI network is active.
|
// NetworkIdentity.UNetStaticUpdate is called from UnityEngine while LLAPI network is active.
|
||||||
// if we want TCP then we need to call it manually. probably best from NetworkManager, although this means
|
// if we want TCP then we need to call it manually. probably best from NetworkManager, although this means
|
||||||
// that we can't use NetworkServer/NetworkClient without a NetworkManager invoking Update anymore.
|
// that we can't use NetworkServer/NetworkClient without a NetworkManager invoking Update anymore.
|
||||||
@ -157,7 +188,7 @@ public virtual void LateUpdate()
|
|||||||
// -> we don't only call while Client/Server.Connected, because then we would stop if disconnected and the
|
// -> we don't only call while Client/Server.Connected, because then we would stop if disconnected and the
|
||||||
// NetworkClient wouldn't receive the last Disconnect event, result in all kinds of issues
|
// NetworkClient wouldn't receive the last Disconnect event, result in all kinds of issues
|
||||||
NetworkServer.Update();
|
NetworkServer.Update();
|
||||||
NetworkClient.UpdateClient();
|
NetworkClient.Update();
|
||||||
UpdateScene();
|
UpdateScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,18 +236,13 @@ internal void RegisterServerMessages()
|
|||||||
NetworkServer.RegisterHandler<ConnectMessage>(OnServerConnectInternal);
|
NetworkServer.RegisterHandler<ConnectMessage>(OnServerConnectInternal);
|
||||||
NetworkServer.RegisterHandler<DisconnectMessage>(OnServerDisconnectInternal);
|
NetworkServer.RegisterHandler<DisconnectMessage>(OnServerDisconnectInternal);
|
||||||
NetworkServer.RegisterHandler<ReadyMessage>(OnServerReadyMessageInternal);
|
NetworkServer.RegisterHandler<ReadyMessage>(OnServerReadyMessageInternal);
|
||||||
NetworkServer.RegisterHandler<AddPlayerMessage>(OnServerAddPlayerMessageInternal);
|
NetworkServer.RegisterHandler<AddPlayerMessage>(OnServerAddPlayer);
|
||||||
NetworkServer.RegisterHandler<RemovePlayerMessage>(OnServerRemovePlayerMessageInternal);
|
NetworkServer.RegisterHandler<RemovePlayerMessage>(OnServerRemovePlayerMessageInternal);
|
||||||
NetworkServer.RegisterHandler<ErrorMessage>(OnServerErrorInternal);
|
NetworkServer.RegisterHandler<ErrorMessage>(OnServerErrorInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartServer()
|
public virtual void ConfigureServerFrameRate()
|
||||||
{
|
{
|
||||||
InitializeSingleton();
|
|
||||||
|
|
||||||
if (runInBackground)
|
|
||||||
Application.runInBackground = true;
|
|
||||||
|
|
||||||
// set a fixed tick rate instead of updating as often as possible
|
// set a fixed tick rate instead of updating as often as possible
|
||||||
// * if not in Editor (it doesn't work in the Editor)
|
// * if not in Editor (it doesn't work in the Editor)
|
||||||
// * if not in Host mode
|
// * if not in Host mode
|
||||||
@ -227,6 +253,16 @@ public bool StartServer()
|
|||||||
Debug.Log("Server Tick Rate set to: " + Application.targetFrameRate + " Hz.");
|
Debug.Log("Server Tick Rate set to: " + Application.targetFrameRate + " Hz.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartServer()
|
||||||
|
{
|
||||||
|
InitializeSingleton();
|
||||||
|
|
||||||
|
if (runInBackground)
|
||||||
|
Application.runInBackground = true;
|
||||||
|
|
||||||
|
ConfigureServerFrameRate();
|
||||||
|
|
||||||
if (!NetworkServer.Listen(maxConnections))
|
if (!NetworkServer.Listen(maxConnections))
|
||||||
{
|
{
|
||||||
@ -251,7 +287,7 @@ public bool StartServer()
|
|||||||
isNetworkActive = true;
|
isNetworkActive = true;
|
||||||
|
|
||||||
// Only change scene if the requested online scene is not blank, and is not already loaded
|
// Only change scene if the requested online scene is not blank, and is not already loaded
|
||||||
string loadedSceneName = SceneManager.GetSceneAt(0).name;
|
string loadedSceneName = SceneManager.GetActiveScene().name;
|
||||||
if (!string.IsNullOrEmpty(onlineScene) && onlineScene != loadedSceneName && onlineScene != offlineScene)
|
if (!string.IsNullOrEmpty(onlineScene) && onlineScene != loadedSceneName && onlineScene != offlineScene)
|
||||||
{
|
{
|
||||||
ServerChangeScene(onlineScene);
|
ServerChangeScene(onlineScene);
|
||||||
@ -263,13 +299,13 @@ public bool StartServer()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RegisterClientMessages(NetworkClient client)
|
internal void RegisterClientMessages()
|
||||||
{
|
{
|
||||||
client.RegisterHandler<ConnectMessage>(OnClientConnectInternal);
|
NetworkClient.RegisterHandler<ConnectMessage>(OnClientConnectInternal);
|
||||||
client.RegisterHandler<DisconnectMessage>(OnClientDisconnectInternal);
|
NetworkClient.RegisterHandler<DisconnectMessage>(OnClientDisconnectInternal);
|
||||||
client.RegisterHandler<NotReadyMessage>(OnClientNotReadyMessageInternal);
|
NetworkClient.RegisterHandler<NotReadyMessage>(OnClientNotReadyMessageInternal);
|
||||||
client.RegisterHandler<ErrorMessage>(OnClientErrorInternal);
|
NetworkClient.RegisterHandler<ErrorMessage>(OnClientErrorInternal);
|
||||||
client.RegisterHandler<SceneMessage>(OnClientSceneInternal);
|
NetworkClient.RegisterHandler<SceneMessage>(OnClientSceneInternal);
|
||||||
|
|
||||||
if (playerPrefab != null)
|
if (playerPrefab != null)
|
||||||
{
|
{
|
||||||
@ -285,7 +321,7 @@ internal void RegisterClientMessages(NetworkClient client)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkClient StartClient()
|
public void StartClient()
|
||||||
{
|
{
|
||||||
InitializeSingleton();
|
InitializeSingleton();
|
||||||
|
|
||||||
@ -294,43 +330,38 @@ public NetworkClient StartClient()
|
|||||||
|
|
||||||
isNetworkActive = true;
|
isNetworkActive = true;
|
||||||
|
|
||||||
client = new NetworkClient();
|
RegisterClientMessages();
|
||||||
|
|
||||||
RegisterClientMessages(client);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(networkAddress))
|
if (string.IsNullOrEmpty(networkAddress))
|
||||||
{
|
{
|
||||||
Debug.LogError("Must set the Network Address field in the manager");
|
Debug.LogError("Must set the Network Address field in the manager");
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkManager StartClient address:" + networkAddress);
|
if (LogFilter.Debug) Debug.Log("NetworkManager StartClient address:" + networkAddress);
|
||||||
|
|
||||||
client.Connect(networkAddress);
|
NetworkClient.Connect(networkAddress);
|
||||||
|
|
||||||
OnStartClient(client);
|
OnStartClient();
|
||||||
s_Address = networkAddress;
|
s_Address = networkAddress;
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual NetworkClient StartHost()
|
public virtual void StartHost()
|
||||||
{
|
{
|
||||||
OnStartHost();
|
OnStartHost();
|
||||||
if (StartServer())
|
if (StartServer())
|
||||||
{
|
{
|
||||||
NetworkClient localClient = ConnectLocalClient();
|
ConnectLocalClient();
|
||||||
OnStartClient(localClient);
|
OnStartClient();
|
||||||
return localClient;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkClient ConnectLocalClient()
|
void ConnectLocalClient()
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkManager StartHost");
|
if (LogFilter.Debug) Debug.Log("NetworkManager StartHost");
|
||||||
networkAddress = "localhost";
|
networkAddress = "localhost";
|
||||||
client = ClientScene.ConnectLocalServer();
|
NetworkServer.ActivateLocalClientScene();
|
||||||
RegisterClientMessages(client);
|
NetworkClient.ConnectLocalServer();
|
||||||
return client;
|
RegisterClientMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopHost()
|
public void StopHost()
|
||||||
@ -364,13 +395,10 @@ public void StopClient()
|
|||||||
|
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkManager StopClient");
|
if (LogFilter.Debug) Debug.Log("NetworkManager StopClient");
|
||||||
isNetworkActive = false;
|
isNetworkActive = false;
|
||||||
if (client != null)
|
|
||||||
{
|
// shutdown client
|
||||||
// only shutdown this client, not ALL clients.
|
NetworkClient.Disconnect();
|
||||||
client.Disconnect();
|
NetworkClient.Shutdown();
|
||||||
client.Shutdown();
|
|
||||||
client = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientScene.DestroyAllClientObjects();
|
ClientScene.DestroyAllClientObjects();
|
||||||
if (!string.IsNullOrEmpty(offlineScene))
|
if (!string.IsNullOrEmpty(offlineScene))
|
||||||
@ -432,11 +460,8 @@ internal void ClientChangeScene(string newSceneName, bool forceReload)
|
|||||||
// vis2k: pause message handling while loading scene. otherwise we will process messages and then lose all
|
// vis2k: pause message handling while loading scene. otherwise we will process messages and then lose all
|
||||||
// the state as soon as the load is finishing, causing all kinds of bugs because of missing state.
|
// the state as soon as the load is finishing, causing all kinds of bugs because of missing state.
|
||||||
// (client may be null after StopClient etc.)
|
// (client may be null after StopClient etc.)
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded.");
|
if (LogFilter.Debug) Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded.");
|
||||||
Transport.activeTransport.enabled = false;
|
Transport.activeTransport.enabled = false;
|
||||||
}
|
|
||||||
|
|
||||||
// Let client prepare for scene change
|
// Let client prepare for scene change
|
||||||
OnClientChangeScene(newSceneName);
|
OnClientChangeScene(newSceneName);
|
||||||
@ -449,8 +474,6 @@ void FinishLoadScene()
|
|||||||
{
|
{
|
||||||
// NOTE: this cannot use NetworkClient.allClients[0] - that client may be for a completely different purpose.
|
// NOTE: this cannot use NetworkClient.allClients[0] - that client may be for a completely different purpose.
|
||||||
|
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
// process queued messages that we received while loading the scene
|
// process queued messages that we received while loading the scene
|
||||||
if (LogFilter.Debug) Debug.Log("FinishLoadScene: resuming handlers after scene was loading.");
|
if (LogFilter.Debug) Debug.Log("FinishLoadScene: resuming handlers after scene was loading.");
|
||||||
Transport.activeTransport.enabled = true;
|
Transport.activeTransport.enabled = true;
|
||||||
@ -461,11 +484,6 @@ void FinishLoadScene()
|
|||||||
OnClientConnect(s_ClientReadyConnection);
|
OnClientConnect(s_ClientReadyConnection);
|
||||||
s_ClientReadyConnection = null;
|
s_ClientReadyConnection = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("FinishLoadScene client is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NetworkServer.active)
|
if (NetworkServer.active)
|
||||||
{
|
{
|
||||||
@ -473,10 +491,10 @@ void FinishLoadScene()
|
|||||||
OnServerSceneChanged(networkSceneName);
|
OnServerSceneChanged(networkSceneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsClientConnected() && client != null)
|
if (NetworkClient.isConnected)
|
||||||
{
|
{
|
||||||
RegisterClientMessages(client);
|
RegisterClientMessages();
|
||||||
OnClientSceneChanged(client.connection);
|
OnClientSceneChanged(NetworkClient.connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,11 +527,11 @@ public static void UnRegisterStartPosition(Transform start)
|
|||||||
startPositions.Remove(start);
|
startPositions.Remove(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use NetworkClient.isConnected instead")]
|
||||||
public bool IsClientConnected()
|
public bool IsClientConnected()
|
||||||
{
|
{
|
||||||
return client != null && client.isConnected;
|
return NetworkClient.isConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is the only way to clear the singleton, so another instance can be created.
|
// this is the only way to clear the singleton, so another instance can be created.
|
||||||
public static void Shutdown()
|
public static void Shutdown()
|
||||||
{
|
{
|
||||||
@ -554,13 +572,6 @@ internal void OnServerReadyMessageInternal(NetworkConnection conn, ReadyMessage
|
|||||||
OnServerReady(conn);
|
OnServerReady(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnServerAddPlayerMessageInternal(NetworkConnection conn, AddPlayerMessage msg)
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerAddPlayerMessageInternal");
|
|
||||||
|
|
||||||
OnServerAddPlayer(conn, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void OnServerRemovePlayerMessageInternal(NetworkConnection conn, RemovePlayerMessage msg)
|
internal void OnServerRemovePlayerMessageInternal(NetworkConnection conn, RemovePlayerMessage msg)
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerRemovePlayerMessageInternal");
|
if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerRemovePlayerMessageInternal");
|
||||||
@ -568,7 +579,7 @@ internal void OnServerRemovePlayerMessageInternal(NetworkConnection conn, Remove
|
|||||||
if (conn.playerController != null)
|
if (conn.playerController != null)
|
||||||
{
|
{
|
||||||
OnServerRemovePlayer(conn, conn.playerController);
|
OnServerRemovePlayer(conn, conn.playerController);
|
||||||
conn.RemovePlayerController();
|
conn.playerController = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,7 +636,7 @@ internal void OnClientSceneInternal(NetworkConnection conn, SceneMessage msg)
|
|||||||
|
|
||||||
string newSceneName = msg.value;
|
string newSceneName = msg.value;
|
||||||
|
|
||||||
if (IsClientConnected() && !NetworkServer.active)
|
if (NetworkClient.isConnected && !NetworkServer.active)
|
||||||
{
|
{
|
||||||
ClientChangeScene(newSceneName, true);
|
ClientChangeScene(newSceneName, true);
|
||||||
}
|
}
|
||||||
@ -654,22 +665,19 @@ public virtual void OnServerReady(NetworkConnection conn)
|
|||||||
[Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")]
|
[Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")]
|
||||||
public virtual void OnServerAddPlayer(NetworkConnection conn, NetworkMessage extraMessage)
|
public virtual void OnServerAddPlayer(NetworkConnection conn, NetworkMessage extraMessage)
|
||||||
{
|
{
|
||||||
OnServerAddPlayerInternal(conn);
|
OnServerAddPlayer(conn, null);
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
|
|
||||||
{
|
|
||||||
OnServerAddPlayerInternal(conn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")]
|
[Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")]
|
||||||
public virtual void OnServerAddPlayer(NetworkConnection conn)
|
public virtual void OnServerAddPlayer(NetworkConnection conn)
|
||||||
{
|
{
|
||||||
OnServerAddPlayerInternal(conn);
|
OnServerAddPlayer(conn, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnServerAddPlayerInternal(NetworkConnection conn)
|
public virtual void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
|
||||||
{
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerAddPlayer");
|
||||||
|
|
||||||
if (playerPrefab == null)
|
if (playerPrefab == null)
|
||||||
{
|
{
|
||||||
Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
|
Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
|
||||||
@ -785,7 +793,9 @@ public virtual void OnClientSceneChanged(NetworkConnection conn)
|
|||||||
|
|
||||||
public virtual void OnStartHost() {}
|
public virtual void OnStartHost() {}
|
||||||
public virtual void OnStartServer() {}
|
public virtual void OnStartServer() {}
|
||||||
|
[Obsolete("Use OnStartClient() instead of OnStartClient(NetworkClient client). All NetworkClient functions are static now, so you can use NetworkClient.Send(message) instead of client.Send(message) directly now.")]
|
||||||
public virtual void OnStartClient(NetworkClient client) {}
|
public virtual void OnStartClient(NetworkClient client) {}
|
||||||
|
public virtual void OnStartClient() {}
|
||||||
public virtual void OnStopServer() {}
|
public virtual void OnStopServer() {}
|
||||||
public virtual void OnStopClient() {}
|
public virtual void OnStopClient() {}
|
||||||
public virtual void OnStopHost() {}
|
public virtual void OnStopHost() {}
|
||||||
|
@ -26,13 +26,10 @@ void OnGUI()
|
|||||||
if (!showGUI)
|
if (!showGUI)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool noConnection = (manager.client == null || manager.client.connection == null ||
|
|
||||||
manager.client.connection.connectionId == -1);
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(10 + offsetX, 40 + offsetY, 215, 9999));
|
GUILayout.BeginArea(new Rect(10 + offsetX, 40 + offsetY, 215, 9999));
|
||||||
if (!manager.IsClientConnected() && !NetworkServer.active)
|
if (!NetworkClient.isConnected && !NetworkServer.active)
|
||||||
{
|
{
|
||||||
if (noConnection)
|
if (!NetworkClient.active)
|
||||||
{
|
{
|
||||||
// LAN Host
|
// LAN Host
|
||||||
if (Application.platform != RuntimePlatform.WebGLPlayer)
|
if (Application.platform != RuntimePlatform.WebGLPlayer)
|
||||||
@ -80,18 +77,18 @@ void OnGUI()
|
|||||||
{
|
{
|
||||||
GUILayout.Label("Server: active. Transport: " + Transport.activeTransport);
|
GUILayout.Label("Server: active. Transport: " + Transport.activeTransport);
|
||||||
}
|
}
|
||||||
if (manager.IsClientConnected())
|
if (NetworkClient.isConnected)
|
||||||
{
|
{
|
||||||
GUILayout.Label("Client: address=" + manager.networkAddress);
|
GUILayout.Label("Client: address=" + manager.networkAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// client ready
|
// client ready
|
||||||
if (manager.IsClientConnected() && !ClientScene.ready)
|
if (NetworkClient.isConnected && !ClientScene.ready)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("Client Ready"))
|
if (GUILayout.Button("Client Ready"))
|
||||||
{
|
{
|
||||||
ClientScene.Ready(manager.client.connection);
|
ClientScene.Ready(NetworkClient.connection);
|
||||||
|
|
||||||
if (ClientScene.localPlayer == null)
|
if (ClientScene.localPlayer == null)
|
||||||
{
|
{
|
||||||
@ -101,7 +98,7 @@ void OnGUI()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop
|
// stop
|
||||||
if (NetworkServer.active || manager.IsClientConnected())
|
if (NetworkServer.active || NetworkClient.isConnected)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("Stop"))
|
if (GUILayout.Button("Stop"))
|
||||||
{
|
{
|
||||||
|
@ -6,14 +6,14 @@ public struct NetworkMessage
|
|||||||
public NetworkConnection conn;
|
public NetworkConnection conn;
|
||||||
public NetworkReader reader;
|
public NetworkReader reader;
|
||||||
|
|
||||||
public TMsg ReadMessage<TMsg>() where TMsg : MessageBase, new()
|
public TMsg ReadMessage<TMsg>() where TMsg : IMessageBase, new()
|
||||||
{
|
{
|
||||||
TMsg msg = new TMsg();
|
TMsg msg = new TMsg();
|
||||||
msg.Deserialize(reader);
|
msg.Deserialize(reader);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReadMessage<TMsg>(TMsg msg) where TMsg : MessageBase
|
public void ReadMessage<TMsg>(TMsg msg) where TMsg : IMessageBase
|
||||||
{
|
{
|
||||||
msg.Deserialize(reader);
|
msg.Deserialize(reader);
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,16 @@ public Vector4 ReadVector4()
|
|||||||
return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector2Int ReadVector2Int()
|
||||||
|
{
|
||||||
|
return new Vector2Int((int)ReadPackedUInt32(), (int)ReadPackedUInt32());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3Int ReadVector3Int()
|
||||||
|
{
|
||||||
|
return new Vector3Int((int)ReadPackedUInt32(), (int)ReadPackedUInt32(), (int)ReadPackedUInt32());
|
||||||
|
}
|
||||||
|
|
||||||
public Color ReadColor()
|
public Color ReadColor()
|
||||||
{
|
{
|
||||||
return new Color(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
return new Color(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||||
|
@ -6,14 +6,13 @@ namespace Mirror
|
|||||||
{
|
{
|
||||||
public static class NetworkServer
|
public static class NetworkServer
|
||||||
{
|
{
|
||||||
static ULocalConnectionToClient s_LocalConnection;
|
static bool initialized;
|
||||||
static bool s_Initialized;
|
static int maxConnections;
|
||||||
static int s_MaxConnections;
|
|
||||||
|
|
||||||
// original HLAPI has .localConnections list with only m_LocalConnection in it
|
// original HLAPI has .localConnections list with only m_LocalConnection in it
|
||||||
// (for downwards compatibility because they removed the real localConnections list a while ago)
|
// (for downwards compatibility because they removed the real localConnections list a while ago)
|
||||||
// => removed it for easier code. use .localConection now!
|
// => removed it for easier code. use .localConection now!
|
||||||
public static NetworkConnection localConnection => s_LocalConnection;
|
public static NetworkConnection localConnection { get; private set; }
|
||||||
|
|
||||||
// <connectionId, NetworkConnection>
|
// <connectionId, NetworkConnection>
|
||||||
public static Dictionary<int, NetworkConnection> connections = new Dictionary<int, NetworkConnection>();
|
public static Dictionary<int, NetworkConnection> connections = new Dictionary<int, NetworkConnection>();
|
||||||
@ -31,7 +30,7 @@ public static void Reset()
|
|||||||
|
|
||||||
public static void Shutdown()
|
public static void Shutdown()
|
||||||
{
|
{
|
||||||
if (s_Initialized)
|
if (initialized)
|
||||||
{
|
{
|
||||||
DisconnectAll();
|
DisconnectAll();
|
||||||
|
|
||||||
@ -49,18 +48,20 @@ public static void Shutdown()
|
|||||||
Transport.activeTransport.OnServerDataReceived.RemoveListener(OnDataReceived);
|
Transport.activeTransport.OnServerDataReceived.RemoveListener(OnDataReceived);
|
||||||
Transport.activeTransport.OnServerError.RemoveListener(OnError);
|
Transport.activeTransport.OnServerError.RemoveListener(OnError);
|
||||||
|
|
||||||
s_Initialized = false;
|
initialized = false;
|
||||||
}
|
}
|
||||||
dontListen = false;
|
dontListen = false;
|
||||||
active = false;
|
active = false;
|
||||||
|
|
||||||
|
NetworkIdentity.ResetNextNetworkId();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Initialize()
|
static void Initialize()
|
||||||
{
|
{
|
||||||
if (s_Initialized)
|
if (initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_Initialized = true;
|
initialized = true;
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkServer Created version " + Version.Current);
|
if (LogFilter.Debug) Debug.Log("NetworkServer Created version " + Version.Current);
|
||||||
|
|
||||||
//Make sure connections are cleared in case any old connections references exist from previous sessions
|
//Make sure connections are cleared in case any old connections references exist from previous sessions
|
||||||
@ -69,7 +70,6 @@ static void Initialize()
|
|||||||
Transport.activeTransport.OnServerConnected.AddListener(OnConnected);
|
Transport.activeTransport.OnServerConnected.AddListener(OnConnected);
|
||||||
Transport.activeTransport.OnServerDataReceived.AddListener(OnDataReceived);
|
Transport.activeTransport.OnServerDataReceived.AddListener(OnDataReceived);
|
||||||
Transport.activeTransport.OnServerError.AddListener(OnError);
|
Transport.activeTransport.OnServerError.AddListener(OnError);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RegisterMessageHandlers()
|
internal static void RegisterMessageHandlers()
|
||||||
@ -80,10 +80,10 @@ internal static void RegisterMessageHandlers()
|
|||||||
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing);
|
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Listen(int maxConnections)
|
public static bool Listen(int maxConns)
|
||||||
{
|
{
|
||||||
Initialize();
|
Initialize();
|
||||||
s_MaxConnections = maxConnections;
|
maxConnections = maxConns;
|
||||||
|
|
||||||
// only start server if we want to listen
|
// only start server if we want to listen
|
||||||
if (!dontListen)
|
if (!dontListen)
|
||||||
@ -117,29 +117,25 @@ public static bool RemoveConnection(int connectionId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// called by LocalClient to add itself. dont call directly.
|
// called by LocalClient to add itself. dont call directly.
|
||||||
internal static int AddLocalClient(LocalClient localClient)
|
internal static void SetLocalConnection(ULocalConnectionToClient conn)
|
||||||
{
|
{
|
||||||
if (s_LocalConnection != null)
|
if (localConnection != null)
|
||||||
{
|
{
|
||||||
Debug.LogError("Local Connection already exists");
|
Debug.LogError("Local Connection already exists");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_LocalConnection = new ULocalConnectionToClient(localClient)
|
localConnection = conn;
|
||||||
{
|
OnConnected(localConnection);
|
||||||
connectionId = 0
|
|
||||||
};
|
|
||||||
OnConnected(s_LocalConnection);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RemoveLocalClient()
|
internal static void RemoveLocalConnection()
|
||||||
{
|
{
|
||||||
if (s_LocalConnection != null)
|
if (localConnection != null)
|
||||||
{
|
{
|
||||||
s_LocalConnection.Disconnect();
|
localConnection.Disconnect();
|
||||||
s_LocalConnection.Dispose();
|
localConnection.Dispose();
|
||||||
s_LocalConnection = null;
|
localConnection = null;
|
||||||
}
|
}
|
||||||
localClientActive = false;
|
localClientActive = false;
|
||||||
RemoveConnection(0);
|
RemoveConnection(0);
|
||||||
@ -189,7 +185,7 @@ static bool SendToObservers(NetworkIdentity identity, short msgType, MessageBase
|
|||||||
|
|
||||||
// this is like SendToReady - but it doesn't check the ready flag on the connection.
|
// this is like SendToReady - but it doesn't check the ready flag on the connection.
|
||||||
// this is used for ObjectDestroy messages.
|
// this is used for ObjectDestroy messages.
|
||||||
static bool SendToObservers<T>(NetworkIdentity identity, T msg) where T: MessageBase
|
static bool SendToObservers<T>(NetworkIdentity identity, T msg) where T: IMessageBase
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("Server.SendToObservers id:" + typeof(T));
|
if (LogFilter.Debug) Debug.Log("Server.SendToObservers id:" + typeof(T));
|
||||||
|
|
||||||
@ -225,7 +221,7 @@ public static bool SendToAll(int msgType, MessageBase msg, int channelId = Chann
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool SendToAll<T>(T msg, int channelId = Channels.DefaultReliable) where T : MessageBase
|
public static bool SendToAll<T>(T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("Server.SendToAll id:" + typeof(T));
|
if (LogFilter.Debug) Debug.Log("Server.SendToAll id:" + typeof(T));
|
||||||
|
|
||||||
@ -264,7 +260,7 @@ public static bool SendToReady(NetworkIdentity identity, short msgType, MessageB
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId = Channels.DefaultReliable) where T: MessageBase
|
public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
|
||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("Server.SendToReady msgType:" + typeof(T));
|
if (LogFilter.Debug) Debug.Log("Server.SendToReady msgType:" + typeof(T));
|
||||||
|
|
||||||
@ -289,7 +285,7 @@ public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId
|
|||||||
public static void DisconnectAll()
|
public static void DisconnectAll()
|
||||||
{
|
{
|
||||||
DisconnectAllConnections();
|
DisconnectAllConnections();
|
||||||
s_LocalConnection = null;
|
localConnection = null;
|
||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
localClientActive = false;
|
localClientActive = false;
|
||||||
@ -360,7 +356,7 @@ static void OnConnected(int connectionId)
|
|||||||
// less code and third party transport might not do that anyway)
|
// less code and third party transport might not do that anyway)
|
||||||
// (this way we could also send a custom 'tooFull' message later,
|
// (this way we could also send a custom 'tooFull' message later,
|
||||||
// Transport can't do that)
|
// Transport can't do that)
|
||||||
if (connections.Count < s_MaxConnections)
|
if (connections.Count < maxConnections)
|
||||||
{
|
{
|
||||||
// get ip address from connection
|
// get ip address from connection
|
||||||
string address = Transport.activeTransport.ServerGetClientAddress(connectionId);
|
string address = Transport.activeTransport.ServerGetClientAddress(connectionId);
|
||||||
@ -425,7 +421,7 @@ static void OnDataReceived(int connectionId, byte[] data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnError(int connectionId, Exception exception)
|
static void OnError(int connectionId, Exception exception)
|
||||||
{
|
{
|
||||||
// TODO Let's discuss how we will handle errors
|
// TODO Let's discuss how we will handle errors
|
||||||
Debug.LogException(exception);
|
Debug.LogException(exception);
|
||||||
@ -494,7 +490,7 @@ public static void RegisterHandler(MsgType msgType, NetworkMessageDelegate handl
|
|||||||
RegisterHandler((int)msgType, handler);
|
RegisterHandler((int)msgType, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterHandler<T>(Action<NetworkConnection, T> handler) where T: MessageBase, new()
|
public static void RegisterHandler<T>(Action<NetworkConnection, T> handler) where T: IMessageBase, new()
|
||||||
{
|
{
|
||||||
int msgType = MessagePacker.GetId<T>();
|
int msgType = MessagePacker.GetId<T>();
|
||||||
if (handlers.ContainsKey(msgType))
|
if (handlers.ContainsKey(msgType))
|
||||||
@ -520,7 +516,7 @@ public static void UnregisterHandler(MsgType msgType)
|
|||||||
UnregisterHandler((int)msgType);
|
UnregisterHandler((int)msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnregisterHandler<T>() where T:MessageBase
|
public static void UnregisterHandler<T>() where T : IMessageBase
|
||||||
{
|
{
|
||||||
int msgType = MessagePacker.GetId<T>();
|
int msgType = MessagePacker.GetId<T>();
|
||||||
handlers.Remove(msgType);
|
handlers.Remove(msgType);
|
||||||
@ -542,7 +538,7 @@ public static void SendToClient(int connectionId, int msgType, MessageBase msg)
|
|||||||
Debug.LogError("Failed to send message to connection ID '" + connectionId + ", not found in connection list");
|
Debug.LogError("Failed to send message to connection ID '" + connectionId + ", not found in connection list");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendToClient<T>(int connectionId, T msg) where T : MessageBase
|
public static void SendToClient<T>(int connectionId, T msg) where T : IMessageBase
|
||||||
{
|
{
|
||||||
NetworkConnection conn;
|
NetworkConnection conn;
|
||||||
if (connections.TryGetValue(connectionId, out conn))
|
if (connections.TryGetValue(connectionId, out conn))
|
||||||
@ -568,7 +564,7 @@ public static void SendToClientOfPlayer(NetworkIdentity identity, int msgType, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send this message to the player only
|
// send this message to the player only
|
||||||
public static void SendToClientOfPlayer<T>(NetworkIdentity identity, T msg) where T: MessageBase
|
public static void SendToClientOfPlayer<T>(NetworkIdentity identity, T msg) where T: IMessageBase
|
||||||
{
|
{
|
||||||
if (identity != null)
|
if (identity != null)
|
||||||
{
|
{
|
||||||
@ -600,20 +596,71 @@ public static bool AddPlayerForConnection(NetworkConnection conn, GameObject pla
|
|||||||
{
|
{
|
||||||
identity.assetId = assetId;
|
identity.assetId = assetId;
|
||||||
}
|
}
|
||||||
return InternalAddPlayerForConnection(conn, player);
|
return AddPlayerForConnection(conn, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SpawnObserversForConnection(NetworkConnection conn)
|
||||||
|
{
|
||||||
|
// Setup spawned objects for local player
|
||||||
|
if (conn is ULocalConnectionToClient)
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("NetworkServer Ready handling ULocalConnectionToClient");
|
||||||
|
|
||||||
|
foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
|
||||||
|
{
|
||||||
|
// Need to call OnStartClient directly here, as it's already been added to the local object dictionary
|
||||||
|
// in the above SetLocalPlayer call
|
||||||
|
if (identity.gameObject != null)
|
||||||
|
{
|
||||||
|
bool visible = identity.OnCheckObserver(conn);
|
||||||
|
if (visible)
|
||||||
|
{
|
||||||
|
identity.AddObserver(conn);
|
||||||
|
}
|
||||||
|
if (!identity.isClient)
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("LocalClient.SetSpawnObject calling OnStartClient");
|
||||||
|
identity.OnStartClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add connection to each nearby NetworkIdentity's observers, which
|
||||||
|
// internally sends a spawn message for each one to the connection.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("Spawning " + NetworkIdentity.spawned.Count + " objects for conn " + conn.connectionId);
|
||||||
|
|
||||||
|
// let connection know that we are about to start spawning...
|
||||||
|
conn.Send(new ObjectSpawnStartedMessage());
|
||||||
|
|
||||||
|
foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
|
||||||
|
{
|
||||||
|
if (identity.gameObject.activeSelf)
|
||||||
|
{
|
||||||
|
if (LogFilter.Debug) Debug.Log("Sending spawn message for current server objects name='" + identity.name + "' netId=" + identity.netId);
|
||||||
|
|
||||||
|
bool visible = identity.OnCheckObserver(conn);
|
||||||
|
if (visible)
|
||||||
|
{
|
||||||
|
identity.AddObserver(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let connection know that we finished spawning, so it can call
|
||||||
|
// OnStartClient on each one (only after all were spawned, which
|
||||||
|
// is how Unity's Start() function works too)
|
||||||
|
conn.Send(new ObjectSpawnFinishedMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AddPlayerForConnection(NetworkConnection conn, GameObject player)
|
public static bool AddPlayerForConnection(NetworkConnection conn, GameObject player)
|
||||||
{
|
{
|
||||||
return InternalAddPlayerForConnection(conn, player);
|
NetworkIdentity identity = player.GetComponent<NetworkIdentity>();
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool InternalAddPlayerForConnection(NetworkConnection conn, GameObject playerGameObject)
|
|
||||||
{
|
|
||||||
NetworkIdentity identity = playerGameObject.GetComponent<NetworkIdentity>();
|
|
||||||
if (identity == null)
|
if (identity == null)
|
||||||
{
|
{
|
||||||
Debug.Log("AddPlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to " + playerGameObject);
|
Debug.Log("AddPlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to " + player);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
identity.Reset();
|
identity.Reset();
|
||||||
@ -625,21 +672,29 @@ internal static bool InternalAddPlayerForConnection(NetworkConnection conn, Game
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetPlayerController(identity);
|
conn.playerController = identity;
|
||||||
|
|
||||||
// Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
|
// Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
|
||||||
identity.connectionToClient = conn;
|
identity.connectionToClient = conn;
|
||||||
|
|
||||||
|
// set ready if not set yet
|
||||||
SetClientReady(conn);
|
SetClientReady(conn);
|
||||||
|
|
||||||
|
// add connection to observers AFTER the playerController was set.
|
||||||
|
// by definition, there is nothing to observe if there is no player
|
||||||
|
// controller.
|
||||||
|
//
|
||||||
|
// IMPORTANT: do this in AddPlayerForConnection & ReplacePlayerForConnection!
|
||||||
|
SpawnObserversForConnection(conn);
|
||||||
|
|
||||||
if (SetupLocalPlayerForConnection(conn, identity))
|
if (SetupLocalPlayerForConnection(conn, identity))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LogFilter.Debug) Debug.Log("Adding new playerGameObject object netId: " + playerGameObject.GetComponent<NetworkIdentity>().netId + " asset ID " + playerGameObject.GetComponent<NetworkIdentity>().assetId);
|
if (LogFilter.Debug) Debug.Log("Adding new playerGameObject object netId: " + identity.netId + " asset ID " + identity.assetId);
|
||||||
|
|
||||||
FinishPlayerForConnection(conn, identity, playerGameObject);
|
FinishPlayerForConnection(conn, identity, player);
|
||||||
if (identity.localPlayerAuthority)
|
if (identity.localPlayerAuthority)
|
||||||
{
|
{
|
||||||
identity.SetClientOwner(conn);
|
identity.SetClientOwner(conn);
|
||||||
@ -666,7 +721,7 @@ static bool SetupLocalPlayerForConnection(NetworkConnection conn, NetworkIdentit
|
|||||||
SendSpawnMessage(identity, null);
|
SendSpawnMessage(identity, null);
|
||||||
|
|
||||||
// Set up local player instance on the client instance and update local object map
|
// Set up local player instance on the client instance and update local object map
|
||||||
localConnection.localClient.AddLocalPlayer(identity);
|
NetworkClient.AddLocalPlayer(identity);
|
||||||
identity.SetClientOwner(conn);
|
identity.SetClientOwner(conn);
|
||||||
|
|
||||||
// Trigger OnAuthority
|
// Trigger OnAuthority
|
||||||
@ -714,13 +769,20 @@ internal static bool InternalReplacePlayerForConnection(NetworkConnection conn,
|
|||||||
conn.playerController.clientAuthorityOwner = null;
|
conn.playerController.clientAuthorityOwner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetPlayerController(playerNetworkIdentity);
|
conn.playerController = playerNetworkIdentity;
|
||||||
|
|
||||||
// Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
|
// Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
|
||||||
playerNetworkIdentity.connectionToClient = conn;
|
playerNetworkIdentity.connectionToClient = conn;
|
||||||
|
|
||||||
//NOTE: DONT set connection ready.
|
//NOTE: DONT set connection ready.
|
||||||
|
|
||||||
|
// add connection to observers AFTER the playerController was set.
|
||||||
|
// by definition, there is nothing to observe if there is no player
|
||||||
|
// controller.
|
||||||
|
//
|
||||||
|
// IMPORTANT: do this in AddPlayerForConnection & ReplacePlayerForConnection!
|
||||||
|
SpawnObserversForConnection(conn);
|
||||||
|
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkServer ReplacePlayer setup local");
|
if (LogFilter.Debug) Debug.Log("NetworkServer ReplacePlayer setup local");
|
||||||
|
|
||||||
if (SetupLocalPlayerForConnection(conn, playerNetworkIdentity))
|
if (SetupLocalPlayerForConnection(conn, playerNetworkIdentity))
|
||||||
@ -753,75 +815,8 @@ public static void SetClientReady(NetworkConnection conn)
|
|||||||
{
|
{
|
||||||
if (LogFilter.Debug) Debug.Log("SetClientReadyInternal for conn:" + conn.connectionId);
|
if (LogFilter.Debug) Debug.Log("SetClientReadyInternal for conn:" + conn.connectionId);
|
||||||
|
|
||||||
if (conn.isReady)
|
// set ready
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("SetClientReady conn " + conn.connectionId + " already ready");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conn.playerController == null)
|
|
||||||
{
|
|
||||||
// this is now allowed
|
|
||||||
if (LogFilter.Debug) Debug.LogWarning("Ready with no player object");
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.isReady = true;
|
conn.isReady = true;
|
||||||
|
|
||||||
if (conn is ULocalConnectionToClient localConnection)
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("NetworkServer Ready handling ULocalConnectionToClient");
|
|
||||||
|
|
||||||
// Setup spawned objects for local player
|
|
||||||
// Only handle the local objects for the first player (no need to redo it when doing more local players)
|
|
||||||
// and don't handle player objects here, they were done above
|
|
||||||
foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
|
|
||||||
{
|
|
||||||
// Need to call OnStartClient directly here, as it's already been added to the local object dictionary
|
|
||||||
// in the above SetLocalPlayer call
|
|
||||||
if (identity.gameObject != null)
|
|
||||||
{
|
|
||||||
bool visible = identity.OnCheckObserver(conn);
|
|
||||||
if (visible)
|
|
||||||
{
|
|
||||||
identity.AddObserver(conn);
|
|
||||||
}
|
|
||||||
if (!identity.isClient)
|
|
||||||
{
|
|
||||||
if (LogFilter.Debug) Debug.Log("LocalClient.SetSpawnObject calling OnStartClient");
|
|
||||||
identity.OnStartClient();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn/update all current server objects
|
|
||||||
if (LogFilter.Debug) Debug.Log("Spawning " + NetworkIdentity.spawned.Count + " objects for conn " + conn.connectionId);
|
|
||||||
|
|
||||||
conn.Send(new ObjectSpawnStartedMessage());
|
|
||||||
|
|
||||||
foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
|
|
||||||
{
|
|
||||||
if (identity == null)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("Invalid object found in server local object list (null NetworkIdentity).");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!identity.gameObject.activeSelf)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LogFilter.Debug) Debug.Log("Sending spawn message for current server objects name='" + identity.name + "' netId=" + identity.netId);
|
|
||||||
|
|
||||||
bool visible = identity.OnCheckObserver(conn);
|
|
||||||
if (visible)
|
|
||||||
{
|
|
||||||
identity.AddObserver(conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.Send(new ObjectSpawnFinishedMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ShowForConnection(NetworkIdentity identity, NetworkConnection conn)
|
internal static void ShowForConnection(NetworkIdentity identity, NetworkConnection conn)
|
||||||
@ -879,7 +874,7 @@ static void OnRemovePlayerMessage(NetworkConnection conn, RemovePlayerMessage ms
|
|||||||
if (conn.playerController != null)
|
if (conn.playerController != null)
|
||||||
{
|
{
|
||||||
Destroy(conn.playerController.gameObject);
|
Destroy(conn.playerController.gameObject);
|
||||||
conn.RemovePlayerController();
|
conn.playerController = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1019,9 +1014,8 @@ public static void DestroyPlayerForConnection(NetworkConnection conn)
|
|||||||
if (conn.playerController != null)
|
if (conn.playerController != null)
|
||||||
{
|
{
|
||||||
DestroyObject(conn.playerController, true);
|
DestroyObject(conn.playerController, true);
|
||||||
|
conn.playerController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.RemovePlayerController();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Spawn(GameObject obj)
|
public static void Spawn(GameObject obj)
|
||||||
|
@ -13,7 +13,7 @@ public static class NetworkTime
|
|||||||
// average out the last few results from Ping
|
// average out the last few results from Ping
|
||||||
public static int PingWindowSize = 10;
|
public static int PingWindowSize = 10;
|
||||||
|
|
||||||
internal static double lastPingTime;
|
static double lastPingTime;
|
||||||
|
|
||||||
|
|
||||||
// Date and time when the application started
|
// Date and time when the application started
|
||||||
@ -28,8 +28,8 @@ static NetworkTime()
|
|||||||
static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10);
|
static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10);
|
||||||
|
|
||||||
// the true offset guaranteed to be in this range
|
// the true offset guaranteed to be in this range
|
||||||
private static double offsetMin = double.MinValue;
|
static double offsetMin = double.MinValue;
|
||||||
private static double offsetMax = double.MaxValue;
|
static double offsetMax = double.MaxValue;
|
||||||
|
|
||||||
// returns the clock time _in this system_
|
// returns the clock time _in this system_
|
||||||
static double LocalTime()
|
static double LocalTime()
|
||||||
@ -45,17 +45,12 @@ public static void Reset()
|
|||||||
offsetMax = double.MaxValue;
|
offsetMax = double.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static NetworkPingMessage GetPing()
|
internal static void UpdateClient()
|
||||||
{
|
|
||||||
return new NetworkPingMessage(LocalTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void UpdateClient(NetworkClient networkClient)
|
|
||||||
{
|
{
|
||||||
if (Time.time - lastPingTime >= PingFrequency)
|
if (Time.time - lastPingTime >= PingFrequency)
|
||||||
{
|
{
|
||||||
NetworkPingMessage pingMessage = GetPing();
|
NetworkPingMessage pingMessage = new NetworkPingMessage(LocalTime());
|
||||||
networkClient.Send(pingMessage);
|
NetworkClient.Send(pingMessage);
|
||||||
lastPingTime = Time.time;
|
lastPingTime = Time.time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,19 @@ public void Write(Vector4 value)
|
|||||||
Write(value.w);
|
Write(value.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Write(Vector2Int value)
|
||||||
|
{
|
||||||
|
WritePackedUInt32((uint)value.x);
|
||||||
|
WritePackedUInt32((uint)value.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(Vector3Int value)
|
||||||
|
{
|
||||||
|
WritePackedUInt32((uint)value.x);
|
||||||
|
WritePackedUInt32((uint)value.y);
|
||||||
|
WritePackedUInt32((uint)value.z);
|
||||||
|
}
|
||||||
|
|
||||||
public void Write(Color value)
|
public void Write(Color value)
|
||||||
{
|
{
|
||||||
Write(value.r);
|
Write(value.r);
|
||||||
@ -331,7 +344,7 @@ public void Write(GameObject value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(MessageBase msg)
|
public void Write<T>(T msg) where T : IMessageBase
|
||||||
{
|
{
|
||||||
msg.Serialize(this);
|
msg.Serialize(this);
|
||||||
}
|
}
|
||||||
|
303
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
303
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Mirror
|
||||||
|
{
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public abstract class SyncDictionary<K, V> : IDictionary<K, V>, SyncObject
|
||||||
|
{
|
||||||
|
public delegate void SyncDictionaryChanged(Operation op, K key, V item);
|
||||||
|
|
||||||
|
readonly Dictionary<K, V> m_Objects;
|
||||||
|
|
||||||
|
public int Count => m_Objects.Count;
|
||||||
|
public bool IsReadOnly { get; private set; }
|
||||||
|
public event SyncDictionaryChanged Callback;
|
||||||
|
|
||||||
|
public enum Operation : byte
|
||||||
|
{
|
||||||
|
OP_ADD,
|
||||||
|
OP_CLEAR,
|
||||||
|
OP_REMOVE,
|
||||||
|
OP_SET,
|
||||||
|
OP_DIRTY
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Change
|
||||||
|
{
|
||||||
|
internal Operation operation;
|
||||||
|
internal K key;
|
||||||
|
internal V item;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly List<Change> Changes = new List<Change>();
|
||||||
|
// how many changes we need to ignore
|
||||||
|
// this is needed because when we initialize the list,
|
||||||
|
// we might later receive changes that have already been applied
|
||||||
|
// so we need to skip them
|
||||||
|
int changesAhead = 0;
|
||||||
|
|
||||||
|
protected virtual void SerializeKey(NetworkWriter writer, K item) {}
|
||||||
|
protected virtual void SerializeItem(NetworkWriter writer, V item) {}
|
||||||
|
protected virtual K DeserializeKey(NetworkReader reader) => default;
|
||||||
|
protected virtual V DeserializeItem(NetworkReader reader) => default;
|
||||||
|
|
||||||
|
public bool IsDirty => Changes.Count > 0;
|
||||||
|
|
||||||
|
public ICollection<K> Keys => m_Objects.Keys;
|
||||||
|
|
||||||
|
public ICollection<V> Values => m_Objects.Values;
|
||||||
|
|
||||||
|
// throw away all the changes
|
||||||
|
// this should be called after a successfull sync
|
||||||
|
public void Flush() => Changes.Clear();
|
||||||
|
|
||||||
|
public SyncDictionary()
|
||||||
|
{
|
||||||
|
m_Objects = new Dictionary<K, V>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncDictionary(IEqualityComparer<K> eq)
|
||||||
|
{
|
||||||
|
m_Objects = new Dictionary<K, V>(eq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddOperation(Operation op, K key, V item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
{
|
||||||
|
throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server");
|
||||||
|
}
|
||||||
|
|
||||||
|
Change change = new Change
|
||||||
|
{
|
||||||
|
operation = op,
|
||||||
|
key = key,
|
||||||
|
item = item
|
||||||
|
};
|
||||||
|
|
||||||
|
Changes.Add(change);
|
||||||
|
|
||||||
|
Callback?.Invoke(op, key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSerializeAll(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
// if init, write the full list content
|
||||||
|
writer.WritePackedUInt32((uint)m_Objects.Count);
|
||||||
|
|
||||||
|
foreach (KeyValuePair<K, V> syncItem in m_Objects)
|
||||||
|
{
|
||||||
|
SerializeKey(writer, syncItem.Key);
|
||||||
|
SerializeItem(writer, syncItem.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// all changes have been applied already
|
||||||
|
// thus the client will need to skip all the pending changes
|
||||||
|
// or they would be applied again.
|
||||||
|
// So we write how many changes are pending
|
||||||
|
writer.WritePackedUInt32((uint)Changes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSerializeDelta(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
// write all the queued up changes
|
||||||
|
writer.WritePackedUInt32((uint)Changes.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < Changes.Count; i++)
|
||||||
|
{
|
||||||
|
Change change = Changes[i];
|
||||||
|
writer.Write((byte)change.operation);
|
||||||
|
|
||||||
|
switch (change.operation)
|
||||||
|
{
|
||||||
|
case Operation.OP_ADD:
|
||||||
|
case Operation.OP_REMOVE:
|
||||||
|
case Operation.OP_SET:
|
||||||
|
case Operation.OP_DIRTY:
|
||||||
|
SerializeKey(writer, change.key);
|
||||||
|
SerializeItem(writer, change.item);
|
||||||
|
break;
|
||||||
|
case Operation.OP_CLEAR:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDeserializeAll(NetworkReader reader)
|
||||||
|
{
|
||||||
|
// This list can now only be modified by synchronization
|
||||||
|
IsReadOnly = true;
|
||||||
|
|
||||||
|
// if init, write the full list content
|
||||||
|
int count = (int)reader.ReadPackedUInt32();
|
||||||
|
|
||||||
|
m_Objects.Clear();
|
||||||
|
Changes.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
K key = DeserializeKey(reader);
|
||||||
|
V obj = DeserializeItem(reader);
|
||||||
|
m_Objects.Add(key, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will need to skip all these changes
|
||||||
|
// the next time the list is synchronized
|
||||||
|
// because they have already been applied
|
||||||
|
changesAhead = (int)reader.ReadPackedUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDeserializeDelta(NetworkReader reader)
|
||||||
|
{
|
||||||
|
// This list can now only be modified by synchronization
|
||||||
|
IsReadOnly = true;
|
||||||
|
|
||||||
|
int changesCount = (int)reader.ReadPackedUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < changesCount; i++)
|
||||||
|
{
|
||||||
|
Operation operation = (Operation)reader.ReadByte();
|
||||||
|
|
||||||
|
// apply the operation only if it is a new change
|
||||||
|
// that we have not applied yet
|
||||||
|
bool apply = changesAhead == 0;
|
||||||
|
K key = default;
|
||||||
|
V item = default;
|
||||||
|
|
||||||
|
switch (operation)
|
||||||
|
{
|
||||||
|
case Operation.OP_ADD:
|
||||||
|
case Operation.OP_SET:
|
||||||
|
case Operation.OP_DIRTY:
|
||||||
|
key = DeserializeKey(reader);
|
||||||
|
item = DeserializeItem(reader);
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
m_Objects[key] = item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_CLEAR:
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
m_Objects.Clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_REMOVE:
|
||||||
|
key = DeserializeKey(reader);
|
||||||
|
item = DeserializeItem(reader);
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
m_Objects.Remove(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
Callback?.Invoke(operation, key, item);
|
||||||
|
}
|
||||||
|
// we just skipped this change
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changesAhead--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_Objects.Clear();
|
||||||
|
AddOperation(Operation.OP_CLEAR, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(K key) => m_Objects.ContainsKey(key);
|
||||||
|
|
||||||
|
public bool Remove(K key)
|
||||||
|
{
|
||||||
|
if (m_Objects.TryGetValue(key, out V item) && m_Objects.Remove(key))
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_REMOVE, key, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dirty(K index)
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_DIRTY, index, m_Objects[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V this[K i]
|
||||||
|
{
|
||||||
|
get => m_Objects[i];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (TryGetValue(i, out V val))
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_SET, i, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_ADD, i, value);
|
||||||
|
}
|
||||||
|
m_Objects[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(K key, out V value) => m_Objects.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
public void Add(K key, V value)
|
||||||
|
{
|
||||||
|
m_Objects.Add(key, value);
|
||||||
|
AddOperation(Operation.OP_ADD, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(KeyValuePair<K, V> item) => Add(item.Key, item.Value);
|
||||||
|
|
||||||
|
public bool Contains(KeyValuePair<K, V> item)
|
||||||
|
{
|
||||||
|
return TryGetValue(item.Key, out V val) && EqualityComparer<V>.Default.Equals(val, item.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
if (array == null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException("Array Is Null");
|
||||||
|
}
|
||||||
|
if (arrayIndex < 0 || arrayIndex > array.Length)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentOutOfRangeException("Array Index Out of Range");
|
||||||
|
}
|
||||||
|
if (array.Length - arrayIndex < Count)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = arrayIndex;
|
||||||
|
foreach (KeyValuePair<K,V> item in m_Objects)
|
||||||
|
{
|
||||||
|
array[i] = item;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<K, V> item)
|
||||||
|
{
|
||||||
|
bool result = m_Objects.Remove(item.Key);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<K, V>> GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b346c49cfdb668488a364c3023590e2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -43,6 +43,7 @@ public class SyncListBool : SyncList<bool>
|
|||||||
// in Unity 2019.1.
|
// in Unity 2019.1.
|
||||||
//
|
//
|
||||||
// TODO rename back to SyncListStruct after 2019.1!
|
// TODO rename back to SyncListStruct after 2019.1!
|
||||||
|
[Obsolete("Use SyncList<MyStruct> instead")]
|
||||||
public class SyncListSTRUCT<T> : SyncList<T> where T : struct
|
public class SyncListSTRUCT<T> : SyncList<T> where T : struct
|
||||||
{
|
{
|
||||||
protected override void SerializeItem(NetworkWriter writer, T item) {}
|
protected override void SerializeItem(NetworkWriter writer, T item) {}
|
||||||
@ -86,8 +87,8 @@ struct Change
|
|||||||
// so we need to skip them
|
// so we need to skip them
|
||||||
int changesAhead = 0;
|
int changesAhead = 0;
|
||||||
|
|
||||||
protected abstract void SerializeItem(NetworkWriter writer, T item);
|
protected virtual void SerializeItem(NetworkWriter writer, T item) { }
|
||||||
protected abstract T DeserializeItem(NetworkReader reader);
|
protected virtual T DeserializeItem(NetworkReader reader) => default(T);
|
||||||
|
|
||||||
public bool IsDirty => Changes.Count > 0;
|
public bool IsDirty => Changes.Count > 0;
|
||||||
|
|
||||||
|
21
Assets/Mirror/Runtime/Transport/Telepathy/LICENSE
Normal file
21
Assets/Mirror/Runtime/Transport/Telepathy/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018, vis2k
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
7
Assets/Mirror/Runtime/Transport/Telepathy/LICENSE.meta
Normal file
7
Assets/Mirror/Runtime/Transport/Telepathy/LICENSE.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0ba11103b95fd4721bffbb08440d5b8e
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Binary file not shown.
@ -10,6 +10,9 @@ public class TelepathyTransport : Transport
|
|||||||
[Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
|
[Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
|
||||||
public bool NoDelay = true;
|
public bool NoDelay = true;
|
||||||
|
|
||||||
|
[Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")]
|
||||||
|
public int MaxMessageSize = 16 * 1024;
|
||||||
|
|
||||||
protected Telepathy.Client client = new Telepathy.Client();
|
protected Telepathy.Client client = new Telepathy.Client();
|
||||||
protected Telepathy.Server server = new Telepathy.Server();
|
protected Telepathy.Server server = new Telepathy.Server();
|
||||||
|
|
||||||
@ -22,7 +25,9 @@ void Awake()
|
|||||||
|
|
||||||
// configure
|
// configure
|
||||||
client.NoDelay = NoDelay;
|
client.NoDelay = NoDelay;
|
||||||
|
client.MaxMessageSize = MaxMessageSize;
|
||||||
server.NoDelay = NoDelay;
|
server.NoDelay = NoDelay;
|
||||||
|
server.MaxMessageSize = MaxMessageSize;
|
||||||
|
|
||||||
// HLAPI's local connection uses hard coded connectionId '0', so we
|
// HLAPI's local connection uses hard coded connectionId '0', so we
|
||||||
// need to make sure that external connections always start at '1'
|
// need to make sure that external connections always start at '1'
|
||||||
|
51
Assets/Mirror/Tests/MessageBaseTests.cs
Normal file
51
Assets/Mirror/Tests/MessageBaseTests.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Mirror
|
||||||
|
{
|
||||||
|
struct TestMessage : IMessageBase
|
||||||
|
{
|
||||||
|
public int IntValue;
|
||||||
|
public string StringValue;
|
||||||
|
public double DoubleValue;
|
||||||
|
|
||||||
|
public TestMessage(int i, string s, double d)
|
||||||
|
{
|
||||||
|
IntValue = i;
|
||||||
|
StringValue = s;
|
||||||
|
DoubleValue = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(NetworkReader reader)
|
||||||
|
{
|
||||||
|
IntValue = reader.ReadInt32();
|
||||||
|
StringValue = reader.ReadString();
|
||||||
|
DoubleValue = reader.ReadDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Serialize(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(IntValue);
|
||||||
|
writer.Write(StringValue);
|
||||||
|
writer.Write(DoubleValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class MessageBaseTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Roundtrip()
|
||||||
|
{
|
||||||
|
NetworkWriter w = new NetworkWriter();
|
||||||
|
w.Write(new TestMessage(1, "2", 3.3));
|
||||||
|
|
||||||
|
byte[] arr = w.ToArray();
|
||||||
|
|
||||||
|
NetworkReader r = new NetworkReader(arr);
|
||||||
|
TestMessage t = new TestMessage();
|
||||||
|
t.Deserialize(r);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, t.IntValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Tests/MessageBaseTests.cs.meta
Normal file
11
Assets/Mirror/Tests/MessageBaseTests.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ecf93fcf0386fee4e85f981d5ca9259d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
233
Assets/Mirror/Tests/SyncDictionaryTest.cs
Normal file
233
Assets/Mirror/Tests/SyncDictionaryTest.cs
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Mirror.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SyncDictionaryTest
|
||||||
|
{
|
||||||
|
public class SyncDictionaryIntString : SyncDictionary<int, string> {}
|
||||||
|
|
||||||
|
SyncDictionaryIntString serverSyncDictionary;
|
||||||
|
SyncDictionaryIntString clientSyncDictionary;
|
||||||
|
|
||||||
|
private void SerializeAllTo<T>(T fromList, T toList) where T : SyncObject
|
||||||
|
{
|
||||||
|
NetworkWriter writer = new NetworkWriter();
|
||||||
|
fromList.OnSerializeAll(writer);
|
||||||
|
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||||
|
toList.OnDeserializeAll(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SerializeDeltaTo<T>(T fromList, T toList) where T : SyncObject
|
||||||
|
{
|
||||||
|
NetworkWriter writer = new NetworkWriter();
|
||||||
|
fromList.OnSerializeDelta(writer);
|
||||||
|
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||||
|
toList.OnDeserializeDelta(reader);
|
||||||
|
fromList.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
serverSyncDictionary = new SyncDictionaryIntString();
|
||||||
|
clientSyncDictionary = new SyncDictionaryIntString();
|
||||||
|
|
||||||
|
// add some data to the list
|
||||||
|
serverSyncDictionary.Add(0, "Hello");
|
||||||
|
serverSyncDictionary.Add(1, "World");
|
||||||
|
serverSyncDictionary.Add(2, "!");
|
||||||
|
SerializeAllTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInit()
|
||||||
|
{
|
||||||
|
Dictionary<int, string> comparer = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[0] = "Hello",
|
||||||
|
[1] = "World",
|
||||||
|
[2] = "!"
|
||||||
|
};
|
||||||
|
Assert.That(clientSyncDictionary[0], Is.EqualTo("Hello"));
|
||||||
|
Assert.That(clientSyncDictionary, Is.EquivalentTo(comparer));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAdd()
|
||||||
|
{
|
||||||
|
serverSyncDictionary.Add(4, "yay");
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(4), Is.EqualTo(true));
|
||||||
|
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
serverSyncDictionary.Clear();
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(serverSyncDictionary, Is.EquivalentTo(new SyncDictionaryIntString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSet()
|
||||||
|
{
|
||||||
|
serverSyncDictionary[1] = "yay";
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(1));
|
||||||
|
Assert.That(clientSyncDictionary[1], Is.EqualTo("yay"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBareSet()
|
||||||
|
{
|
||||||
|
serverSyncDictionary[4] = "yay";
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(4));
|
||||||
|
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBareSetNull()
|
||||||
|
{
|
||||||
|
serverSyncDictionary[4] = null;
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary[4], Is.Null);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConsecutiveSet()
|
||||||
|
{
|
||||||
|
serverSyncDictionary[1] = "yay";
|
||||||
|
serverSyncDictionary[1] = "world";
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary[1], Is.EqualTo("world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNullSet()
|
||||||
|
{
|
||||||
|
serverSyncDictionary[1] = null;
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(1));
|
||||||
|
Assert.That(clientSyncDictionary[1], Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemove()
|
||||||
|
{
|
||||||
|
serverSyncDictionary.Remove(1);
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(1), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultSync()
|
||||||
|
{
|
||||||
|
serverSyncDictionary.Add(10, "1");
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
// add some delta and see if it applies
|
||||||
|
serverSyncDictionary.Add(11, "2");
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(10));
|
||||||
|
Assert.That(clientSyncDictionary[10], Is.EqualTo("1"));
|
||||||
|
Assert.That(clientSyncDictionary.ContainsKey(11));
|
||||||
|
Assert.That(clientSyncDictionary[11], Is.EqualTo("2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestContains()
|
||||||
|
{
|
||||||
|
Assert.That(!clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
|
||||||
|
serverSyncDictionary[2] = "Hello";
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CallbackTest()
|
||||||
|
{
|
||||||
|
bool called = false;
|
||||||
|
clientSyncDictionary.Callback += (op, index, item) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
|
||||||
|
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_ADD));
|
||||||
|
Assert.That(index, Is.EqualTo(3));
|
||||||
|
Assert.That(item, Is.EqualTo("yay"));
|
||||||
|
};
|
||||||
|
serverSyncDictionary.Add(3, "yay");
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(called, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CallbackRemoveTest()
|
||||||
|
{
|
||||||
|
bool called = false;
|
||||||
|
clientSyncDictionary.Callback += (op, key, item) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_REMOVE));
|
||||||
|
Assert.That(item, Is.EqualTo("World"));
|
||||||
|
};
|
||||||
|
serverSyncDictionary.Remove(1);
|
||||||
|
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||||
|
Assert.That(called, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CountTest()
|
||||||
|
{
|
||||||
|
Assert.That(serverSyncDictionary.Count, Is.EqualTo(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadOnlyTest()
|
||||||
|
{
|
||||||
|
Assert.That(serverSyncDictionary.IsReadOnly, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DirtyTest()
|
||||||
|
{
|
||||||
|
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
|
||||||
|
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
|
||||||
|
|
||||||
|
// nothing to send
|
||||||
|
Assert.That(serverList.IsDirty, Is.False);
|
||||||
|
|
||||||
|
// something has changed
|
||||||
|
serverList.Add(15, "yay");
|
||||||
|
Assert.That(serverList.IsDirty, Is.True);
|
||||||
|
SerializeDeltaTo(serverList, clientList);
|
||||||
|
|
||||||
|
// data has been flushed, should go back to clear
|
||||||
|
Assert.That(serverList.IsDirty, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadonlyTest()
|
||||||
|
{
|
||||||
|
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
|
||||||
|
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
|
||||||
|
|
||||||
|
// data has been flushed, should go back to clear
|
||||||
|
Assert.That(clientList.IsReadOnly, Is.False);
|
||||||
|
|
||||||
|
serverList.Add(20, "yay");
|
||||||
|
serverList.Add(30, "hello");
|
||||||
|
serverList.Add(35, "world");
|
||||||
|
SerializeDeltaTo(serverList, clientList);
|
||||||
|
|
||||||
|
// client list should now lock itself, trying to modify it
|
||||||
|
// should produce an InvalidOperationException
|
||||||
|
Assert.That(clientList.IsReadOnly, Is.True);
|
||||||
|
Assert.Throws<InvalidOperationException>(() => clientList.Add(50, "fail"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Tests/SyncDictionaryTest.cs.meta
Normal file
11
Assets/Mirror/Tests/SyncDictionaryTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cadf48c3662efac4181b91f5c9c88774
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -263,6 +263,13 @@ public void SyncListMissingParamlessCtor()
|
|||||||
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
||||||
Assert.That(m_weaverErrors[0], Does.Match("Missing parameter-less constructor"));
|
Assert.That(m_weaverErrors[0], Does.Match("Missing parameter-less constructor"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SyncListByteValid() {
|
||||||
|
Assert.That(CompilationFinishedHook.WeaveFailed, Is.False);
|
||||||
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SyncListStruct tests
|
#region SyncListStruct tests
|
||||||
@ -278,32 +285,32 @@ public void SyncListStructGenericGeneric()
|
|||||||
{
|
{
|
||||||
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
||||||
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
||||||
Assert.That(m_weaverErrors[0], Does.Match("Struct passed into SyncListStruct<T> can't have generic parameters"));
|
Assert.That(m_weaverErrors[0], Is.EqualTo("Mirror.Weaver error: GenerateSerialization for MyGenericStruct<Single> failed. Can't have generic parameters"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SyncListStructMemberGeneric()
|
public void SyncListStructMemberGeneric()
|
||||||
{
|
{
|
||||||
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
||||||
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(2));
|
||||||
Assert.That(m_weaverErrors[0], Does.Match("member cannot have generic parameters"));
|
Assert.That(m_weaverErrors[0], Is.EqualTo("Mirror.Weaver error: WriteReadFunc for potato [MirrorTest.MirrorTestPlayer/MyGenericStruct`1<System.Single>/MirrorTest.MirrorTestPlayer/MyGenericStruct`1<System.Single>]. Cannot have generic parameters."));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SyncListStructMemberInterface()
|
public void SyncListStructMemberInterface()
|
||||||
{
|
{
|
||||||
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
||||||
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(2));
|
||||||
Assert.That(m_weaverErrors[0], Does.Match("member cannot be an interface"));
|
Assert.That(m_weaverErrors[0], Is.EqualTo( "Mirror.Weaver error: WriteReadFunc for potato [MirrorTest.MirrorTestPlayer/IPotato/MirrorTest.MirrorTestPlayer/IPotato]. Cannot be an interface."));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SyncListStructMemberBasicType()
|
public void SyncListStructMemberBasicType()
|
||||||
{
|
{
|
||||||
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
Assert.That(CompilationFinishedHook.WeaveFailed, Is.True);
|
||||||
Assert.That(m_weaverErrors.Count, Is.EqualTo(2));
|
Assert.That(m_weaverErrors.Count, Is.EqualTo(3));
|
||||||
Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type"));
|
Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type"));
|
||||||
Assert.That(m_weaverErrors[1], Does.Match("member variables must be basic types"));
|
Assert.That(m_weaverErrors[1], Does.Match("Mirror.Weaver error: WriteReadFunc for nonbasicpotato type System.Object no supported"));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
12
Assets/Mirror/Tests/WeaverTests~/SyncListByteValid.cs
Normal file
12
Assets/Mirror/Tests/WeaverTests~/SyncListByteValid.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using Mirror;
|
||||||
|
|
||||||
|
namespace MirrorTest
|
||||||
|
{
|
||||||
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
|
{
|
||||||
|
class MyByteClass : SyncList<byte> {};
|
||||||
|
|
||||||
|
MyByteClass Foo;
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ struct MyStruct
|
|||||||
MyGenericStruct<MyPODStruct> potato;
|
MyGenericStruct<MyPODStruct> potato;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyStructClass : SyncListSTRUCT<MyGenericStruct<float>> {};
|
class MyStructClass : SyncList<MyGenericStruct<float>> {};
|
||||||
|
|
||||||
MyStructClass harpseals;
|
MyStructClass harpseals;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ struct MyStruct
|
|||||||
public object nonbasicpotato;
|
public object nonbasicpotato;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyStructClass : SyncListSTRUCT<MyStruct>
|
class MyStructClass : SyncList<MyStruct>
|
||||||
{
|
{
|
||||||
int potatoCount;
|
int potatoCount;
|
||||||
public MyStructClass(int numberOfPotatoes)
|
public MyStructClass(int numberOfPotatoes)
|
||||||
|
@ -20,7 +20,7 @@ struct MyStruct
|
|||||||
public MyGenericStruct<float> potato;
|
public MyGenericStruct<float> potato;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyStructClass : SyncListSTRUCT<MyStruct> {};
|
class MyStructClass : SyncList<MyStruct> {};
|
||||||
|
|
||||||
MyStructClass harpseals;
|
MyStructClass harpseals;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ struct MyStruct
|
|||||||
public IPotato potato;
|
public IPotato potato;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyStructClass : SyncListSTRUCT<MyStruct> {};
|
class MyStructClass : SyncList<MyStruct> {};
|
||||||
|
|
||||||
MyStructClass harpseals;
|
MyStructClass harpseals;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ struct MyStruct
|
|||||||
float floatingpotato;
|
float floatingpotato;
|
||||||
double givemetwopotatoes;
|
double givemetwopotatoes;
|
||||||
}
|
}
|
||||||
class MyStructClass : SyncListSTRUCT<MyStruct> {};
|
class MyStructClass : SyncList<MyStruct> {};
|
||||||
MyStructClass Foo;
|
MyStructClass Foo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
class MySyncVar : NetworkBehaviour
|
class MySyncVar : NetworkBehaviour
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
class MySyncVar : ScriptableObject
|
class MySyncVar : ScriptableObject
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
[SyncVar]
|
[SyncVar]
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
class MySyncVar<T>
|
class MySyncVar<T>
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
interface MySyncVar
|
interface MySyncVar
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
[SyncVar] int var2;
|
[SyncVar] int var2;
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook = "OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
public void TakeDamage(int amount)
|
public void TakeDamage(int amount)
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
[SyncVar]
|
[SyncVar]
|
||||||
|
@ -15,7 +15,7 @@ public void OnDeserializeDelta(NetworkReader reader) {}
|
|||||||
|
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook = "OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
[SyncVar]
|
[SyncVar]
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook = "OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
public void TakeDamage(int amount)
|
public void TakeDamage(int amount)
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook="OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
[SyncVar] int var2;
|
[SyncVar] int var2;
|
||||||
|
@ -5,7 +5,7 @@ namespace MirrorTest
|
|||||||
{
|
{
|
||||||
class MirrorTestPlayer : NetworkBehaviour
|
class MirrorTestPlayer : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[SyncVar(hook = "OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
int health;
|
int health;
|
||||||
|
|
||||||
public void TakeDamage(int amount)
|
public void TakeDamage(int amount)
|
||||||
|
@ -5,3 +5,4 @@ EditorBuildSettings:
|
|||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Scenes: []
|
m_Scenes: []
|
||||||
|
m_configObjects: {}
|
||||||
|
@ -13,6 +13,7 @@ install:
|
|||||||
- bundle install
|
- bundle install
|
||||||
- cd ..
|
- cd ..
|
||||||
- cp c:\Tools\curl\bin\libcurl.dll C:\Ruby25-x64\bin
|
- cp c:\Tools\curl\bin\libcurl.dll C:\Ruby25-x64\bin
|
||||||
|
- choco install unitypackager
|
||||||
|
|
||||||
#build:
|
#build:
|
||||||
# project: Mirror/Networking.sln
|
# project: Mirror/Networking.sln
|
||||||
@ -27,9 +28,11 @@ build_script:
|
|||||||
- ruby checksite.rb
|
- ruby checksite.rb
|
||||||
- cd ..
|
- cd ..
|
||||||
|
|
||||||
|
after_build:
|
||||||
|
- UnityPackager pack Mirror.unitypackage Assets/Mirror Assets/Mirror LICENSE Assets/Mirror/LICENSE
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: Assets
|
- path: Mirror.unitypackage
|
||||||
name: Mirror
|
|
||||||
|
|
||||||
image: Visual Studio 2017
|
image: Visual Studio 2017
|
||||||
|
|
||||||
|
@ -30,4 +30,4 @@ These attributes can be used for Unity game loop methods like Start or Update, a
|
|||||||
- **SyncVar**
|
- **SyncVar**
|
||||||
SyncVars are used to synchronize a variable from the server to all clients automatically. Don't assign them from a client, it's pointless. Don't let them be null, you will get errors. You can use int, long, float, string, Vector3 etc. (all simple types) and NetworkIdentity and GameObject if the GameObject has a NetworkIdentity attached to it. You can use [hooks](SyncVarHook).
|
SyncVars are used to synchronize a variable from the server to all clients automatically. Don't assign them from a client, it's pointless. Don't let them be null, you will get errors. You can use int, long, float, string, Vector3 etc. (all simple types) and NetworkIdentity and GameObject if the GameObject has a NetworkIdentity attached to it. You can use [hooks](SyncVarHook).
|
||||||
- **SyncEvent**
|
- **SyncEvent**
|
||||||
Something about this
|
Networked events like ClientRpc's, but instead of calling a function on the GameObject, they trigger Events instead.
|
||||||
|
@ -14,19 +14,7 @@ NetworkServer is a High-Level-API class that manages connections from multiple c
|
|||||||
Dictionary of the message handlers registered with the server.
|
Dictionary of the message handlers registered with the server.
|
||||||
- **hostTopology**
|
- **hostTopology**
|
||||||
The host topology that the server is using.
|
The host topology that the server is using.
|
||||||
- **listenPort**
|
|
||||||
The port that the server is listening on.
|
|
||||||
- **localClientActive**
|
- **localClientActive**
|
||||||
True if a local client is currently active on the server.
|
True if a local client is currently active on the server.
|
||||||
- **localConnection**
|
- **localConnection**
|
||||||
The connection to the local client. This is only used when in host mode
|
The connection to the local client. This is only used when in host mode
|
||||||
- **maxDelay**
|
|
||||||
The maximum delay before sending packets on connections.
|
|
||||||
- **networkConnectionClass**
|
|
||||||
The class to be used when creating new network connections.
|
|
||||||
- **numChannels**
|
|
||||||
The number of channels the network is configure with.
|
|
||||||
- **serverHostId**
|
|
||||||
The transport layer hostId used by this server.
|
|
||||||
- **useWebSockets**
|
|
||||||
This makes the server listen for WebSockets connections instead of normal transport layer connections.
|
|
||||||
|
55
docs/Classes/SyncDictionary.md
Normal file
55
docs/Classes/SyncDictionary.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# SyncDictionary
|
||||||
|
|
||||||
|
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be of the following types:
|
||||||
|
|
||||||
|
- Basic type (byte, int, float, string, UInt64, etc)
|
||||||
|
- Built-in Unity math type (Vector3, Quaternion, etc)
|
||||||
|
- NetworkIdentity
|
||||||
|
- GameObject with a NetworkIdentity component attached.
|
||||||
|
- Struct with any of the above
|
||||||
|
|
||||||
|
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
|
||||||
|
|
||||||
|
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
|
||||||
|
|
||||||
|
## Simple Example
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using Mirror;
|
||||||
|
|
||||||
|
public class ExamplePlayer : NetworkBehaviour
|
||||||
|
{
|
||||||
|
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
|
||||||
|
|
||||||
|
public struct Item
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public int hitPoints;
|
||||||
|
public int durability;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
|
||||||
|
|
||||||
|
public void OnStartServer()
|
||||||
|
{
|
||||||
|
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
|
||||||
|
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
|
||||||
|
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
|
||||||
|
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartClient()
|
||||||
|
{
|
||||||
|
// Equipment is already populated with anything the server set up
|
||||||
|
// but we can subscribe to the callback in case it is updated later on
|
||||||
|
Equipment.Callback += OnEquipmentChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
|
||||||
|
{
|
||||||
|
// equipment changed, perhaps update the gameobject
|
||||||
|
Debug.Log(op + " - " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
3
docs/Classes/SyncHashSet.md
Normal file
3
docs/Classes/SyncHashSet.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# SyncHashSet
|
||||||
|
|
||||||
|
Need description and code samples for SyncHashSet.
|
@ -1,58 +1,122 @@
|
|||||||
# SyncLists Overview
|
# SyncLists Overview
|
||||||
|
|
||||||
There are some very important optimizations when it comes to bandwidth done in Mirror.
|
`SyncLists` are array based lists similar to C# [List\<T\>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.2) that synchronize their contents from the server to the clients.
|
||||||
|
|
||||||
## Channels
|
A SyncList can contain items of the following types:
|
||||||
|
|
||||||
There was a bug in HLAPI that caused syncvar to be sent to every channel when they changed. If you had 10 channels, then all the variables would be sent 10 times during the same frame, all as different packages.
|
|
||||||
|
|
||||||
## SyncLists
|
|
||||||
|
|
||||||
HLAPI SyncLists sent a message for every change immediately. They did not respect the SyncInterval. If you add 10 items to a list, it means sending 10 messages.
|
|
||||||
|
|
||||||
In Mirror SyncList were redesigned. The lists queue up their changes, and the changes are sent as part of the syncvar synchronization. If you add 10 items, then only 1 message is sent with all changes according to the next SyncInterval.
|
|
||||||
|
|
||||||
We also raised the limit from 32 SyncVars to 64 per NetworkBehavior.
|
|
||||||
|
|
||||||
A SyncList can only be of the following type
|
|
||||||
|
|
||||||
- Basic type (byte, int, float, string, UInt64, etc)
|
- Basic type (byte, int, float, string, UInt64, etc)
|
||||||
- Built-in Unity math type (Vector3, Quaternion, etc)
|
- Built-in Unity math type (Vector3, Quaternion, etc)
|
||||||
- NetworkIdentity
|
- NetworkIdentity
|
||||||
- GameObject with a NetworkIdentity component attached.
|
- GameObject with a NetworkIdentity component attached.
|
||||||
|
- Structure with any of the above
|
||||||
|
|
||||||
|
## Differences with HLAPI
|
||||||
|
|
||||||
|
HLAPI also supports SyncLists, but we have made redesigned them to better suit our needs. Some of the key differences include:
|
||||||
|
|
||||||
|
* In HLAPI, SyncLists were synchornized immediatelly when they changed. If you add 10 elements, that means 10 separate messages. Mirror synchronizes synclists with the syncvars. The 10 elements and other syncvars are batched together into a single message. Mirror also respects the sync interval when synchronizing lists.
|
||||||
|
|
||||||
|
* In HLAPI if you want a list of structs, you have to use `SyncListStruct<MyStructure>`, we changed it to just `SyncList<MyStructure>`
|
||||||
|
|
||||||
|
* In HLAPI the Callback is a delegate. In Mirror we changed it to an event, so that you can add many subscribers.
|
||||||
|
|
||||||
|
* In HLAPI the Callback tells you the operation and index. In Mirror, the callback also receives an item. We made this change so that we could tell what item was removed.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Don't modify them in Awake, use OnStartServer or Start. SyncListStructs use Structs. C\# structs are value types, just like int, float, Vector3 etc. You can't do synclist.value = newvalue; You have to copy the element, assign the new value, assign the new element to the synclist. You can use a callback hook like this:
|
Create a class that derives from SyncList<T> for your specific type. This is necesary because Mirror will add methods to that class with the weaver. Then add a SyncList field to your NetworkBehaviour class. For example:
|
||||||
|
|
||||||
```cs
|
```cs
|
||||||
// In this example we are using String
|
|
||||||
// This can also be used for any custom data type
|
public struct Item
|
||||||
SyncListString myStringList;
|
{
|
||||||
SyncListItem myItemList;
|
public string name;
|
||||||
|
public int amount;
|
||||||
|
public Color32 color;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncListItem : SyncList<Item> {}
|
||||||
|
|
||||||
|
class Player : NetworkBehaviour {
|
||||||
|
|
||||||
|
SyncListItem inventory;
|
||||||
|
|
||||||
|
public int coins = 100;
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
public void CmdPurchase(string itemName)
|
||||||
|
{
|
||||||
|
if (coins > 10)
|
||||||
|
{
|
||||||
|
coins -= 10;
|
||||||
|
Item item = new Item
|
||||||
|
{
|
||||||
|
name = "Sword",
|
||||||
|
amount = 3,
|
||||||
|
color = new Color32(125,125,125);
|
||||||
|
};
|
||||||
|
|
||||||
|
// during next synchronization, all clients will see the item
|
||||||
|
inventory.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some ready made SyncLists you can use:
|
||||||
|
* `SyncListString`
|
||||||
|
* `SyncListFloat`
|
||||||
|
* `SyncListInt`
|
||||||
|
* `SyncListUInt`
|
||||||
|
* `SyncListBool`
|
||||||
|
|
||||||
|
You can also detect when a synclist changes in the client or server. This is useful for refreshing your character when you add equipment or determining when you need to update your database. Subscribe to the Callback event typically during `Start`, `OnClientStart` or `OnServerStart` for that. Note that by the time you subscribe, the list will already be initialized, so you will not get a call for the initial data, only updates.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
class Player : NetworkBehaviour {
|
||||||
|
|
||||||
|
SyncListItem inventory;
|
||||||
|
|
||||||
// this will add the delegates on both server and client.
|
// this will add the delegates on both server and client.
|
||||||
// Use OnStartClient instead if you just want the client to act upon updates
|
// Use OnStartClient instead if you just want the client to act upon updates
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
myStringList.Callback += MyStringCallback;
|
myStringList.Callback += OnInventoryUpdated;
|
||||||
myItemList.Callback += MyItemCallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyStringCallback(SyncListString.Operation op, int index, String item)
|
void OnInventoryUpdated(SyncListItem.Operation op, int index, Item item)
|
||||||
{
|
{
|
||||||
// Callback functionality here
|
switch (op)
|
||||||
}
|
|
||||||
|
|
||||||
void MyItemCallback(SyncListItem.Operation op, int index, Item item)
|
|
||||||
{
|
{
|
||||||
// Callback functionality here
|
case SyncListItem.Operation.OP_ADD:
|
||||||
|
// index is where it got added in the list
|
||||||
|
// item is the new item
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_CLEAR:
|
||||||
|
// list got cleared
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_INSERT:
|
||||||
|
// index is where it got added in the list
|
||||||
|
// item is the new item
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_REMOVE:
|
||||||
|
// index is where it got removed in the list
|
||||||
|
// item is the item that was removed
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_REMOVEAT:
|
||||||
|
// index is where it got removed in the list
|
||||||
|
// item is the item that was removed
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_SET:
|
||||||
|
// index is the index of the item that was updated
|
||||||
|
// item is the previous item
|
||||||
|
break;
|
||||||
|
case SyncListItem.Operation.OP_SET:
|
||||||
|
// index is the index of the item that was updated
|
||||||
|
// item is the previous item
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Item
|
|
||||||
{
|
|
||||||
// Define struct
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SyncListItem : SyncListSTRUCT<Item> { }
|
|
||||||
```
|
```
|
||||||
|
@ -13,7 +13,7 @@ public class Health : NetworkBehaviour
|
|||||||
public const int m_MaxHealth = 100;
|
public const int m_MaxHealth = 100;
|
||||||
|
|
||||||
//Detects when a health change happens and calls the appropriate function
|
//Detects when a health change happens and calls the appropriate function
|
||||||
[SyncVar(hook = "OnChangeHealth")]
|
[SyncVar(hook = nameof(OnChangeHealth))]
|
||||||
public int m_CurrentHealth = m_MaxHealth;
|
public int m_CurrentHealth = m_MaxHealth;
|
||||||
public RectTransform healthBar;
|
public RectTransform healthBar;
|
||||||
|
|
||||||
|
@ -13,9 +13,4 @@ General description of Classes
|
|||||||
- [Attributes](Attributes)
|
- [Attributes](Attributes)
|
||||||
Networking attributes are added to member functions of NetworkBehaviour scripts, to make them run on either the client or server.
|
Networking attributes are added to member functions of NetworkBehaviour scripts, to make them run on either the client or server.
|
||||||
- [SyncLists](SyncLists)
|
- [SyncLists](SyncLists)
|
||||||
SyncLists contain lists of values:
|
SyncLists contain lists of values and synchronize data from servers to clients.
|
||||||
- SyncListString
|
|
||||||
- SyncListFloat
|
|
||||||
- SyncListInt
|
|
||||||
- SyncListUInt
|
|
||||||
- SyncListBool
|
|
||||||
|
@ -71,3 +71,59 @@ public class MyScript : NetworkBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SyncDictionaries
|
||||||
|
|
||||||
|
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be of the following types:
|
||||||
|
|
||||||
|
- Basic type (byte, int, float, string, UInt64, etc)
|
||||||
|
- Built-in Unity math type (Vector3, Quaternion, etc)
|
||||||
|
- NetworkIdentity
|
||||||
|
- GameObject with a NetworkIdentity component attached.
|
||||||
|
- Struct with any of the above
|
||||||
|
|
||||||
|
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
|
||||||
|
|
||||||
|
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
|
||||||
|
|
||||||
|
### Simple Example
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using Mirror;
|
||||||
|
|
||||||
|
public class ExamplePlayer : NetworkBehaviour
|
||||||
|
{
|
||||||
|
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
|
||||||
|
|
||||||
|
public struct Item
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public int hitPoints;
|
||||||
|
public int durability;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
|
||||||
|
|
||||||
|
public void OnStartServer()
|
||||||
|
{
|
||||||
|
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
|
||||||
|
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
|
||||||
|
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
|
||||||
|
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartClient()
|
||||||
|
{
|
||||||
|
// Equipment is already populated with anything the server set up
|
||||||
|
// but we can subscribe to the callback in case it is updated later on
|
||||||
|
Equipment.Callback += OnEquipmentChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
|
||||||
|
{
|
||||||
|
// equipment changed, perhaps update the gameobject
|
||||||
|
Debug.Log(op + " - " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -3,13 +3,30 @@
|
|||||||
## Version 1.7 -- In Progress
|
## Version 1.7 -- In Progress
|
||||||
|
|
||||||
- Added: Semantic Versioning
|
- Added: Semantic Versioning
|
||||||
- Added: SyncDictionary...SyncHashSet coming soon™
|
- Added: [SyncDictionary](../Classes/SyncDictionary) ... [SyncHashSet](../Classes/SyncHashSet) coming soon™
|
||||||
|
- Added: NoRotation to NetworkTransform
|
||||||
|
- Added: Scale is now included in spawn payload along with position and rotation
|
||||||
|
- Added: Generic `IMessageBase` to allow struct message types
|
||||||
|
- Added: Weaver now supports Vector2Int and Vector3Int
|
||||||
|
- Added: List Server example
|
||||||
|
- Fixed: SyncLists now work correctly for primitives and structs
|
||||||
|
- Fixed: SyncVar Hooks now will update the local property value after the hook is called
|
||||||
|
- You no longer need to have a line of code in your hook method to manualy update the local property.
|
||||||
- Fixed: Host should not call Disconnect on transports
|
- Fixed: Host should not call Disconnect on transports
|
||||||
|
- Fixed: NetworkAnimimator now supports up to 64 animator parameters
|
||||||
|
- Fixed: NetworkManager `StartServer` no longer assumes scene zero is the default scene...uses `GetActiveScene` now
|
||||||
|
- Fixed: NetworkServer `Shutdown` now resets `netId` to zero
|
||||||
|
- Fixed: Observers are now properly rebuilt when client joins and `OnRebuildObservers` / `OnCheckObserver` is overridden
|
||||||
|
- Fixed: NetworkLobbyPlayer `OnClientReady` works now
|
||||||
|
- Fixed: NetworkLobbyManager `pendingPlayers` and `lobbySlots` lists are now public for inheritors
|
||||||
- Fixed: Offline scene switching now works via `StopClient()`
|
- Fixed: Offline scene switching now works via `StopClient()`
|
||||||
- Fixed: Pong example updated
|
- Fixed: Pong example updated
|
||||||
|
- Changed: TargetRpc NetworkConnection paramater is now optional...the calling client's NetworkConnection is default
|
||||||
- Changed: Movement example replaced with Tank example
|
- Changed: Movement example replaced with Tank example
|
||||||
|
- Changed: NetworkClient functions are all static now, so the singleton is gone. Use NetworkClient directly.
|
||||||
- Changed: SyncList now supports structs directly, making SyncListSTRUCT obsolete.
|
- Changed: SyncList now supports structs directly, making SyncListSTRUCT obsolete.
|
||||||
- Removed: SyncListSTRUCT - Use SyncList instead.
|
- Removed: SyncListSTRUCT - Use SyncList instead.
|
||||||
|
- Removed: NetworkClient.ShutdownAll is obsolete -- Use NetworkClient.Shutdown instead
|
||||||
|
|
||||||
## Version 1.6 -- 2019-Mar-14
|
## Version 1.6 -- 2019-Mar-14
|
||||||
|
|
||||||
@ -20,6 +37,7 @@
|
|||||||
- Changed: Documentation for [Transports](../Transports)
|
- Changed: Documentation for [Transports](../Transports)
|
||||||
- Changed: Weaver is now full source...FINALLY!
|
- Changed: Weaver is now full source...FINALLY!
|
||||||
- Changed: ClientScene.AddPlayer 2nd parameter is now `byte[] extraData` instead of `MessageBase extraMessage`
|
- Changed: ClientScene.AddPlayer 2nd parameter is now `byte[] extraData` instead of `MessageBase extraMessage`
|
||||||
|
- Please refer to the code sample [here](../Concepts/Authentication) to see how to update your code.
|
||||||
- Changed: NetworkManager -- Headless Auto-Start moved to `Start()` from `Awake()`
|
- Changed: NetworkManager -- Headless Auto-Start moved to `Start()` from `Awake()`
|
||||||
- Changed: Removed Message ID's for all messages - See [Network Messages](../Concepts/Communications/NetworkMessages) for details
|
- Changed: Removed Message ID's for all messages - See [Network Messages](../Concepts/Communications/NetworkMessages) for details
|
||||||
- Message IDs are now generated automatically based on the message name.
|
- Message IDs are now generated automatically based on the message name.
|
||||||
|
BIN
docs/General/Integrations/Dissonance.jpg
Normal file
BIN
docs/General/Integrations/Dissonance.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
docs/General/Integrations/NetworkSyncTransform.jpg
Normal file
BIN
docs/General/Integrations/NetworkSyncTransform.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
docs/General/Integrations/NobleConnectFree.jpg
Normal file
BIN
docs/General/Integrations/NobleConnectFree.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user