Merge pull request #5 from vis2k/master

Catchup
This commit is contained in:
MrGadget 2019-03-27 17:18:18 -04:00 committed by GitHub
commit ad4a421a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 6351 additions and 1223 deletions

View File

@ -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"}
]
}],
] ]
} }

View File

@ -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);
} }

View File

@ -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() {}

View File

@ -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()
{ {

View File

@ -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)
{ {

View File

@ -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()
{ {

View File

@ -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;
}
}
}
}

View File

@ -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
@ -51,18 +50,18 @@ public static void OnPostProcessScene()
identity.gameObject.SetActive(false); identity.gameObject.SetActive(false);
// safety check for prefabs with more than one NetworkIdentity // safety check for prefabs with more than one NetworkIdentity
#if UNITY_2018_2_OR_NEWER #if UNITY_2018_2_OR_NEWER
GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject) as GameObject; GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject) as GameObject;
#else #else
GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject) as GameObject; GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject) as GameObject;
#endif #endif
if (prefabGO) if (prefabGO)
{ {
#if UNITY_2018_3_OR_NEWER #if UNITY_2018_3_OR_NEWER
GameObject prefabRootGO = prefabGO.transform.root.gameObject; GameObject prefabRootGO = prefabGO.transform.root.gameObject;
#else #else
GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO); GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO);
#endif #endif
if (prefabRootGO) if (prefabRootGO)
{ {
if (prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1) if (prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1)
@ -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.");
}
}
} }
} }
} }

View File

@ -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));
} }
} }
} }

View File

@ -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;

View File

@ -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");
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5c4d04450e91c438385de7300abef1b6 guid: 29e4a45f69822462ab0b15adda962a29
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View 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");
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9589e903d4e98490fb1157762a307fd7 guid: 4f3445268e45d437fac325837aff3246
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -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;
}
}
}

View 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;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 93397916cae0248bc9294f863fa49f81 guid: 78f71efc83cde4917b7d21efa90bcc9a
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -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

View File

@ -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
{ {

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f95b8a8cb007dd64980ee3c5d4258f95 guid: e192f90e0acbb41f88dfe3dba300a5c9
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View 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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69f796b44735c414783d66f47b150c5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9826d673f42ad4c59b8fc27ae86729e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -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:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ccdea83d270df4b078963a0243e41ac0
timeCreated: 1426587410
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8d7b75cc57dee4425af050c10508257a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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]'

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 611330311f6094567ad2f8012b470129
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f24d39c9182b4098bcfd0c2546d0bf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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;
} }

View File

@ -1,8 +1,9 @@
using UnityEngine; using UnityEngine;
using Mirror;
public class Projectile : NetworkBehaviour namespace Mirror.Examples.Tanks
{ {
public class Projectile : NetworkBehaviour
{
public float destroyAfter = 5; public float destroyAfter = 5;
public Rigidbody rigidBody; public Rigidbody rigidBody;
public float force = 1000; public float force = 1000;
@ -33,4 +34,5 @@ void OnTriggerEnter(Collider co)
{ {
NetworkServer.Destroy(gameObject); NetworkServer.Destroy(gameObject);
} }
}
} }

View File

@ -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
{ {

View File

@ -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;
}
} }
} }
} }

View File

@ -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);
}
}
}

View File

@ -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)
{ {

View File

@ -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);

View File

@ -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) {}

View File

@ -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();
} }
} }
} }

View File

@ -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);
@ -143,7 +132,7 @@ public virtual bool Send<T>(T msg, int channelId = Channels.DefaultReliable) whe
// internal because no one except Mirror should send bytes directly to // internal because no one except Mirror should send bytes directly to
// the client. they would be detected as a message. send messages instead. // the client. they would be detected as a message. send messages instead.
internal virtual bool SendBytes( byte[] bytes, int channelId = Channels.DefaultReliable) internal virtual bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
{ {
if (logNetworkMessages) Debug.Log("ConnectionSend con:" + connectionId + " bytes:" + BitConverter.ToString(bytes)); if (logNetworkMessages) Debug.Log("ConnectionSend con:" + connectionId + " bytes:" + BitConverter.ToString(bytes));
@ -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);

View File

@ -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);
} }

View File

@ -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() {}

View File

@ -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"))
{ {

View File

@ -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);
} }

View File

@ -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());

View File

@ -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)

View File

@ -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;
} }
} }

View File

@ -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);
} }

View 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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b346c49cfdb668488a364c3023590e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;

View 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.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0ba11103b95fd4721bffbb08440d5b8e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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'

View 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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecf93fcf0386fee4e85f981d5ca9259d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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"));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cadf48c3662efac4181b91f5c9c88774
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,12 @@
using UnityEngine;
using Mirror;
namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
class MyByteClass : SyncList<byte> {};
MyByteClass Foo;
}
}

View File

@ -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;
} }

View File

@ -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)

View File

@ -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;
} }

View File

@ -15,7 +15,7 @@ struct MyStruct
public IPotato potato; public IPotato potato;
} }
class MyStructClass : SyncListSTRUCT<MyStruct> {}; class MyStructClass : SyncList<MyStruct> {};
MyStructClass harpseals; MyStructClass harpseals;
} }

View File

@ -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;
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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]

View File

@ -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]

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -5,3 +5,4 @@ EditorBuildSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 2 serializedVersion: 2
m_Scenes: [] m_Scenes: []
m_configObjects: {}

View File

@ -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

View File

@ -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.

View File

@ -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.

View 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);
}
}
```

View File

@ -0,0 +1,3 @@
# SyncHashSet
Need description and code samples for SyncHashSet.

View File

@ -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
SyncListString myStringList;
SyncListItem myItemList;
// this will add the delegates on both server and client.
// Use OnStartClient instead if you just want the client to act upon updates
void Start()
{
myStringList.Callback += MyStringCallback;
myItemList.Callback += MyItemCallback;
}
void MyStringCallback(SyncListString.Operation op, int index, String item)
{
// Callback functionality here
}
void MyItemCallback(SyncListItem.Operation op, int index, Item item)
{
// Callback functionality here
}
public struct Item public struct Item
{ {
// Define struct public string name;
public int amount;
public Color32 color;
} }
public class SyncListItem : SyncListSTRUCT<Item> { } 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.
// Use OnStartClient instead if you just want the client to act upon updates
void Start()
{
myStringList.Callback += OnInventoryUpdated;
}
void OnInventoryUpdated(SyncListItem.Operation op, int index, Item item)
{
switch (op)
{
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;
}
}
}
``` ```

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}
```

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

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