mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
feature: SyncDirection (V2) to easily support client auth components without extra Rpcs/Cmds. previously OnSerialize was only server-to-client direction. (#3244)
* feature: SyncDirection to easily support client auth components without extra Rpcs/Cmds. previously OnSerialize was only server-to-client direction. (#3232) * fix and test * add test * remove todo * comments * only serialize once * simplify dirty checks * syntax * remove doulbe comments
This commit is contained in:
parent
a1fbc55e8a
commit
e0a91eeb3a
@ -26,7 +26,7 @@ namespace Mirror
|
|||||||
{
|
{
|
||||||
public abstract class NetworkTransformBase : NetworkBehaviour
|
public abstract class NetworkTransformBase : NetworkBehaviour
|
||||||
{
|
{
|
||||||
// TODO SyncDirection { CLIENT_TO_SERVER, SERVER_TO_CLIENT } is easier?
|
// TODO SyncDirection { ClientToServer, ServerToClient } is easier?
|
||||||
[Header("Authority")]
|
[Header("Authority")]
|
||||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||||
public bool clientAuthority;
|
public bool clientAuthority;
|
||||||
|
@ -9,12 +9,25 @@ namespace Mirror
|
|||||||
// SyncMode decides if a component is synced to all observers, or only owner
|
// SyncMode decides if a component is synced to all observers, or only owner
|
||||||
public enum SyncMode { Observers, Owner }
|
public enum SyncMode { Observers, Owner }
|
||||||
|
|
||||||
|
// SyncDirection decides if a component is synced from:
|
||||||
|
// * server to all clients
|
||||||
|
// * owner client, to server, to all other clients
|
||||||
|
//
|
||||||
|
// naming: 'ClientToServer' etc. instead of 'ClientAuthority', because
|
||||||
|
// that wouldn't be accurate. server's OnDeserialize can still validate
|
||||||
|
// client data before applying. it's really about direction, not authority.
|
||||||
|
public enum SyncDirection { ServerToClient, ClientToServer }
|
||||||
|
|
||||||
/// <summary>Base class for networked components.</summary>
|
/// <summary>Base class for networked components.</summary>
|
||||||
[AddComponentMenu("")]
|
[AddComponentMenu("")]
|
||||||
[RequireComponent(typeof(NetworkIdentity))]
|
[RequireComponent(typeof(NetworkIdentity))]
|
||||||
[HelpURL("https://mirror-networking.gitbook.io/docs/guides/networkbehaviour")]
|
[HelpURL("https://mirror-networking.gitbook.io/docs/guides/networkbehaviour")]
|
||||||
public abstract class NetworkBehaviour : MonoBehaviour
|
public abstract class NetworkBehaviour : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
/// <summary>Sync direction for OnSerialize. ServerToClient by default. ClientToServer for client authority.</summary>
|
||||||
|
[Tooltip("Server Authority calls OnSerialize on the server and syncs it to clients.\n\nClient Authority calls OnSerialize on the owning client, syncs it to server, which then broadcasts it to all other clients.\n\nUse server authority for cheat safety.")]
|
||||||
|
[HideInInspector] public SyncDirection syncDirection = SyncDirection.ServerToClient;
|
||||||
|
|
||||||
/// <summary>sync mode for OnSerialize</summary>
|
/// <summary>sync mode for OnSerialize</summary>
|
||||||
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
|
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
|
||||||
[Tooltip("By default synced data is sent from the server to all Observers of the object.\nChange this to Owner to only have the server update the client that has ownership authority for this object")]
|
[Tooltip("By default synced data is sent from the server to all Observers of the object.\nChange this to Owner to only have the server update the client that has ownership authority for this object")]
|
||||||
@ -53,6 +66,24 @@ public abstract class NetworkBehaviour : MonoBehaviour
|
|||||||
[Obsolete(".hasAuthority was renamed to .isOwned. This is easier to understand and prepares for SyncDirection, where there is a difference betwen isOwned and authority.")] // 2022-10-13
|
[Obsolete(".hasAuthority was renamed to .isOwned. This is easier to understand and prepares for SyncDirection, where there is a difference betwen isOwned and authority.")] // 2022-10-13
|
||||||
public bool hasAuthority => isOwned;
|
public bool hasAuthority => isOwned;
|
||||||
|
|
||||||
|
/// <summary>authority is true if we are allowed to modify this component's state. On server, it's true if SyncDirection is ServerToClient. On client, it's true if SyncDirection is ClientToServer and(!) if this object is owned by the client.</summary>
|
||||||
|
// on the client: if owned and if clientAuthority sync direction
|
||||||
|
// on the server: if serverAuthority sync direction
|
||||||
|
//
|
||||||
|
// for example, NetworkTransform:
|
||||||
|
// client may modify position if ClientAuthority mode and owned
|
||||||
|
// server may modify position only if server authority
|
||||||
|
//
|
||||||
|
// note that in original Mirror, hasAuthority only meant 'isOwned'.
|
||||||
|
// there was no syncDirection to check.
|
||||||
|
//
|
||||||
|
// also note that this is a per-NetworkBehaviour flag.
|
||||||
|
// another component may not be client authoritative, etc.
|
||||||
|
public bool authority =>
|
||||||
|
isClient
|
||||||
|
? syncDirection == SyncDirection.ClientToServer && isOwned
|
||||||
|
: syncDirection == SyncDirection.ServerToClient;
|
||||||
|
|
||||||
/// <summary>The unique network Id of this object (unique at runtime).</summary>
|
/// <summary>The unique network Id of this object (unique at runtime).</summary>
|
||||||
public uint netId => netIdentity.netId;
|
public uint netId => netIdentity.netId;
|
||||||
|
|
||||||
@ -1118,8 +1149,13 @@ internal static int ErrorCorrection(int size, byte safety)
|
|||||||
return (int)(cleared | safety);
|
return (int)(cleared | safety);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Deserialize(NetworkReader reader, bool initialState)
|
// returns false in case of errors.
|
||||||
|
// server needs to know in order to disconnect on error.
|
||||||
|
internal bool Deserialize(NetworkReader reader, bool initialState)
|
||||||
{
|
{
|
||||||
|
// detect errors, but attempt to correct before returning
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
// read 1 byte length hash safety & capture beginning for size check
|
// read 1 byte length hash safety & capture beginning for size check
|
||||||
byte safety = reader.ReadByte();
|
byte safety = reader.ReadByte();
|
||||||
int chunkStart = reader.Position;
|
int chunkStart = reader.Position;
|
||||||
@ -1140,6 +1176,7 @@ internal void Deserialize(NetworkReader reader, bool initialState)
|
|||||||
$" * Are the server and client the exact same project?\n" +
|
$" * Are the server and client the exact same project?\n" +
|
||||||
$" * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" +
|
$" * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" +
|
||||||
$"Exception {e}");
|
$"Exception {e}");
|
||||||
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare bytes read with length hash
|
// compare bytes read with length hash
|
||||||
@ -1157,7 +1194,10 @@ internal void Deserialize(NetworkReader reader, bool initialState)
|
|||||||
// see test: SerializationSizeMismatch.
|
// see test: SerializationSizeMismatch.
|
||||||
int correctedSize = ErrorCorrection(size, safety);
|
int correctedSize = ErrorCorrection(size, safety);
|
||||||
reader.Position = chunkStart + correctedSize;
|
reader.Position = chunkStart + correctedSize;
|
||||||
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ResetSyncObjects()
|
internal void ResetSyncObjects()
|
||||||
|
@ -1031,7 +1031,7 @@ internal static void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage me
|
|||||||
{
|
{
|
||||||
using (NetworkReaderPooled payloadReader = NetworkReaderPool.Get(message.payload))
|
using (NetworkReaderPooled payloadReader = NetworkReaderPool.Get(message.payload))
|
||||||
{
|
{
|
||||||
identity.Deserialize(payloadReader, true);
|
identity.DeserializeClient(payloadReader, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1284,7 +1284,7 @@ static void OnEntityStateMessage(EntityStateMessage message)
|
|||||||
if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null)
|
if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null)
|
||||||
{
|
{
|
||||||
using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload))
|
using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload))
|
||||||
identity.Deserialize(reader, false);
|
identity.DeserializeClient(reader, false);
|
||||||
}
|
}
|
||||||
else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
|
else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
|
||||||
}
|
}
|
||||||
@ -1408,6 +1408,54 @@ static void DestroyObject(uint netId)
|
|||||||
//else Debug.LogWarning($"Did not find target for destroy message for {netId}");
|
//else Debug.LogWarning($"Did not find target for destroy message for {netId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// broadcast ///////////////////////////////////////////////////////////
|
||||||
|
// make sure Broadcast() is only called every sendInterval.
|
||||||
|
// calling it every update() would require too much bandwidth.
|
||||||
|
static void Broadcast()
|
||||||
|
{
|
||||||
|
// joined the world yet?
|
||||||
|
if (!connection.isReady) return;
|
||||||
|
|
||||||
|
// nothing to do in host mode. server already knows the state.
|
||||||
|
if (NetworkServer.active) return;
|
||||||
|
|
||||||
|
// for each entity that the client owns
|
||||||
|
foreach (NetworkIdentity identity in connection.owned)
|
||||||
|
{
|
||||||
|
// make sure it's not null or destroyed.
|
||||||
|
// (which can happen if someone uses
|
||||||
|
// GameObject.Destroy instead of
|
||||||
|
// NetworkServer.Destroy)
|
||||||
|
if (identity != null)
|
||||||
|
{
|
||||||
|
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||||
|
{
|
||||||
|
// get serialization for this entity viewed by this connection
|
||||||
|
// (if anything was serialized this time)
|
||||||
|
identity.SerializeClient(writer);
|
||||||
|
if (writer.Position > 0)
|
||||||
|
{
|
||||||
|
// send state update message
|
||||||
|
EntityStateMessage message = new EntityStateMessage
|
||||||
|
{
|
||||||
|
netId = identity.netId,
|
||||||
|
payload = writer.ToArraySegment()
|
||||||
|
};
|
||||||
|
Send(message);
|
||||||
|
|
||||||
|
// reset dirty bits so it's not resent next time.
|
||||||
|
identity.ClearDirtyComponentsDirtyBits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// spawned list should have no null entries because we
|
||||||
|
// always call Remove in OnObjectDestroy everywhere.
|
||||||
|
// if it does have null then someone used
|
||||||
|
// GameObject.Destroy instead of NetworkServer.Destroy.
|
||||||
|
else Debug.LogWarning($"Found 'null' entry in observing list for connectionId={connection.connectionId}. Please call NetworkServer.Destroy to destroy networked objects. Don't use GameObject.Destroy.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update //////////////////////////////////////////////////////////////
|
// update //////////////////////////////////////////////////////////////
|
||||||
// NetworkEarlyUpdate called before any Update/FixedUpdate
|
// NetworkEarlyUpdate called before any Update/FixedUpdate
|
||||||
// (we add this to the UnityEngine in NetworkLoop)
|
// (we add this to the UnityEngine in NetworkLoop)
|
||||||
@ -1425,6 +1473,19 @@ internal static void NetworkEarlyUpdate()
|
|||||||
// (we add this to the UnityEngine in NetworkLoop)
|
// (we add this to the UnityEngine in NetworkLoop)
|
||||||
internal static void NetworkLateUpdate()
|
internal static void NetworkLateUpdate()
|
||||||
{
|
{
|
||||||
|
// broadcast ClientToServer components while active
|
||||||
|
// note that Broadcast() runs every update.
|
||||||
|
// on clients with 120 Hz, this will run 120 times per second.
|
||||||
|
// however, Broadcast only checks .owned, which usually aren't many.
|
||||||
|
//
|
||||||
|
// we could use a .sendInterval, but it would also put a minimum
|
||||||
|
// limit to every component's sendInterval automatically.
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update connections to flush out messages _after_ broadcast
|
||||||
// local connection?
|
// local connection?
|
||||||
if (connection is LocalConnectionToServer localConnection)
|
if (connection is LocalConnectionToServer localConnection)
|
||||||
{
|
{
|
||||||
|
@ -884,9 +884,9 @@ internal void OnStopAuthority()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build dirty mask for owner & observer (= all dirty components).
|
// build dirty mask for server owner & observers (= all dirty components).
|
||||||
// faster to do it in one iteration instead of iterating separately.
|
// faster to do it in one iteration instead of iterating separately.
|
||||||
(ulong, ulong) DirtyMasks(bool initialState)
|
(ulong, ulong) ServerDirtyMasks(bool initialState)
|
||||||
{
|
{
|
||||||
ulong ownerMask = 0;
|
ulong ownerMask = 0;
|
||||||
ulong observerMask = 0;
|
ulong observerMask = 0;
|
||||||
@ -896,21 +896,60 @@ internal void OnStopAuthority()
|
|||||||
{
|
{
|
||||||
NetworkBehaviour component = components[i];
|
NetworkBehaviour component = components[i];
|
||||||
|
|
||||||
// check if dirty.
|
bool dirty = component.IsDirty();
|
||||||
// for owner, it's always included if dirty.
|
ulong nthBit = (1u << i);
|
||||||
// for observers, it's only included if dirty AND syncmode to observers.
|
|
||||||
bool ownerDirty = initialState || component.IsDirty();
|
|
||||||
bool observerDirty = ownerDirty && component.syncMode == SyncMode.Observers;
|
|
||||||
|
|
||||||
// set the n-th bit.
|
// owner needs to be considered for both SyncModes, because
|
||||||
// shifting from small to large numbers is varint-efficient.
|
// Observers mode always includes the Owner.
|
||||||
ownerMask |= (ulong)(ownerDirty ? 1 : 0) << i;
|
//
|
||||||
observerMask |= (ulong)(observerDirty ? 1 : 0) << i;
|
// for initial, it should always sync owner.
|
||||||
|
// for delta, only for ServerToClient and only if dirty.
|
||||||
|
// ClientToServer comes from the owner client.
|
||||||
|
if (initialState || (component.syncDirection == SyncDirection.ServerToClient && dirty))
|
||||||
|
ownerMask |= nthBit;
|
||||||
|
|
||||||
|
// observers need to be considered only in Observers mode
|
||||||
|
//
|
||||||
|
// for initial, it should always sync to observers.
|
||||||
|
// for delta, only if dirty.
|
||||||
|
// SyncDirection is irrelevant, as both are broadcast to
|
||||||
|
// observers which aren't the owner.
|
||||||
|
if (component.syncMode == SyncMode.Observers && (initialState || dirty))
|
||||||
|
observerMask |= nthBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (ownerMask, observerMask);
|
return (ownerMask, observerMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build dirty mask for client.
|
||||||
|
// server always knows initialState, so we don't need it here.
|
||||||
|
ulong ClientDirtyMask()
|
||||||
|
{
|
||||||
|
ulong mask = 0;
|
||||||
|
|
||||||
|
NetworkBehaviour[] components = NetworkBehaviours;
|
||||||
|
for (int i = 0; i < components.Length; ++i)
|
||||||
|
{
|
||||||
|
// on the client, we need to consider different sync scenarios:
|
||||||
|
//
|
||||||
|
// ServerToClient SyncDirection:
|
||||||
|
// do nothing.
|
||||||
|
// ClientToServer SyncDirection:
|
||||||
|
// serialize only if owned.
|
||||||
|
|
||||||
|
// on client, only consider owned components with SyncDirection to server
|
||||||
|
NetworkBehaviour component = components[i];
|
||||||
|
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
|
{
|
||||||
|
// set the n-th bit if dirty
|
||||||
|
// shifting from small to large numbers is varint-efficient.
|
||||||
|
if (component.IsDirty()) mask |= (1u << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
// check if n-th component is dirty.
|
// check if n-th component is dirty.
|
||||||
// in other words, if it has the n-th bit set in the dirty mask.
|
// in other words, if it has the n-th bit set in the dirty mask.
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@ -920,27 +959,24 @@ internal static bool IsDirty(ulong mask, int index)
|
|||||||
return (mask & nthBit) != 0;
|
return (mask & nthBit) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize all components using dirtyComponentsMask
|
// serialize components into writer on the server.
|
||||||
// check ownerWritten/observersWritten to know if anything was written
|
// check ownerWritten/observersWritten to know if anything was written
|
||||||
// We pass dirtyComponentsMask into this function so that we can check
|
// We pass dirtyComponentsMask into this function so that we can check
|
||||||
// if any Components are dirty before creating writers
|
// if any Components are dirty before creating writers
|
||||||
internal void Serialize(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
|
internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
|
||||||
{
|
{
|
||||||
// ensure NetworkBehaviours are valid before usage
|
// ensure NetworkBehaviours are valid before usage
|
||||||
ValidateComponents();
|
ValidateComponents();
|
||||||
NetworkBehaviour[] components = NetworkBehaviours;
|
NetworkBehaviour[] components = NetworkBehaviours;
|
||||||
|
|
||||||
|
// check which components are dirty for owner / observers.
|
||||||
|
// this is quite complicated with SyncMode + SyncDirection.
|
||||||
|
// see the function for explanation.
|
||||||
|
//
|
||||||
// instead of writing a 1 byte index per component,
|
// instead of writing a 1 byte index per component,
|
||||||
// we limit components to 64 bits and write one ulong instead.
|
// we limit components to 64 bits and write one ulong instead.
|
||||||
// the ulong is also varint compressed for minimum bandwidth.
|
// the ulong is also varint compressed for minimum bandwidth.
|
||||||
(ulong ownerMask, ulong observerMask) = DirtyMasks(initialState);
|
(ulong ownerMask, ulong observerMask) = ServerDirtyMasks(initialState);
|
||||||
|
|
||||||
// varint compresses the mask to 1 byte in most cases.
|
|
||||||
// instead of writing an 8 byte ulong.
|
|
||||||
// 7 components fit into 1 byte. (previously 7 bytes)
|
|
||||||
// 11 components fit into 2 bytes. (previously 11 bytes)
|
|
||||||
// 16 components fit into 3 bytes. (previously 16 bytes)
|
|
||||||
// TODO imer: client knows amount of comps, write N bytes instead
|
|
||||||
|
|
||||||
// if nothing dirty, then don't even write the mask.
|
// if nothing dirty, then don't even write the mask.
|
||||||
// otherwise, every unchanged object would send a 1 byte dirty mask!
|
// otherwise, every unchanged object would send a 1 byte dirty mask!
|
||||||
@ -955,42 +991,123 @@ internal void Serialize(bool initialState, NetworkWriter ownerWriter, NetworkWri
|
|||||||
{
|
{
|
||||||
NetworkBehaviour comp = components[i];
|
NetworkBehaviour comp = components[i];
|
||||||
|
|
||||||
// is this component dirty?
|
// is the component dirty for anyone (owner or observers)?
|
||||||
// reuse the mask instead of calling comp.IsDirty() again here.
|
// may be serialized to owner, observer, both, or neither.
|
||||||
if (IsDirty(ownerMask, i))
|
//
|
||||||
|
// OnSerialize should only be called once.
|
||||||
|
// this is faster, and it cleaner because it may set
|
||||||
|
// internal state, counters, logs, etc.
|
||||||
|
//
|
||||||
|
// previously we always serialized to owner and then copied
|
||||||
|
// the serialization to observers. however, since
|
||||||
|
// SyncDirection it's not guaranteed to be in owner anymore.
|
||||||
|
// so we need to serialize to temporary writer first.
|
||||||
|
// and then copy as needed.
|
||||||
|
bool ownerDirty = IsDirty(ownerMask, i);
|
||||||
|
bool observersDirty = IsDirty(observerMask, i);
|
||||||
|
if (ownerDirty || observersDirty)
|
||||||
{
|
{
|
||||||
//Debug.Log($"SerializeAll: {name} -> {comp.GetType()} initial:{ initialState}");
|
// serialize into helper writer
|
||||||
|
using (NetworkWriterPooled temp = NetworkWriterPool.Get())
|
||||||
// remember start position in case we need to copy it into
|
|
||||||
// observers writer too
|
|
||||||
int startPosition = ownerWriter.Position;
|
|
||||||
|
|
||||||
// serialize into ownerWriter first
|
|
||||||
// (owner always gets everything!)
|
|
||||||
comp.Serialize(ownerWriter, initialState);
|
|
||||||
|
|
||||||
// copy into observersWriter too if SyncMode.Observers
|
|
||||||
// -> we copy instead of calling OnSerialize again because
|
|
||||||
// we don't know what magic the user does in OnSerialize.
|
|
||||||
// -> it's not guaranteed that calling it twice gets the
|
|
||||||
// same result
|
|
||||||
// -> it's not guaranteed that calling it twice doesn't mess
|
|
||||||
// with the user's OnSerialize timing code etc.
|
|
||||||
// => so we just copy the result without touching
|
|
||||||
// OnSerialize again
|
|
||||||
if (IsDirty(observerMask, i))
|
|
||||||
// if (comp.syncMode == SyncMode.Observers)
|
|
||||||
{
|
{
|
||||||
ArraySegment<byte> segment = ownerWriter.ToArraySegment();
|
comp.Serialize(temp, initialState);
|
||||||
int length = ownerWriter.Position - startPosition;
|
ArraySegment<byte> segment = temp.ToArraySegment();
|
||||||
observersWriter.WriteBytes(segment.Array, startPosition, length);
|
|
||||||
|
// copy to owner / observers as needed
|
||||||
|
if (ownerDirty) ownerWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
|
||||||
|
if (observersDirty) observersWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Deserialize(NetworkReader reader, bool initialState)
|
// serialize components into writer on the client.
|
||||||
|
internal void SerializeClient(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
// ensure NetworkBehaviours are valid before usage
|
||||||
|
ValidateComponents();
|
||||||
|
NetworkBehaviour[] components = NetworkBehaviours;
|
||||||
|
|
||||||
|
// check which components are dirty.
|
||||||
|
// this is quite complicated with SyncMode + SyncDirection.
|
||||||
|
// see the function for explanation.
|
||||||
|
//
|
||||||
|
// instead of writing a 1 byte index per component,
|
||||||
|
// we limit components to 64 bits and write one ulong instead.
|
||||||
|
// the ulong is also varint compressed for minimum bandwidth.
|
||||||
|
ulong dirtyMask = ClientDirtyMask();
|
||||||
|
|
||||||
|
// varint compresses the mask to 1 byte in most cases.
|
||||||
|
// instead of writing an 8 byte ulong.
|
||||||
|
// 7 components fit into 1 byte. (previously 7 bytes)
|
||||||
|
// 11 components fit into 2 bytes. (previously 11 bytes)
|
||||||
|
// 16 components fit into 3 bytes. (previously 16 bytes)
|
||||||
|
// TODO imer: server knows amount of comps, write N bytes instead
|
||||||
|
|
||||||
|
// if nothing dirty, then don't even write the mask.
|
||||||
|
// otherwise, every unchanged object would send a 1 byte dirty mask!
|
||||||
|
if (dirtyMask != 0) Compression.CompressVarUInt(writer, dirtyMask);
|
||||||
|
|
||||||
|
// serialize all components
|
||||||
|
// perf: only iterate if dirty mask has dirty bits.
|
||||||
|
if (dirtyMask != 0)
|
||||||
|
{
|
||||||
|
// serialize all components
|
||||||
|
for (int i = 0; i < components.Length; ++i)
|
||||||
|
{
|
||||||
|
NetworkBehaviour comp = components[i];
|
||||||
|
|
||||||
|
// is this component dirty?
|
||||||
|
// reuse the mask instead of calling comp.IsDirty() again here.
|
||||||
|
if (IsDirty(dirtyMask, i))
|
||||||
|
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
|
{
|
||||||
|
// serialize into writer.
|
||||||
|
// server always knows initialState, we never need to send it
|
||||||
|
comp.Serialize(writer, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserialize components from the client on the server.
|
||||||
|
// there's no 'initialState'. server always knows the initial state.
|
||||||
|
internal bool DeserializeServer(NetworkReader reader)
|
||||||
|
{
|
||||||
|
// ensure NetworkBehaviours are valid before usage
|
||||||
|
ValidateComponents();
|
||||||
|
NetworkBehaviour[] components = NetworkBehaviours;
|
||||||
|
|
||||||
|
// first we deserialize the varinted dirty mask
|
||||||
|
ulong mask = Compression.DecompressVarUInt(reader);
|
||||||
|
|
||||||
|
// now deserialize every dirty component
|
||||||
|
for (int i = 0; i < components.Length; ++i)
|
||||||
|
{
|
||||||
|
// was this one dirty?
|
||||||
|
if (IsDirty(mask, i))
|
||||||
|
{
|
||||||
|
NetworkBehaviour comp = components[i];
|
||||||
|
|
||||||
|
// safety check to ensure clients can only modify their own
|
||||||
|
// ClientToServer components, nothing else.
|
||||||
|
if (comp.syncDirection == SyncDirection.ClientToServer)
|
||||||
|
{
|
||||||
|
// deserialize this component
|
||||||
|
// server always knows the initial state (initial=false)
|
||||||
|
// disconnect if failed, to prevent exploits etc.
|
||||||
|
if (!comp.Deserialize(reader, false)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// successfully deserialized everything
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserialize components from server on the client.
|
||||||
|
internal void DeserializeClient(NetworkReader reader, bool initialState)
|
||||||
{
|
{
|
||||||
// ensure NetworkBehaviours are valid before usage
|
// ensure NetworkBehaviours are valid before usage
|
||||||
ValidateComponents();
|
ValidateComponents();
|
||||||
@ -1006,14 +1123,16 @@ internal void Deserialize(NetworkReader reader, bool initialState)
|
|||||||
if (IsDirty(mask, i))
|
if (IsDirty(mask, i))
|
||||||
{
|
{
|
||||||
// deserialize this component
|
// deserialize this component
|
||||||
components[i].Deserialize(reader, initialState);
|
NetworkBehaviour comp = components[i];
|
||||||
|
comp.Deserialize(reader, initialState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get cached serialization for this tick (or serialize if none yet)
|
// get cached serialization for this tick (or serialize if none yet).
|
||||||
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
|
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks.
|
||||||
internal NetworkIdentitySerialization GetSerializationAtTick(int tick)
|
// calls SerializeServer, so this function is to be called on server.
|
||||||
|
internal NetworkIdentitySerialization GetServerSerializationAtTick(int tick)
|
||||||
{
|
{
|
||||||
// only rebuild serialization once per tick. reuse otherwise.
|
// only rebuild serialization once per tick. reuse otherwise.
|
||||||
// except for tests, where Time.frameCount never increases.
|
// except for tests, where Time.frameCount never increases.
|
||||||
@ -1032,9 +1151,9 @@ internal NetworkIdentitySerialization GetSerializationAtTick(int tick)
|
|||||||
lastSerialization.observersWriter.Position = 0;
|
lastSerialization.observersWriter.Position = 0;
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
Serialize(false,
|
SerializeServer(false,
|
||||||
lastSerialization.ownerWriter,
|
lastSerialization.ownerWriter,
|
||||||
lastSerialization.observersWriter);
|
lastSerialization.observersWriter);
|
||||||
|
|
||||||
// clear dirty bits for the components that we serialized.
|
// clear dirty bits for the components that we serialized.
|
||||||
// previously we did this in NetworkServer.BroadcastToConnection
|
// previously we did this in NetworkServer.BroadcastToConnection
|
||||||
|
@ -134,6 +134,7 @@ internal static void RegisterMessageHandlers()
|
|||||||
RegisterHandler<ReadyMessage>(OnClientReadyMessage);
|
RegisterHandler<ReadyMessage>(OnClientReadyMessage);
|
||||||
RegisterHandler<CommandMessage>(OnCommandMessage);
|
RegisterHandler<CommandMessage>(OnCommandMessage);
|
||||||
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
|
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
|
||||||
|
RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Starts server and listens to incoming connections with max connections limit.</summary>
|
/// <summary>Starts server and listens to incoming connections with max connections limit.</summary>
|
||||||
@ -985,6 +986,41 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg,
|
|||||||
identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn);
|
identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// client to server broadcast //////////////////////////////////////////
|
||||||
|
// for client's owned ClientToServer components.
|
||||||
|
static void OnEntityStateMessage(NetworkConnectionToClient connection, EntityStateMessage message)
|
||||||
|
{
|
||||||
|
// need to validate permissions carefully.
|
||||||
|
// an attacker may attempt to modify a not-owned or not-ClientToServer component.
|
||||||
|
|
||||||
|
// valid netId?
|
||||||
|
if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null)
|
||||||
|
{
|
||||||
|
// owned by the connection?
|
||||||
|
if (identity.connectionToClient == connection)
|
||||||
|
{
|
||||||
|
using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload))
|
||||||
|
{
|
||||||
|
// DeserializeServer checks permissions internally.
|
||||||
|
// failure to deserialize disconnects to prevent exploits.
|
||||||
|
if (!identity.DeserializeServer(reader))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Server failed to deserialize client state for {identity.name} with netId={identity.netId}, Disconnecting.");
|
||||||
|
connection.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// an attacker may attempt to modify another connection's entity
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Connection {connection.connectionId} attempted to modify {identity} which is not owned by the connection. Disconnecting the connection.");
|
||||||
|
connection.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no warning. don't spam server logs.
|
||||||
|
// else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
|
||||||
|
}
|
||||||
|
|
||||||
// spawning ////////////////////////////////////////////////////////////
|
// spawning ////////////////////////////////////////////////////////////
|
||||||
static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter)
|
static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter)
|
||||||
{
|
{
|
||||||
@ -996,7 +1032,7 @@ static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentit
|
|||||||
|
|
||||||
// serialize all components with initialState = true
|
// serialize all components with initialState = true
|
||||||
// (can be null if has none)
|
// (can be null if has none)
|
||||||
identity.Serialize(true, ownerWriter, observersWriter);
|
identity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
// convert to ArraySegment to avoid reader allocations
|
// convert to ArraySegment to avoid reader allocations
|
||||||
// if nothing was written, .ToArraySegment returns an empty segment.
|
// if nothing was written, .ToArraySegment returns an empty segment.
|
||||||
@ -1592,7 +1628,7 @@ static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identit
|
|||||||
{
|
{
|
||||||
// get serialization for this entity (cached)
|
// get serialization for this entity (cached)
|
||||||
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
|
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
|
||||||
NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(Time.frameCount);
|
NetworkIdentitySerialization serialization = identity.GetServerSerializationAtTick(Time.frameCount);
|
||||||
|
|
||||||
// is this entity owned by this connection?
|
// is this entity owned by this connection?
|
||||||
bool owned = identity.connectionToClient == connection;
|
bool owned = identity.connectionToClient == connection;
|
||||||
|
@ -85,7 +85,15 @@ protected void DrawDefaultSyncSettings()
|
|||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel);
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
|
// sync direction
|
||||||
|
SerializedProperty syncDirection = serializedObject.FindProperty("syncDirection");
|
||||||
|
EditorGUILayout.PropertyField(syncDirection);
|
||||||
|
|
||||||
|
// sync mdoe: only show for ServerToClient components
|
||||||
|
if (syncDirection.enumValueIndex == (int)SyncDirection.ServerToClient)
|
||||||
|
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
|
||||||
|
|
||||||
|
// sync interval
|
||||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
|
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
|
||||||
|
|
||||||
// apply
|
// apply
|
||||||
|
8
Assets/Mirror/Examples/SyncDirection.meta
Normal file
8
Assets/Mirror/Examples/SyncDirection.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f11945ec2c0384dd8880a19d3daf98cd
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
46
Assets/Mirror/Examples/SyncDirection/Player.cs
Normal file
46
Assets/Mirror/Examples/SyncDirection/Player.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Mirror.Examples.SyncDir // ".SyncDirection" would overshadow the enum
|
||||||
|
{
|
||||||
|
public class Player : NetworkBehaviour
|
||||||
|
{
|
||||||
|
public TextMesh textMesh;
|
||||||
|
[SyncVar] public int health;
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// show health for everyone
|
||||||
|
textMesh.text = health.ToString();
|
||||||
|
|
||||||
|
// space bar increases health for local player.
|
||||||
|
// note that trusting the client is a bad idea, especially with health.
|
||||||
|
// SyncDirection is usually used for movement.
|
||||||
|
//
|
||||||
|
// when using custom OnSerialize, the custom OnDeserialize can still
|
||||||
|
// safely validate client data (check position, velocity etc.).
|
||||||
|
// this is why it's named SyncDirection, and not ClientAuthority.
|
||||||
|
// because the server can still validate the client's data first.
|
||||||
|
//
|
||||||
|
// try to change SyncDirection to ServerToClient in the editor.
|
||||||
|
// then restart the game, clients won't be allowed to change their
|
||||||
|
// own health anymore.
|
||||||
|
if (isLocalPlayer)
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.Space))
|
||||||
|
++health;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show instructions
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
if (!isLocalPlayer) return;
|
||||||
|
|
||||||
|
int width = 250;
|
||||||
|
int height = 20;
|
||||||
|
GUI.Label(
|
||||||
|
new Rect(Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height),
|
||||||
|
"Press Space to increase your own health!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Examples/SyncDirection/Player.cs.meta
Normal file
11
Assets/Mirror/Examples/SyncDirection/Player.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 956e0a6952a1c4c0dabd59805ab743a7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
219
Assets/Mirror/Examples/SyncDirection/Player.prefab
Normal file
219
Assets/Mirror/Examples/SyncDirection/Player.prefab
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &449802645721213856
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 2697352357490696306}
|
||||||
|
- component: {fileID: 8695142844114820487}
|
||||||
|
- component: {fileID: 6692327599609520039}
|
||||||
|
- component: {fileID: 1078519278818213949}
|
||||||
|
- component: {fileID: -3280429733832156930}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Player
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &2697352357490696306
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 449802645721213856}
|
||||||
|
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_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 6238039487279352128}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!33 &8695142844114820487
|
||||||
|
MeshFilter:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 449802645721213856}
|
||||||
|
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
--- !u!23 &6692327599609520039
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 449802645721213856}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 2100000, guid: 2dd029e0943dc488ea1435fa13429182, type: 2}
|
||||||
|
m_StaticBatchInfo:
|
||||||
|
firstSubMesh: 0
|
||||||
|
subMeshCount: 0
|
||||||
|
m_StaticBatchRoot: {fileID: 0}
|
||||||
|
m_ProbeAnchor: {fileID: 0}
|
||||||
|
m_LightProbeVolumeOverride: {fileID: 0}
|
||||||
|
m_ScaleInLightmap: 1
|
||||||
|
m_ReceiveGI: 1
|
||||||
|
m_PreserveUVs: 0
|
||||||
|
m_IgnoreNormalsForChartDetection: 0
|
||||||
|
m_ImportantGI: 0
|
||||||
|
m_StitchLightmapSeams: 0
|
||||||
|
m_SelectedEditorRenderState: 3
|
||||||
|
m_MinimumChartSize: 4
|
||||||
|
m_AutoUVMaxDistance: 0.5
|
||||||
|
m_AutoUVMaxAngle: 89
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!114 &1078519278818213949
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 449802645721213856}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
sceneId: 0
|
||||||
|
_assetId: 645619783
|
||||||
|
serverOnly: 0
|
||||||
|
visible: 0
|
||||||
|
hasSpawned: 0
|
||||||
|
--- !u!114 &-3280429733832156930
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 449802645721213856}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 956e0a6952a1c4c0dabd59805ab743a7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
syncDirection: 1
|
||||||
|
syncMode: 0
|
||||||
|
syncInterval: 0.1
|
||||||
|
textMesh: {fileID: 5045126741796351903}
|
||||||
|
health: 0
|
||||||
|
--- !u!1 &3382624280860738034
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 6238039487279352128}
|
||||||
|
- component: {fileID: 5006305470578639025}
|
||||||
|
- component: {fileID: 5045126741796351903}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: TextMesh
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &6238039487279352128
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3382624280860738034}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 4, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 2697352357490696306}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!23 &5006305470578639025
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3382624280860738034}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_StaticBatchInfo:
|
||||||
|
firstSubMesh: 0
|
||||||
|
subMeshCount: 0
|
||||||
|
m_StaticBatchRoot: {fileID: 0}
|
||||||
|
m_ProbeAnchor: {fileID: 0}
|
||||||
|
m_LightProbeVolumeOverride: {fileID: 0}
|
||||||
|
m_ScaleInLightmap: 1
|
||||||
|
m_ReceiveGI: 1
|
||||||
|
m_PreserveUVs: 0
|
||||||
|
m_IgnoreNormalsForChartDetection: 0
|
||||||
|
m_ImportantGI: 0
|
||||||
|
m_StitchLightmapSeams: 1
|
||||||
|
m_SelectedEditorRenderState: 3
|
||||||
|
m_MinimumChartSize: 4
|
||||||
|
m_AutoUVMaxDistance: 0.5
|
||||||
|
m_AutoUVMaxAngle: 89
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!102 &5045126741796351903
|
||||||
|
TextMesh:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3382624280860738034}
|
||||||
|
m_Text: 0
|
||||||
|
m_OffsetZ: 0
|
||||||
|
m_CharacterSize: 0.24
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Anchor: 1
|
||||||
|
m_Alignment: 0
|
||||||
|
m_TabSize: 4
|
||||||
|
m_FontSize: 86
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_Font: {fileID: 0}
|
||||||
|
m_Color:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 4278190080
|
7
Assets/Mirror/Examples/SyncDirection/Player.prefab.meta
Normal file
7
Assets/Mirror/Examples/SyncDirection/Player.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c1affccc0b0c040a486023a5cff7fd46
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
607
Assets/Mirror/Examples/SyncDirection/Scene.unity
Normal file
607
Assets/Mirror/Examples/SyncDirection/Scene.unity
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 00000000000000000000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 9
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 3
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 12
|
||||||
|
m_GIWorkflowMode: 1
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 0
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_FinalGather: 0
|
||||||
|
m_FinalGatherFiltering: 1
|
||||||
|
m_FinalGatherRayCount: 256
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 0
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 500
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 500
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 2
|
||||||
|
m_PVRDenoiserTypeDirect: 0
|
||||||
|
m_PVRDenoiserTypeIndirect: 0
|
||||||
|
m_PVRDenoiserTypeAO: 0
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 0
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 5
|
||||||
|
m_PVRFilteringGaussRadiusAO: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
accuratePlacement: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 23800000, guid: 0bc607fa2e315482ebe98797e844e11f, type: 2}
|
||||||
|
--- !u!1 &88936773
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 88936777}
|
||||||
|
- component: {fileID: 88936776}
|
||||||
|
- component: {fileID: 88936774}
|
||||||
|
- component: {fileID: 88936778}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &88936774
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 88936773}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 9021b6cc314944290986ab6feb48db79, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
height: 150
|
||||||
|
maxLogCount: 50
|
||||||
|
hotKey: 293
|
||||||
|
--- !u!20 &88936776
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 88936773}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.39215687, g: 0.58431375, b: 0.92941177, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 0
|
||||||
|
orthographic size: 35
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &88936777
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 88936773}
|
||||||
|
m_LocalRotation: {x: 0.38268343, y: 0, z: 0, w: 0.92387956}
|
||||||
|
m_LocalPosition: {x: 0, y: 50, z: -80}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 0
|
||||||
|
m_LocalEulerAnglesHint: {x: 45, y: 0, z: 0}
|
||||||
|
--- !u!114 &88936778
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 88936773}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6635375fbc6be456ea640b75add6378e, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
showGUI: 1
|
||||||
|
showLog: 0
|
||||||
|
--- !u!1 &148314705
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 148314710}
|
||||||
|
- component: {fileID: 148314709}
|
||||||
|
- component: {fileID: 148314708}
|
||||||
|
- component: {fileID: 148314707}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: NetworkManager
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &148314707
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 148314705}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
Port: 7777
|
||||||
|
DualMode: 1
|
||||||
|
NoDelay: 1
|
||||||
|
Interval: 10
|
||||||
|
Timeout: 10000
|
||||||
|
FastResend: 2
|
||||||
|
CongestionWindow: 0
|
||||||
|
SendWindowSize: 4096
|
||||||
|
ReceiveWindowSize: 4096
|
||||||
|
MaxRetransmit: 40
|
||||||
|
NonAlloc: 1
|
||||||
|
MaximizeSendReceiveBuffersToOSLimit: 1
|
||||||
|
ReliableMaxMessageSize: 298449
|
||||||
|
UnreliableMaxMessageSize: 1199
|
||||||
|
debugLog: 0
|
||||||
|
statisticsGUI: 0
|
||||||
|
statisticsLog: 0
|
||||||
|
--- !u!114 &148314708
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 148314705}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
offsetX: 0
|
||||||
|
offsetY: 0
|
||||||
|
--- !u!114 &148314709
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 148314705}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 8aab4c8111b7c411b9b92cf3dbc5bd4e, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
dontDestroyOnLoad: 1
|
||||||
|
runInBackground: 1
|
||||||
|
autoStartServerBuild: 1
|
||||||
|
autoConnectClientBuild: 0
|
||||||
|
serverTickRate: 30
|
||||||
|
offlineScene:
|
||||||
|
onlineScene:
|
||||||
|
transport: {fileID: 148314707}
|
||||||
|
networkAddress: localhost
|
||||||
|
maxConnections: 1000
|
||||||
|
authenticator: {fileID: 0}
|
||||||
|
playerPrefab: {fileID: 449802645721213856, guid: c1affccc0b0c040a486023a5cff7fd46,
|
||||||
|
type: 3}
|
||||||
|
autoCreatePlayer: 1
|
||||||
|
playerSpawnMethod: 1
|
||||||
|
spawnPrefabs: []
|
||||||
|
timeInterpolationGui: 1
|
||||||
|
--- !u!4 &148314710
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 148314705}
|
||||||
|
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_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 2
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &985780316
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 985780318}
|
||||||
|
- component: {fileID: 985780317}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SpawnPosition (1)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &985780317
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 985780316}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!4 &985780318
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 985780316}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -1, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 3
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &996584419
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 996584421}
|
||||||
|
- component: {fileID: 996584420}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SpawnPosition (3)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &996584420
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 996584419}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!4 &996584421
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 996584419}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 4, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 5
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &1001938617
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1001938619}
|
||||||
|
- component: {fileID: 1001938618}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SpawnPosition (2)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &1001938618
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1001938617}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!4 &1001938619
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1001938617}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 1, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 4
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &1592625468
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1592625470}
|
||||||
|
- component: {fileID: 1592625469}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SpawnPosition (4)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &1592625469
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1592625468}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!4 &1592625470
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1592625468}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -4, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 6
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &2054208274
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 2054208276}
|
||||||
|
- component: {fileID: 2054208275}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Directional light
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!108 &2054208275
|
||||||
|
Light:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2054208274}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Type: 1
|
||||||
|
m_Shape: 0
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_Intensity: 0.8
|
||||||
|
m_Range: 10
|
||||||
|
m_SpotAngle: 30
|
||||||
|
m_InnerSpotAngle: 21.80208
|
||||||
|
m_CookieSize: 10
|
||||||
|
m_Shadows:
|
||||||
|
m_Type: 2
|
||||||
|
m_Resolution: -1
|
||||||
|
m_CustomResolution: -1
|
||||||
|
m_Strength: 1
|
||||||
|
m_Bias: 0.05
|
||||||
|
m_NormalBias: 0.4
|
||||||
|
m_NearPlane: 0.2
|
||||||
|
m_CullingMatrixOverride:
|
||||||
|
e00: 1
|
||||||
|
e01: 0
|
||||||
|
e02: 0
|
||||||
|
e03: 0
|
||||||
|
e10: 0
|
||||||
|
e11: 1
|
||||||
|
e12: 0
|
||||||
|
e13: 0
|
||||||
|
e20: 0
|
||||||
|
e21: 0
|
||||||
|
e22: 1
|
||||||
|
e23: 0
|
||||||
|
e30: 0
|
||||||
|
e31: 0
|
||||||
|
e32: 0
|
||||||
|
e33: 1
|
||||||
|
m_UseCullingMatrixOverride: 0
|
||||||
|
m_Cookie: {fileID: 0}
|
||||||
|
m_DrawHalo: 0
|
||||||
|
m_Flare: {fileID: 0}
|
||||||
|
m_RenderMode: 0
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_Lightmapping: 4
|
||||||
|
m_LightShadowCasterMode: 0
|
||||||
|
m_AreaSize: {x: 1, y: 1}
|
||||||
|
m_BounceIntensity: 1
|
||||||
|
m_ColorTemperature: 6570
|
||||||
|
m_UseColorTemperature: 0
|
||||||
|
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_UseBoundingSphereOverride: 0
|
||||||
|
m_UseViewFrustumForShadowCasterCull: 1
|
||||||
|
m_ShadowRadius: 0
|
||||||
|
m_ShadowAngle: 0
|
||||||
|
--- !u!4 &2054208276
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2054208274}
|
||||||
|
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_RootOrder: 1
|
||||||
|
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
7
Assets/Mirror/Examples/SyncDirection/Scene.unity.meta
Normal file
7
Assets/Mirror/Examples/SyncDirection/Scene.unity.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 13d58f0d13296447ea167348d60d6c22
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
77
Assets/Mirror/Examples/SyncDirection/White.mat
Normal file
77
Assets/Mirror/Examples/SyncDirection/White.mat
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: White
|
||||||
|
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_ShaderKeywords: _EMISSION
|
||||||
|
m_LightmapFlags: 0
|
||||||
|
m_EnableInstancingVariants: 1
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Floats:
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _GlossMapScale: 1
|
||||||
|
- _Glossiness: 0
|
||||||
|
- _GlossyReflections: 1
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Mode: 0
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.02
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _UVSec: 0
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _EmissionColor: {r: 0.3254902, g: 0.3254902, b: 0.3254902, a: 1}
|
8
Assets/Mirror/Examples/SyncDirection/White.mat.meta
Normal file
8
Assets/Mirror/Examples/SyncDirection/White.mat.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2dd029e0943dc488ea1435fa13429182
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -49,11 +49,11 @@ public void SerializeAndDeserializeAll()
|
|||||||
serverObserversComp.value = 42;
|
serverObserversComp.value = 42;
|
||||||
|
|
||||||
// serialize server object
|
// serialize server object
|
||||||
serverIdentity.Serialize(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
// deserialize client object with OWNER payload
|
// deserialize client object with OWNER payload
|
||||||
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
Assert.That(clientOwnerComp.value, Is.EqualTo("42"));
|
Assert.That(clientOwnerComp.value, Is.EqualTo("42"));
|
||||||
Assert.That(clientObserversComp.value, Is.EqualTo(42));
|
Assert.That(clientObserversComp.value, Is.EqualTo(42));
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ public void SerializeAndDeserializeAll()
|
|||||||
|
|
||||||
// deserialize client object with OBSERVERS payload
|
// deserialize client object with OBSERVERS payload
|
||||||
reader = new NetworkReader(observersWriter.ToArray());
|
reader = new NetworkReader(observersWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
Assert.That(clientOwnerComp.value, Is.EqualTo(null)); // owner mode shouldn't be in data
|
Assert.That(clientOwnerComp.value, Is.EqualTo(null)); // owner mode shouldn't be in data
|
||||||
Assert.That(clientObserversComp.value, Is.EqualTo(42)); // observers mode should be in data
|
Assert.That(clientObserversComp.value, Is.EqualTo(42)); // observers mode should be in data
|
||||||
}
|
}
|
||||||
@ -95,13 +95,13 @@ public void SerializationException()
|
|||||||
// serialize server object
|
// serialize server object
|
||||||
// should work even if compExc throws an exception.
|
// should work even if compExc throws an exception.
|
||||||
// error log because of the exception is expected.
|
// error log because of the exception is expected.
|
||||||
serverIdentity.Serialize(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
// deserialize client object with OWNER payload
|
// deserialize client object with OWNER payload
|
||||||
// should work even if compExc throws an exception
|
// should work even if compExc throws an exception
|
||||||
// error log because of the exception is expected
|
// error log because of the exception is expected
|
||||||
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
Assert.That(clientComp2.value, Is.EqualTo("42"));
|
Assert.That(clientComp2.value, Is.EqualTo("42"));
|
||||||
|
|
||||||
// reset component values
|
// reset component values
|
||||||
@ -111,7 +111,7 @@ public void SerializationException()
|
|||||||
// should work even if compExc throws an exception
|
// should work even if compExc throws an exception
|
||||||
// error log because of the exception is expected
|
// error log because of the exception is expected
|
||||||
reader = new NetworkReader(observersWriter.ToArray());
|
reader = new NetworkReader(observersWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
Assert.That(clientComp2.value, Is.EqualTo(null)); // owner mode should be in data
|
Assert.That(clientComp2.value, Is.EqualTo(null)); // owner mode should be in data
|
||||||
|
|
||||||
// restore error checks
|
// restore error checks
|
||||||
@ -186,13 +186,13 @@ public void SerializationMismatch()
|
|||||||
serverComp.value = "42";
|
serverComp.value = "42";
|
||||||
|
|
||||||
// serialize server object
|
// serialize server object
|
||||||
serverIdentity.Serialize(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
// deserialize on client
|
// deserialize on client
|
||||||
// ignore warning log because of serialization mismatch
|
// ignore warning log because of serialization mismatch
|
||||||
LogAssert.ignoreFailingMessages = true;
|
LogAssert.ignoreFailingMessages = true;
|
||||||
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
LogAssert.ignoreFailingMessages = false;
|
LogAssert.ignoreFailingMessages = false;
|
||||||
|
|
||||||
// the mismatch component will fail, but the one before and after
|
// the mismatch component will fail, but the one before and after
|
||||||
@ -205,7 +205,7 @@ public void SerializationMismatch()
|
|||||||
// 0-dirty-mask. instead, we need to ensure it writes nothing.
|
// 0-dirty-mask. instead, we need to ensure it writes nothing.
|
||||||
// too easy to miss, with too significant bandwidth implications.
|
// too easy to miss, with too significant bandwidth implications.
|
||||||
[Test]
|
[Test]
|
||||||
public void Serialize_NotInitial_NotDirty_WritesNothing()
|
public void SerializeServer_NotInitial_NotDirty_WritesNothing()
|
||||||
{
|
{
|
||||||
// create spawned so that isServer/isClient is set properly
|
// create spawned so that isServer/isClient is set properly
|
||||||
CreateNetworkedAndSpawn(
|
CreateNetworkedAndSpawn(
|
||||||
@ -218,9 +218,123 @@ public void Serialize_NotInitial_NotDirty_WritesNothing()
|
|||||||
// serialize server object.
|
// serialize server object.
|
||||||
// 'initial' would write everything.
|
// 'initial' would write everything.
|
||||||
// instead, try 'not initial' with 0 dirty bits
|
// instead, try 'not initial' with 0 dirty bits
|
||||||
serverIdentity.Serialize(false, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializeClient_NotInitial_NotDirty_WritesNothing()
|
||||||
|
{
|
||||||
|
// create spawned so that isServer/isClient is set properly
|
||||||
|
CreateNetworkedAndSpawn(
|
||||||
|
out _, out NetworkIdentity serverIdentity, out SerializeTest1NetworkBehaviour serverComp1, out SerializeTest2NetworkBehaviour serverComp2,
|
||||||
|
out _, out NetworkIdentity clientIdentity, out SerializeTest1NetworkBehaviour clientComp1, out SerializeTest2NetworkBehaviour clientComp2);
|
||||||
|
|
||||||
|
// change nothing
|
||||||
|
// clientComp.value = "42";
|
||||||
|
|
||||||
|
// serialize client object
|
||||||
|
serverIdentity.SerializeClient(ownerWriter);
|
||||||
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize -> deserialize. multiple components to be sure.
|
||||||
|
// one for Owner, one for Observer
|
||||||
|
// one ServerToClient, one ClientToServer
|
||||||
|
[Test]
|
||||||
|
public void SerializeAndDeserialize_ClientToServer_NOT_OWNED()
|
||||||
|
{
|
||||||
|
CreateNetworked(out GameObject _, out NetworkIdentity identity,
|
||||||
|
out SerializeTest1NetworkBehaviour comp1,
|
||||||
|
out SerializeTest2NetworkBehaviour comp2);
|
||||||
|
|
||||||
|
// set to CLIENT with some unique values
|
||||||
|
// and set connection to server to pretend we are the owner.
|
||||||
|
identity.isOwned = false;
|
||||||
|
identity.connectionToServer = null; // NOT OWNED
|
||||||
|
comp1.syncDirection = SyncDirection.ServerToClient;
|
||||||
|
comp1.value = 12345;
|
||||||
|
comp2.syncDirection = SyncDirection.ClientToServer;
|
||||||
|
comp2.value = "67890";
|
||||||
|
|
||||||
|
// serialize all
|
||||||
|
identity.SerializeClient(ownerWriter);
|
||||||
|
|
||||||
|
// shouldn't sync anything. because even though it's ClientToServer,
|
||||||
|
// we don't own this one so we shouldn't serialize & sync it.
|
||||||
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// server should still send initial even if Owner + ClientToServer
|
||||||
|
[Test]
|
||||||
|
public void SerializeServer_OwnerMode_ClientToServer()
|
||||||
|
{
|
||||||
|
CreateNetworked(out GameObject _, out NetworkIdentity identity,
|
||||||
|
out SyncVarTest1NetworkBehaviour comp);
|
||||||
|
|
||||||
|
// pretend to be owned
|
||||||
|
identity.isOwned = true;
|
||||||
|
comp.syncMode = SyncMode.Owner;
|
||||||
|
comp.syncInterval = 0;
|
||||||
|
|
||||||
|
// set to CLIENT with some unique values
|
||||||
|
// and set connection to server to pretend we are the owner.
|
||||||
|
comp.syncDirection = SyncDirection.ClientToServer;
|
||||||
|
comp.value = 12345;
|
||||||
|
|
||||||
|
// initial: should still write for owner
|
||||||
|
identity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
Debug.Log("initial ownerWriter: " + ownerWriter);
|
||||||
|
Debug.Log("initial observerWriter: " + observersWriter);
|
||||||
|
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
||||||
|
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
||||||
|
|
||||||
|
// delta: ClientToServer comes from the client
|
||||||
|
++comp.value; // change something
|
||||||
|
ownerWriter.Position = 0;
|
||||||
|
observersWriter.Position = 0;
|
||||||
|
identity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
|
Debug.Log("delta ownerWriter: " + ownerWriter);
|
||||||
|
Debug.Log("delta observersWriter: " + observersWriter);
|
||||||
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
|
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// server should still broadcast ClientToServer components to everyone
|
||||||
|
// except the owner.
|
||||||
|
[Test]
|
||||||
|
public void SerializeServer_ObserversMode_ClientToServer()
|
||||||
|
{
|
||||||
|
CreateNetworked(out GameObject _, out NetworkIdentity identity,
|
||||||
|
out SyncVarTest1NetworkBehaviour comp);
|
||||||
|
|
||||||
|
// pretend to be owned
|
||||||
|
identity.isOwned = true;
|
||||||
|
comp.syncMode = SyncMode.Observers;
|
||||||
|
comp.syncInterval = 0;
|
||||||
|
|
||||||
|
// set to CLIENT with some unique values
|
||||||
|
// and set connection to server to pretend we are the owner.
|
||||||
|
comp.syncDirection = SyncDirection.ClientToServer;
|
||||||
|
comp.value = 12345;
|
||||||
|
|
||||||
|
// initial: should write something for owner and observers
|
||||||
|
identity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
Debug.Log("initial ownerWriter: " + ownerWriter);
|
||||||
|
Debug.Log("initial observerWriter: " + observersWriter);
|
||||||
|
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
||||||
|
Assert.That(observersWriter.Position, Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// delta: should only write for observers
|
||||||
|
++comp.value; // change something
|
||||||
|
ownerWriter.Position = 0;
|
||||||
|
observersWriter.Position = 0;
|
||||||
|
identity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
|
Debug.Log("delta ownerWriter: " + ownerWriter);
|
||||||
|
Debug.Log("delta observersWriter: " + observersWriter);
|
||||||
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
|
Assert.That(observersWriter.Position, Is.GreaterThan(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,16 @@ public override void OnDeserialize(NetworkReader reader, bool initialState)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SyncVarTest1NetworkBehaviour : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[SyncVar] public int value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncVarTest2NetworkBehaviour : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[SyncVar] public string value;
|
||||||
|
}
|
||||||
|
|
||||||
class SerializeExceptionNetworkBehaviour : NetworkBehaviour
|
class SerializeExceptionNetworkBehaviour : NetworkBehaviour
|
||||||
{
|
{
|
||||||
public override void OnSerialize(NetworkWriter writer, bool initialState)
|
public override void OnSerialize(NetworkWriter writer, bool initialState)
|
||||||
|
@ -407,14 +407,14 @@ public void TestSyncingAbstractNetworkBehaviour()
|
|||||||
NetworkWriter ownerWriter = new NetworkWriter();
|
NetworkWriter ownerWriter = new NetworkWriter();
|
||||||
// not really used in this Test
|
// not really used in this Test
|
||||||
NetworkWriter observersWriter = new NetworkWriter();
|
NetworkWriter observersWriter = new NetworkWriter();
|
||||||
serverIdentity.Serialize(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
// set up a "client" object
|
// set up a "client" object
|
||||||
CreateNetworked(out _, out NetworkIdentity clientIdentity, out SyncVarAbstractNetworkBehaviour clientBehaviour);
|
CreateNetworked(out _, out NetworkIdentity clientIdentity, out SyncVarAbstractNetworkBehaviour clientBehaviour);
|
||||||
|
|
||||||
// apply all the data from the server object
|
// apply all the data from the server object
|
||||||
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
||||||
clientIdentity.Deserialize(reader, true);
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
|
|
||||||
// check that the syncvars got updated
|
// check that the syncvars got updated
|
||||||
Assert.That(clientBehaviour.monster1, Is.EqualTo(serverBehaviour.monster1), "Data should be synchronized");
|
Assert.That(clientBehaviour.monster1, Is.EqualTo(serverBehaviour.monster1), "Data should be synchronized");
|
||||||
|
@ -66,10 +66,10 @@ public IEnumerator TestSerializationWithLargeTimestamps()
|
|||||||
// 14 * 24 hours per day * 60 minutes per hour * 60 seconds per minute = 14 days
|
// 14 * 24 hours per day * 60 minutes per hour * 60 seconds per minute = 14 days
|
||||||
// NOTE: change this to 'float' to see the tests fail
|
// NOTE: change this to 'float' to see the tests fail
|
||||||
int tick = 14 * 24 * 60 * 60;
|
int tick = 14 * 24 * 60 * 60;
|
||||||
NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(tick);
|
NetworkIdentitySerialization serialization = identity.GetServerSerializationAtTick(tick);
|
||||||
// advance tick
|
// advance tick
|
||||||
++tick;
|
++tick;
|
||||||
NetworkIdentitySerialization serializationNew = identity.GetSerializationAtTick(tick);
|
NetworkIdentitySerialization serializationNew = identity.GetServerSerializationAtTick(tick);
|
||||||
|
|
||||||
// if the serialization has been changed the tickTimeStamp should have moved
|
// if the serialization has been changed the tickTimeStamp should have moved
|
||||||
Assert.That(serialization.tick == serializationNew.tick, Is.False);
|
Assert.That(serialization.tick == serializationNew.tick, Is.False);
|
||||||
|
Loading…
Reference in New Issue
Block a user