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