Merge pull request #5 from vis2k/master

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

View File

@ -8,6 +8,10 @@
]
}],
'@semantic-release/release-notes-generator',
'@semantic-release/github'
["@semantic-release/github", {
"assets": [
{"path": "Mirror.unitypackage", "label": "Mirror.unitypackage"}
]
}],
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,83 +0,0 @@
using System;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : Editor
{
NetworkAnimator m_AnimSync;
[NonSerialized] bool m_Initialized;
SerializedProperty m_AnimatorProperty;
GUIContent m_AnimatorLabel = new GUIContent("Animator", "The Animator component to synchronize.");
void Init()
{
if (m_Initialized)
return;
m_Initialized = true;
m_AnimSync = target as NetworkAnimator;
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
}
public override void OnInspectorGUI()
{
Init();
serializedObject.Update();
DrawControls();
serializedObject.ApplyModifiedProperties();
}
void DrawControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
if (EditorGUI.EndChangeCheck())
{
m_AnimSync.ResetParameterOptions();
}
if (m_AnimSync.animator == null)
return;
AnimatorController controller = m_AnimSync.animator.runtimeAnimatorController as AnimatorController;
if (controller != null)
{
bool showWarning = false;
EditorGUI.indentLevel += 1;
int i = 0;
foreach (AnimatorControllerParameter p in controller.parameters)
{
if (i >= 32)
{
showWarning = true;
break;
}
bool oldSend = m_AnimSync.GetParameterAutoSend(i);
bool send = EditorGUILayout.Toggle("Sync " + p.name, oldSend);
if (send != oldSend)
{
m_AnimSync.SetParameterAutoSend(i, send);
EditorUtility.SetDirty(target);
}
i += 1;
}
if (showWarning)
{
EditorGUILayout.HelpBox("NetworkAnimator can only select between the first 32 parameters in a mecanim controller", MessageType.Warning);
}
EditorGUI.indentLevel -= 1;
}
}
}
}

View File

@ -2,26 +2,24 @@
using UnityEditor;
using UnityEditor.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.");
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,19 @@
// this class generates OnSerialize/OnDeserialize for SyncLists
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mirror.Weaver
{
static class SyncDictionaryProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
}
}
}

View File

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

View File

@ -0,0 +1,18 @@
// this class generates OnSerialize/OnDeserialize for SyncLists
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mirror.Weaver
{
static class SyncListProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeItem", "DeserializeItem");
}
}
}

View File

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

View File

@ -1,158 +0,0 @@
// this class generates OnSerialize/OnDeserialize for SyncListStructs
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mirror.Weaver
{
static class SyncListStructProcessor
{
public static void Process(TypeDefinition td)
{
// find item type
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
if (gt.GenericArguments.Count == 0)
{
Weaver.Error("SyncListStructProcessor no generic args");
return;
}
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[0]);
Weaver.DLog(td, "SyncListStructProcessor Start item:" + itemType.FullName);
Weaver.ResetRecursionCount();
MethodReference writeItemFunc = GenerateSerialization(td, itemType);
if (Weaver.WeavingFailed)
{
return;
}
MethodReference readItemFunc = GenerateDeserialization(td, itemType);
if (readItemFunc == null || writeItemFunc == null)
return;
Weaver.DLog(td, "SyncListStructProcessor Done");
}
// serialization of individual element
static MethodReference GenerateSerialization(TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (var m in td.Methods)
{
if (m.Name == "SerializeItem")
return m;
}
MethodDefinition serializeFunc = new MethodDefinition("SerializeItem", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
if (itemType.IsGenericInstance)
{
Weaver.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Struct passed into SyncListStruct<T> can't have generic parameters");
return null;
}
foreach (FieldDefinition field in itemType.Resolve().Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field);
TypeDefinition ft = importedField.FieldType.Resolve();
if (ft.HasGenericParameters)
{
Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot have generic parameters.");
return null;
}
if (ft.IsInterface)
{
Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot be an interface.");
return null;
}
MethodReference writeFunc = Weaver.GetWriteFunc(field.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, importedField));
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member variables must be basic types.");
return null;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
return serializeFunc;
}
static MethodReference GenerateDeserialization(TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (var m in td.Methods)
{
if (m.Name == "DeserializeItem")
return m;
}
MethodDefinition serializeFunc = new MethodDefinition("DeserializeItem", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
itemType);
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
serWorker.Body.InitLocals = true;
serWorker.Body.Variables.Add(new VariableDefinition(itemType));
// init item instance
serWorker.Append(serWorker.Create(OpCodes.Ldloca, 0));
serWorker.Append(serWorker.Create(OpCodes.Initobj, itemType));
foreach (FieldDefinition field in itemType.Resolve().Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field);
TypeDefinition ft = importedField.FieldType.Resolve();
MethodReference readerFunc = Weaver.GetReadFunc(field.FieldType);
if (readerFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldloca, 0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
serWorker.Append(serWorker.Create(OpCodes.Stfld, importedField));
}
else
{
Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + ft + "]. [SyncListStruct] member variables must be basic types.");
return null;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
return serializeFunc;
}
}
}

View File

@ -0,0 +1,124 @@
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectProcessor
{
/// <summary>
/// Generates the serialization and deserialization methods for a specified generic argument
/// </summary>
/// <param name="td">The type of the class that needs serialization methods</param>
/// <param name="genericArgument">Which generic argument to serialize, 0 is the first one</param>
/// <param name="serializeMethod">The name of the serialize method</param>
/// <param name="deserializeMethod">The name of the deserialize method</param>
public static void GenerateSerialization(TypeDefinition td, int genericArgument, string serializeMethod, string deserializeMethod)
{
// find item type
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
if (gt.GenericArguments.Count <= genericArgument)
{
Weaver.Error("SyncObjectProcessor no generic args");
return;
}
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[genericArgument]);
Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);
Weaver.ResetRecursionCount();
MethodReference writeItemFunc = GenerateSerialization(serializeMethod, td, itemType);
if (Weaver.WeavingFailed)
{
return;
}
MethodReference readItemFunc = GenerateDeserialization(deserializeMethod, td, itemType);
if (readItemFunc == null || writeItemFunc == null)
return;
Weaver.DLog(td, "SyncObjectProcessor Done");
}
// serialization of individual element
static MethodReference GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (var m in td.Methods)
{
if (m.Name == methodName)
return m;
}
MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
if (itemType.IsGenericInstance)
{
Weaver.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Can't have generic parameters");
return null;
}
MethodReference writeFunc = Weaver.GetWriteFunc(itemType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + itemType + "/" + itemType.FullName + "]. Member variables must be basic types.");
return null;
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
return serializeFunc;
}
static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (var m in td.Methods)
{
if (m.Name == methodName)
return m;
}
MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
itemType);
deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor serWorker = deserializeFunction.Body.GetILProcessor();
MethodReference readerFunc = Weaver.GetReadFunc(itemType);
if (readerFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
serWorker.Append(serWorker.Create(OpCodes.Ret));
}
else
{
Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + itemType + "]. Member variables must be basic types.");
return null;
}
td.Methods.Add(deserializeFunction);
return deserializeFunction;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,307 @@
// add this component to the NetworkManager
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
namespace Mirror.Examples.ListServer
{
public class ServerStatus
{
public string ip;
//public ushort port; // <- not all transports use a port. assume default port. feel free to also send a port if needed.
public string title;
public ushort players;
public ushort capacity;
public int lastLatency = -1;
public Ping ping;
public ServerStatus(string ip, /*ushort port,*/ string title, ushort players, ushort capacity)
{
this.ip = ip;
//this.port = port;
this.title = title;
this.players = players;
this.capacity = capacity;
ping = new Ping(ip);
}
}
[RequireComponent(typeof(NetworkManager))]
public class ListServer : MonoBehaviour
{
[Header("Listen Server Connection")]
public string listServerIp = "127.0.0.1";
public ushort gameServerToListenPort = 8887;
public ushort clientToListenPort = 8888;
public string gameServerTitle = "Deathmatch";
Telepathy.Client gameServerToListenConnection = new Telepathy.Client();
Telepathy.Client clientToListenConnection = new Telepathy.Client();
[Header("UI")]
public GameObject mainPanel;
public Transform content;
public UIServerStatusSlot slotPrefab;
public Button serverAndPlayButton;
public Button serverOnlyButton;
public GameObject connectingPanel;
public Text connectingText;
public Button connectingCancelButton;
int connectingDots = 0;
// all the servers, stored as dict with unique ip key so we can
// update them more easily
// (use "ip:port" if port is needed)
Dictionary<string, ServerStatus> list = new Dictionary<string, ServerStatus>();
void Start()
{
// examples
//list["127.0.0.1"] = new ServerStatus("127.0.0.1", "Deathmatch", 3, 10);
//list["192.168.0.1"] = new ServerStatus("192.168.0.1", "Free for all", 7, 10);
//list["172.217.22.3"] = new ServerStatus("172.217.22.3", "5vs5", 10, 10);
//list["172.217.16.142"] = new ServerStatus("172.217.16.142", "Hide & Seek Mod", 0, 10);
// Update once a second. no need to try to reconnect or read data
// in each Update call
// -> calling it more than 1/second would also cause significantly
// more broadcasts in the list server.
InvokeRepeating(nameof(Tick), 0, 1);
}
bool IsConnecting() => NetworkClient.active && !ClientScene.ready;
bool FullyConnected() => NetworkClient.active && ClientScene.ready;
// should we use the client to listen connection?
bool UseClientToListen()
{
return !NetworkManager.IsHeadless() && !NetworkServer.active && !FullyConnected();
}
// should we use the game server to listen connection?
bool UseGameServerToListen()
{
return NetworkServer.active;
}
void Tick()
{
TickGameServer();
TickClient();
}
// send server status to list server
void SendStatus()
{
BinaryWriter writer = new BinaryWriter(new MemoryStream());
// create message
// NOTE: you can send anything that you want, as long as you also
// receive it in ParseMessage
char[] titleChars = gameServerTitle.ToCharArray();
writer.Write((ushort)titleChars.Length);
writer.Write(titleChars);
writer.Write((ushort)NetworkServer.connections.Count);
writer.Write((ushort)NetworkManager.singleton.maxConnections);
// send it
writer.Flush();
gameServerToListenConnection.Send(((MemoryStream)writer.BaseStream).ToArray());
}
void TickGameServer()
{
// send server data to listen
if (UseGameServerToListen())
{
// connected yet?
if (gameServerToListenConnection.Connected)
{
SendStatus();
}
// otherwise try to connect
// (we may have just started the game)
else if (!gameServerToListenConnection.Connecting)
{
Debug.Log("Establishing game server to listen connection...");
gameServerToListenConnection.Connect(listServerIp, gameServerToListenPort);
}
}
// shouldn't use game server, but still using it?
else if (gameServerToListenConnection.Connected)
{
gameServerToListenConnection.Disconnect();
}
}
void ParseMessage(byte[] bytes)
{
// use binary reader because our NetworkReader uses custom string reading with bools
// => we don't use ReadString here because the listen server doesn't
// know C#'s '7-bit-length + utf8' encoding for strings
BinaryReader reader = new BinaryReader(new MemoryStream(bytes, false), Encoding.UTF8);
ushort ipLength = reader.ReadUInt16();
string ip = new string(reader.ReadChars(ipLength));
//ushort port = reader.ReadUInt16(); <- not all Transports use a port. assume default.
ushort titleLength = reader.ReadUInt16();
string title = new string(reader.ReadChars(titleLength));
ushort players = reader.ReadUInt16();
ushort capacity = reader.ReadUInt16();
//Debug.Log("PARSED: ip=" + ip + /*" port=" + port +*/ " title=" + title + " players=" + players + " capacity= " + capacity);
// build key
string key = ip/* + ":" + port*/;
// find existing or create new one
ServerStatus server;
if (list.TryGetValue(key, out server))
{
// refresh
server.title = title;
server.players = players;
server.capacity = capacity;
}
else
{
// create
server = new ServerStatus(ip, /*port,*/ title, players, capacity);
}
// save
list[key] = server;
}
void TickClient()
{
// receive client data from listen
if (UseClientToListen())
{
// connected yet?
if (clientToListenConnection.Connected)
{
// receive latest game server info
while (clientToListenConnection.GetNextMessage(out Telepathy.Message message))
{
// data message?
if (message.eventType == Telepathy.EventType.Data)
ParseMessage(message.data);
}
// ping again if previous ping finished
foreach (ServerStatus server in list.Values)
{
if (server.ping.isDone)
{
server.lastLatency = server.ping.time;
server.ping = new Ping(server.ip);
}
}
}
// otherwise try to connect
// (we may have just joined the menu/disconnect from game server)
else if (!clientToListenConnection.Connecting)
{
Debug.Log("Establishing client to listen connection...");
clientToListenConnection.Connect(listServerIp, clientToListenPort);
}
}
// shouldn't use client, but still using it? (e.g. after joining)
else if (clientToListenConnection.Connected)
{
clientToListenConnection.Disconnect();
list.Clear();
}
// refresh UI afterwards
OnUI();
}
// instantiate/remove enough prefabs to match amount
public static void BalancePrefabs(GameObject prefab, int amount, Transform parent)
{
// instantiate until amount
for (int i = parent.childCount; i < amount; ++i)
{
GameObject go = GameObject.Instantiate(prefab);
go.transform.SetParent(parent, false);
}
// delete everything that's too much
// (backwards loop because Destroy changes childCount)
for (int i = parent.childCount-1; i >= amount; --i)
GameObject.Destroy(parent.GetChild(i).gameObject);
}
void OnUI()
{
// only show while client not connected and server not started
if (!NetworkManager.singleton.isNetworkActive || IsConnecting())
{
mainPanel.SetActive(true);
// instantiate/destroy enough slots
BalancePrefabs(slotPrefab.gameObject, list.Count, content);
// refresh all members
for (int i = 0; i < list.Values.Count; ++i)
{
UIServerStatusSlot slot = content.GetChild(i).GetComponent<UIServerStatusSlot>();
ServerStatus server = list.Values.ToList()[i];
slot.titleText.text = server.title;
slot.playersText.text = server.players + "/" + server.capacity;
slot.latencyText.text = server.lastLatency != -1 ? server.lastLatency.ToString() : "...";
slot.addressText.text = server.ip;
slot.joinButton.interactable = !IsConnecting();
slot.joinButton.gameObject.SetActive(server.players < server.capacity);
slot.joinButton.onClick.RemoveAllListeners();
slot.joinButton.onClick.AddListener(() => {
NetworkManager.singleton.networkAddress = server.ip;
NetworkManager.singleton.StartClient();
});
}
// server buttons
serverAndPlayButton.interactable = !IsConnecting();
serverAndPlayButton.onClick.RemoveAllListeners();
serverAndPlayButton.onClick.AddListener(() => {
NetworkManager.singleton.StartHost();
});
serverOnlyButton.interactable = !IsConnecting();
serverOnlyButton.onClick.RemoveAllListeners();
serverOnlyButton.onClick.AddListener(() => {
NetworkManager.singleton.StartServer();
});
}
else mainPanel.SetActive(false);
// show connecting panel while connecting
if (IsConnecting())
{
connectingPanel.SetActive(true);
// . => .. => ... => ....
connectingDots = ((connectingDots + 1) % 4);
connectingText.text = "Connecting" + new string('.', connectingDots);
// cancel button
connectingCancelButton.onClick.RemoveAllListeners();
connectingCancelButton.onClick.AddListener(NetworkManager.singleton.StopClient);
}
else connectingPanel.SetActive(false);
}
// disconnect everything when pressing Stop in the Editor
void OnApplicationQuit()
{
if (gameServerToListenConnection.Connected)
gameServerToListenConnection.Disconnect();
if (clientToListenConnection.Connected)
clientToListenConnection.Disconnect();
}
}
}

View File

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

View File

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

Binary file not shown.

View File

@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 83612f89e0d5b404fbd99891bda78df4
guid: b7816657e55c04901be36ace7275ad85
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
mainObjectFileID: 23800000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,651 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1009505356919436
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224428085307354514}
- component: {fileID: 222534217462828066}
- component: {fileID: 114077992825021022}
m_Layer: 5
m_Name: TextPlayers
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224428085307354514
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1009505356919436}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 224813910264502154}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 60, y: 16}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222534217462828066
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1009505356919436}
m_CullTransparentMesh: 0
--- !u!114 &114077992825021022
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1009505356919436}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: '[Players]'
--- !u!1 &1165846264137056
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224595697833923954}
- component: {fileID: 222565095748274674}
- component: {fileID: 114410999938197916}
m_Layer: 5
m_Name: TextLatency
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224595697833923954
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1165846264137056}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 224813910264502154}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 60, y: 16}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222565095748274674
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1165846264137056}
m_CullTransparentMesh: 0
--- !u!114 &114410999938197916
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1165846264137056}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: '[Latency]'
--- !u!1 &1259177336467004
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224813910264502154}
- component: {fileID: 222443701544564472}
- component: {fileID: 114424214131292870}
- component: {fileID: 114617214395066132}
- component: {fileID: 114618400118094574}
- component: {fileID: 114611570997501482}
m_Layer: 5
m_Name: ServerStatusSlot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224813910264502154
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 224596263305646570}
- {fileID: 224428085307354514}
- {fileID: 224595697833923954}
- {fileID: 2588874080615256095}
- {fileID: 224530003443674914}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222443701544564472
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_CullTransparentMesh: 0
--- !u!114 &114424214131292870
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.2509804, g: 0.2509804, b: 0.2509804, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
--- !u!114 &114617214395066132
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1741964061, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!114 &114618400118094574
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -405508275, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 4
m_Right: 2
m_Top: 1
m_Bottom: 1
m_ChildAlignment: 0
m_Spacing: 0
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 0
m_ChildControlHeight: 0
--- !u!114 &114611570997501482
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1259177336467004}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7f24d39c9182b4098bcfd0c2546d0bf2, type: 3}
m_Name:
m_EditorClassIdentifier:
titleText: {fileID: 114105517410707240}
playersText: {fileID: 114077992825021022}
latencyText: {fileID: 114410999938197916}
addressText: {fileID: 1711666012325280996}
joinButton: {fileID: 114358377111651776}
--- !u!1 &1368168976437814
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224596263305646570}
- component: {fileID: 222703717971346548}
- component: {fileID: 114105517410707240}
m_Layer: 5
m_Name: TextTitle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224596263305646570
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1368168976437814}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 224813910264502154}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 218, y: 16}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222703717971346548
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1368168976437814}
m_CullTransparentMesh: 0
--- !u!114 &114105517410707240
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1368168976437814}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: '[Title]'
--- !u!1 &1462871638010074
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224530003443674914}
- component: {fileID: 222322555260831376}
- component: {fileID: 114653458098104780}
- component: {fileID: 114358377111651776}
m_Layer: 5
m_Name: ButtonJoin
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224530003443674914
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1462871638010074}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 224615151935076648}
m_Father: {fileID: 224813910264502154}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 40, y: 18}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222322555260831376
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1462871638010074}
m_CullTransparentMesh: 0
--- !u!114 &114653458098104780
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1462871638010074}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
--- !u!114 &114358377111651776
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1462871638010074}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 0.32156864, g: 0.32156864, b: 0.32156864, a: 1}
m_HighlightedColor: {r: 0.3529412, g: 0.3529412, b: 0.3529412, a: 1}
m_PressedColor: {r: 0.3882353, g: 0.3882353, b: 0.3882353, a: 1}
m_DisabledColor: {r: 0.3882353, g: 0.3882353, b: 0.3882353, a: 0.37254903}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 114653458098104780}
m_OnClick:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null
--- !u!1 &1575165076438694
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224615151935076648}
- component: {fileID: 222903696298421472}
- component: {fileID: 114447744505293664}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224615151935076648
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1575165076438694}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 224530003443674914}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &222903696298421472
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1575165076438694}
m_CullTransparentMesh: 0
--- !u!114 &114447744505293664
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1575165076438694}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Join
--- !u!1 &2231260898927249423
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2588874080615256095}
- component: {fileID: 2389080155505640677}
- component: {fileID: 1711666012325280996}
m_Layer: 5
m_Name: TextAddress
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2588874080615256095
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2231260898927249423}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 224813910264502154}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 130, y: 16}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2389080155505640677
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2231260898927249423}
m_CullTransparentMesh: 0
--- !u!114 &1711666012325280996
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2231260898927249423}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: '[Address]'

View File

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

View File

@ -0,0 +1,13 @@
// Attach to the prefab for easier component access by the UI Scripts.
// Otherwise we would need slot.GetChild(0).GetComponentInChildren<Text> etc.
using UnityEngine;
using UnityEngine.UI;
public class UIServerStatusSlot : MonoBehaviour
{
public Text titleText;
public Text playersText;
public Text latencyText;
public Text addressText;
public Button joinButton;
}

View File

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

View File

@ -51,8 +51,8 @@ LightmapSettings:
m_IndirectOutputScale: 1
m_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:

View File

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

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: ba6c3cbc7dcdf184a99a7d73e8762759
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 7
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 1
seamlessCubemap: 1
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 2
aniso: 0
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -38,21 +38,21 @@ RenderSettings:
m_ReflectionIntensity: 1
m_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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
using UnityEngine;
using UnityEngine.AI;
namespace Mirror.Examples.Movement
namespace Mirror.Examples.Tanks
{
public class Tank : NetworkBehaviour
{

View File

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

View File

@ -1,63 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
sealed class LocalClient : NetworkClient
{
// local client in host mode might call Cmds/Rpcs during Update, but we
// want to apply them in LateUpdate like all other Transport messages
// to avoid race conditions. keep packets in Queue until LateUpdate.
internal Queue<byte[]> packetQueue = new Queue<byte[]>();
internal void InternalConnectLocalServer()
{
connection = new ULocalConnectionToServer();
SetHandlers(connection);
connection.connectionId = NetworkServer.AddLocalClient(this);
connectState = ConnectState.Connected;
active = true;
RegisterSystemHandlers(true);
packetQueue.Enqueue(MessagePacker.Pack(new ConnectMessage()));
}
public override void Disconnect()
{
connectState = ConnectState.Disconnected;
ClientScene.HandleClientDisconnect(connection);
if (isConnected)
{
packetQueue.Enqueue(MessagePacker.Pack(new DisconnectMessage()));
}
NetworkServer.RemoveLocalClient();
}
internal override void Update()
{
// process internal messages so they are applied at the correct time
while (packetQueue.Count > 0)
{
byte[] packet = packetQueue.Dequeue();
OnDataReceived(packet);
}
}
// Called by the server to set the LocalClient's LocalPlayer object during NetworkServer.AddPlayer()
internal void AddLocalPlayer(NetworkIdentity localPlayer)
{
if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + connection.connectionId);
connection.isReady = true;
connection.SetPlayerController(localPlayer);
if (localPlayer != null)
{
localPlayer.isClient = true;
NetworkIdentity.spawned[localPlayer.netId] = localPlayer;
localPlayer.connectionToServer = connection;
}
// there is no SystemOwnerMessage for local client. add to ClientScene here instead
ClientScene.InternalAddPlayer(localPlayer);
}
}
}

View File

@ -6,16 +6,15 @@ namespace Mirror
// sending messages on this connection causes the client's handler function to be invoked directly
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)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,303 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
namespace Mirror
{
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class SyncDictionary<K, V> : IDictionary<K, V>, SyncObject
{
public delegate void SyncDictionaryChanged(Operation op, K key, V item);
readonly Dictionary<K, V> m_Objects;
public int Count => m_Objects.Count;
public bool IsReadOnly { get; private set; }
public event SyncDictionaryChanged Callback;
public enum Operation : byte
{
OP_ADD,
OP_CLEAR,
OP_REMOVE,
OP_SET,
OP_DIRTY
}
struct Change
{
internal Operation operation;
internal K key;
internal V item;
}
readonly List<Change> Changes = new List<Change>();
// how many changes we need to ignore
// this is needed because when we initialize the list,
// we might later receive changes that have already been applied
// so we need to skip them
int changesAhead = 0;
protected virtual void SerializeKey(NetworkWriter writer, K item) {}
protected virtual void SerializeItem(NetworkWriter writer, V item) {}
protected virtual K DeserializeKey(NetworkReader reader) => default;
protected virtual V DeserializeItem(NetworkReader reader) => default;
public bool IsDirty => Changes.Count > 0;
public ICollection<K> Keys => m_Objects.Keys;
public ICollection<V> Values => m_Objects.Values;
// throw away all the changes
// this should be called after a successfull sync
public void Flush() => Changes.Clear();
public SyncDictionary()
{
m_Objects = new Dictionary<K, V>();
}
public SyncDictionary(IEqualityComparer<K> eq)
{
m_Objects = new Dictionary<K, V>(eq);
}
void AddOperation(Operation op, K key, V item)
{
if (IsReadOnly)
{
throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server");
}
Change change = new Change
{
operation = op,
key = key,
item = item
};
Changes.Add(change);
Callback?.Invoke(op, key, item);
}
public void OnSerializeAll(NetworkWriter writer)
{
// if init, write the full list content
writer.WritePackedUInt32((uint)m_Objects.Count);
foreach (KeyValuePair<K, V> syncItem in m_Objects)
{
SerializeKey(writer, syncItem.Key);
SerializeItem(writer, syncItem.Value);
}
// all changes have been applied already
// thus the client will need to skip all the pending changes
// or they would be applied again.
// So we write how many changes are pending
writer.WritePackedUInt32((uint)Changes.Count);
}
public void OnSerializeDelta(NetworkWriter writer)
{
// write all the queued up changes
writer.WritePackedUInt32((uint)Changes.Count);
for (int i = 0; i < Changes.Count; i++)
{
Change change = Changes[i];
writer.Write((byte)change.operation);
switch (change.operation)
{
case Operation.OP_ADD:
case Operation.OP_REMOVE:
case Operation.OP_SET:
case Operation.OP_DIRTY:
SerializeKey(writer, change.key);
SerializeItem(writer, change.item);
break;
case Operation.OP_CLEAR:
break;
}
}
}
public void OnDeserializeAll(NetworkReader reader)
{
// This list can now only be modified by synchronization
IsReadOnly = true;
// if init, write the full list content
int count = (int)reader.ReadPackedUInt32();
m_Objects.Clear();
Changes.Clear();
for (int i = 0; i < count; i++)
{
K key = DeserializeKey(reader);
V obj = DeserializeItem(reader);
m_Objects.Add(key, obj);
}
// We will need to skip all these changes
// the next time the list is synchronized
// because they have already been applied
changesAhead = (int)reader.ReadPackedUInt32();
}
public void OnDeserializeDelta(NetworkReader reader)
{
// This list can now only be modified by synchronization
IsReadOnly = true;
int changesCount = (int)reader.ReadPackedUInt32();
for (int i = 0; i < changesCount; i++)
{
Operation operation = (Operation)reader.ReadByte();
// apply the operation only if it is a new change
// that we have not applied yet
bool apply = changesAhead == 0;
K key = default;
V item = default;
switch (operation)
{
case Operation.OP_ADD:
case Operation.OP_SET:
case Operation.OP_DIRTY:
key = DeserializeKey(reader);
item = DeserializeItem(reader);
if (apply)
{
m_Objects[key] = item;
}
break;
case Operation.OP_CLEAR:
if (apply)
{
m_Objects.Clear();
}
break;
case Operation.OP_REMOVE:
key = DeserializeKey(reader);
item = DeserializeItem(reader);
if (apply)
{
m_Objects.Remove(key);
}
break;
}
if (apply)
{
Callback?.Invoke(operation, key, item);
}
// we just skipped this change
else
{
changesAhead--;
}
}
}
public void Clear()
{
m_Objects.Clear();
AddOperation(Operation.OP_CLEAR, default, default);
}
public bool ContainsKey(K key) => m_Objects.ContainsKey(key);
public bool Remove(K key)
{
if (m_Objects.TryGetValue(key, out V item) && m_Objects.Remove(key))
{
AddOperation(Operation.OP_REMOVE, key, item);
return true;
}
return false;
}
public void Dirty(K index)
{
AddOperation(Operation.OP_DIRTY, index, m_Objects[index]);
}
public V this[K i]
{
get => m_Objects[i];
set
{
if (TryGetValue(i, out V val))
{
AddOperation(Operation.OP_SET, i, value);
}
else
{
AddOperation(Operation.OP_ADD, i, value);
}
m_Objects[i] = value;
}
}
public bool TryGetValue(K key, out V value) => m_Objects.TryGetValue(key, out value);
public void Add(K key, V value)
{
m_Objects.Add(key, value);
AddOperation(Operation.OP_ADD, key, value);
}
public void Add(KeyValuePair<K, V> item) => Add(item.Key, item.Value);
public bool Contains(KeyValuePair<K, V> item)
{
return TryGetValue(item.Key, out V val) && EqualityComparer<V>.Default.Equals(val, item.Value);
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
if (array == null)
{
throw new System.ArgumentNullException("Array Is Null");
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new System.ArgumentOutOfRangeException("Array Index Out of Range");
}
if (array.Length - arrayIndex < Count)
{
throw new System.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");
}
int i = arrayIndex;
foreach (KeyValuePair<K,V> item in m_Objects)
{
array[i] = item;
i++;
}
}
public bool Remove(KeyValuePair<K, V> item)
{
bool result = m_Objects.Remove(item.Key);
if (result)
{
AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
}
return result;
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
}
}

View File

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

View File

@ -43,6 +43,7 @@ public class SyncListBool : SyncList<bool>
// in Unity 2019.1.
//
// 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;

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018, vis2k
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

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

View File

@ -10,6 +10,9 @@ public class TelepathyTransport : Transport
[Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
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'

View File

@ -0,0 +1,51 @@
using NUnit.Framework;
namespace Mirror
{
struct TestMessage : IMessageBase
{
public int IntValue;
public string StringValue;
public double DoubleValue;
public TestMessage(int i, string s, double d)
{
IntValue = i;
StringValue = s;
DoubleValue = d;
}
public void Deserialize(NetworkReader reader)
{
IntValue = reader.ReadInt32();
StringValue = reader.ReadString();
DoubleValue = reader.ReadDouble();
}
public void Serialize(NetworkWriter writer)
{
writer.Write(IntValue);
writer.Write(StringValue);
writer.Write(DoubleValue);
}
}
[TestFixture]
public class MessageBaseTests
{
[Test]
public void Roundtrip()
{
NetworkWriter w = new NetworkWriter();
w.Write(new TestMessage(1, "2", 3.3));
byte[] arr = w.ToArray();
NetworkReader r = new NetworkReader(arr);
TestMessage t = new TestMessage();
t.Deserialize(r);
Assert.AreEqual(1, t.IntValue);
}
}
}

View File

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

View File

@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Mirror.Tests
{
[TestFixture]
public class SyncDictionaryTest
{
public class SyncDictionaryIntString : SyncDictionary<int, string> {}
SyncDictionaryIntString serverSyncDictionary;
SyncDictionaryIntString clientSyncDictionary;
private void SerializeAllTo<T>(T fromList, T toList) where T : SyncObject
{
NetworkWriter writer = new NetworkWriter();
fromList.OnSerializeAll(writer);
NetworkReader reader = new NetworkReader(writer.ToArray());
toList.OnDeserializeAll(reader);
}
private void SerializeDeltaTo<T>(T fromList, T toList) where T : SyncObject
{
NetworkWriter writer = new NetworkWriter();
fromList.OnSerializeDelta(writer);
NetworkReader reader = new NetworkReader(writer.ToArray());
toList.OnDeserializeDelta(reader);
fromList.Flush();
}
[SetUp]
public void SetUp()
{
serverSyncDictionary = new SyncDictionaryIntString();
clientSyncDictionary = new SyncDictionaryIntString();
// add some data to the list
serverSyncDictionary.Add(0, "Hello");
serverSyncDictionary.Add(1, "World");
serverSyncDictionary.Add(2, "!");
SerializeAllTo(serverSyncDictionary, clientSyncDictionary);
}
[Test]
public void TestInit()
{
Dictionary<int, string> comparer = new Dictionary<int, string>
{
[0] = "Hello",
[1] = "World",
[2] = "!"
};
Assert.That(clientSyncDictionary[0], Is.EqualTo("Hello"));
Assert.That(clientSyncDictionary, Is.EquivalentTo(comparer));
}
[Test]
public void TestAdd()
{
serverSyncDictionary.Add(4, "yay");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(4), Is.EqualTo(true));
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
}
[Test]
public void TestClear()
{
serverSyncDictionary.Clear();
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(serverSyncDictionary, Is.EquivalentTo(new SyncDictionaryIntString()));
}
[Test]
public void TestSet()
{
serverSyncDictionary[1] = "yay";
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(1));
Assert.That(clientSyncDictionary[1], Is.EqualTo("yay"));
}
[Test]
public void TestBareSet()
{
serverSyncDictionary[4] = "yay";
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(4));
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
}
[Test]
public void TestBareSetNull()
{
serverSyncDictionary[4] = null;
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary[4], Is.Null);
Assert.That(clientSyncDictionary.ContainsKey(4));
}
[Test]
public void TestConsecutiveSet()
{
serverSyncDictionary[1] = "yay";
serverSyncDictionary[1] = "world";
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary[1], Is.EqualTo("world"));
}
[Test]
public void TestNullSet()
{
serverSyncDictionary[1] = null;
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(1));
Assert.That(clientSyncDictionary[1], Is.Null);
}
[Test]
public void TestRemove()
{
serverSyncDictionary.Remove(1);
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(1), Is.False);
}
[Test]
public void TestMultSync()
{
serverSyncDictionary.Add(10, "1");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
// add some delta and see if it applies
serverSyncDictionary.Add(11, "2");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.ContainsKey(10));
Assert.That(clientSyncDictionary[10], Is.EqualTo("1"));
Assert.That(clientSyncDictionary.ContainsKey(11));
Assert.That(clientSyncDictionary[11], Is.EqualTo("2"));
}
[Test]
public void TestContains()
{
Assert.That(!clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
serverSyncDictionary[2] = "Hello";
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
}
[Test]
public void CallbackTest()
{
bool called = false;
clientSyncDictionary.Callback += (op, index, item) =>
{
called = true;
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_ADD));
Assert.That(index, Is.EqualTo(3));
Assert.That(item, Is.EqualTo("yay"));
};
serverSyncDictionary.Add(3, "yay");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(called, Is.True);
}
[Test]
public void CallbackRemoveTest()
{
bool called = false;
clientSyncDictionary.Callback += (op, key, item) =>
{
called = true;
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_REMOVE));
Assert.That(item, Is.EqualTo("World"));
};
serverSyncDictionary.Remove(1);
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(called, Is.True);
}
[Test]
public void CountTest()
{
Assert.That(serverSyncDictionary.Count, Is.EqualTo(3));
}
[Test]
public void ReadOnlyTest()
{
Assert.That(serverSyncDictionary.IsReadOnly, Is.False);
}
[Test]
public void DirtyTest()
{
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
// nothing to send
Assert.That(serverList.IsDirty, Is.False);
// something has changed
serverList.Add(15, "yay");
Assert.That(serverList.IsDirty, Is.True);
SerializeDeltaTo(serverList, clientList);
// data has been flushed, should go back to clear
Assert.That(serverList.IsDirty, Is.False);
}
[Test]
public void ReadonlyTest()
{
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
// data has been flushed, should go back to clear
Assert.That(clientList.IsReadOnly, Is.False);
serverList.Add(20, "yay");
serverList.Add(30, "hello");
serverList.Add(35, "world");
SerializeDeltaTo(serverList, clientList);
// client list should now lock itself, trying to modify it
// should produce an InvalidOperationException
Assert.That(clientList.IsReadOnly, Is.True);
Assert.Throws<InvalidOperationException>(() => clientList.Add(50, "fail"));
}
}
}

View File

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

View File

@ -263,6 +263,13 @@ public void SyncListMissingParamlessCtor()
Assert.That(m_weaverErrors.Count, Is.EqualTo(1));
Assert.That(m_weaverErrors[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

View File

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

View File

@ -20,7 +20,7 @@ struct MyStruct
MyGenericStruct<MyPODStruct> potato;
}
class MyStructClass : SyncListSTRUCT<MyGenericStruct<float>> {};
class MyStructClass : SyncList<MyGenericStruct<float>> {};
MyStructClass harpseals;
}

View File

@ -16,7 +16,7 @@ struct MyStruct
public object nonbasicpotato;
}
class MyStructClass : SyncListSTRUCT<MyStruct>
class MyStructClass : SyncList<MyStruct>
{
int potatoCount;
public MyStructClass(int numberOfPotatoes)

View File

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

View File

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

View File

@ -11,7 +11,7 @@ struct MyStruct
float floatingpotato;
double givemetwopotatoes;
}
class MyStructClass : SyncListSTRUCT<MyStruct> {};
class MyStructClass : SyncList<MyStruct> {};
MyStructClass Foo;
}
}

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
class MySyncVar : NetworkBehaviour

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
class MySyncVar : ScriptableObject

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
[SyncVar]

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
class MySyncVar<T>

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
interface MySyncVar

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
[SyncVar] int var2;

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook = "OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
public void TakeDamage(int amount)

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
[SyncVar]

View File

@ -15,7 +15,7 @@ public void OnDeserializeDelta(NetworkReader reader) {}
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook = "OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
[SyncVar]

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook = "OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
public void TakeDamage(int amount)

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook="OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
[SyncVar] int var2;

View File

@ -5,7 +5,7 @@ namespace MirrorTest
{
class MirrorTestPlayer : NetworkBehaviour
{
[SyncVar(hook = "OnChangeHealth")]
[SyncVar(hook = nameof(OnChangeHealth))]
int health;
public void TakeDamage(int amount)

View File

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

View File

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

View File

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

View File

@ -14,19 +14,7 @@ NetworkServer is a High-Level-API class that manages connections from multiple c
Dictionary of the message handlers registered with the server.
- **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.

View File

@ -0,0 +1,55 @@
# SyncDictionary
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be of the following types:
- Basic type (byte, int, float, string, UInt64, etc)
- Built-in Unity math type (Vector3, Quaternion, etc)
- NetworkIdentity
- GameObject with a NetworkIdentity component attached.
- Struct with any of the above
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
## Simple Example
```cs
using UnityEngine;
using Mirror;
public class ExamplePlayer : NetworkBehaviour
{
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
public struct Item
{
public string name;
public int hitPoints;
public int durability;
}
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
public void OnStartServer()
{
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
}
private void OnStartClient()
{
// Equipment is already populated with anything the server set up
// but we can subscribe to the callback in case it is updated later on
Equipment.Callback += OnEquipmentChange;
}
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
{
// equipment changed, perhaps update the gameobject
Debug.Log(op + " - " + key);
}
}
```

View File

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

View File

@ -1,58 +1,122 @@
# SyncLists Overview
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;
}
}
}
```

View File

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

View File

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

View File

@ -71,3 +71,59 @@ public class MyScript : NetworkBehaviour
}
}
```
## SyncDictionaries
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be of the following types:
- Basic type (byte, int, float, string, UInt64, etc)
- Built-in Unity math type (Vector3, Quaternion, etc)
- NetworkIdentity
- GameObject with a NetworkIdentity component attached.
- Struct with any of the above
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
### Simple Example
```cs
using UnityEngine;
using Mirror;
public class ExamplePlayer : NetworkBehaviour
{
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
public struct Item
{
public string name;
public int hitPoints;
public int durability;
}
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
public void OnStartServer()
{
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
}
private void OnStartClient()
{
// Equipment is already populated with anything the server set up
// but we can subscribe to the callback in case it is updated later on
Equipment.Callback += OnEquipmentChange;
}
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
{
// equipment changed, perhaps update the gameobject
Debug.Log(op + " - " + key);
}
}
```

View File

@ -3,13 +3,30 @@
## Version 1.7 -- In Progress
- 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Some files were not shown because too many files have changed in this diff Show More