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:
mischa 2022-10-19 23:54:54 +02:00 committed by GitHub
parent a1fbc55e8a
commit e0a91eeb3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1455 additions and 77 deletions

View File

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

View File

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

View File

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

View File

@ -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!
@ -951,46 +987,127 @@ internal void Serialize(bool initialState, NetworkWriter ownerWriter, NetworkWri
// perf: only iterate if either dirty mask has dirty bits. // perf: only iterate if either dirty mask has dirty bits.
if ((ownerMask | observerMask) != 0) if ((ownerMask | observerMask) != 0)
{ {
for (int i = 0; i < components.Length; ++i)
{
NetworkBehaviour comp = components[i];
// is the component dirty for anyone (owner or observers)?
// may be serialized to owner, observer, both, or neither.
//
// 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)
{
// serialize into helper writer
using (NetworkWriterPooled temp = NetworkWriterPool.Get())
{
comp.Serialize(temp, initialState);
ArraySegment<byte> segment = temp.ToArraySegment();
// 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);
}
}
}
}
}
// 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) for (int i = 0; i < components.Length; ++i)
{ {
NetworkBehaviour comp = components[i]; NetworkBehaviour comp = components[i];
// is this component dirty? // is this component dirty?
// reuse the mask instead of calling comp.IsDirty() again here. // reuse the mask instead of calling comp.IsDirty() again here.
if (IsDirty(ownerMask, i)) if (IsDirty(dirtyMask, i))
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
{ {
//Debug.Log($"SerializeAll: {name} -> {comp.GetType()} initial:{ initialState}"); // serialize into writer.
// server always knows initialState, we never need to send it
comp.Serialize(writer, false);
}
}
}
}
// remember start position in case we need to copy it into // deserialize components from the client on the server.
// observers writer too // there's no 'initialState'. server always knows the initial state.
int startPosition = ownerWriter.Position; internal bool DeserializeServer(NetworkReader reader)
// 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(); // ensure NetworkBehaviours are valid before usage
int length = ownerWriter.Position - startPosition; ValidateComponents();
observersWriter.WriteBytes(segment.Array, startPosition, length); 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;
} }
} }
} }
internal void Deserialize(NetworkReader reader, bool initialState) // 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,7 +1151,7 @@ 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);

View File

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

View File

@ -85,7 +85,15 @@ protected void DrawDefaultSyncSettings()
EditorGUILayout.Space(); EditorGUILayout.Space();
EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel); EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel);
// 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")); EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
// sync interval
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval")); EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
// apply // apply

View File

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

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

View File

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

View 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

View File

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

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

View File

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

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2dd029e0943dc488ea1435fa13429182
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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