fishnet installed

This commit is contained in:
2023-05-31 11:32:21 -04:00
parent 47b25269f1
commit a001fe1b04
1291 changed files with 126631 additions and 1 deletions

View File

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

View File

@ -0,0 +1,51 @@
using FishNet.Connection;
using FishNet.Managing;
using System;
using UnityEngine;
namespace FishNet.Authenticating
{
/// <summary>
/// When inherited from this can be used to create a custom authentication process before clients may communicate with the server.
/// </summary>
public abstract class Authenticator : MonoBehaviour
{
#region Public.
/// <summary>
/// True if this authenticator has been intiialzied.
/// </summary>
public bool Initialized { get; private set; }
#endregion
#region Protected.
/// <summary>
/// NetworkManager for this Authenticator.
/// </summary>
protected NetworkManager NetworkManager { get; private set; }
#endregion
/// <summary>
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
/// Server listens for this event automatically.
/// </summary>
public abstract event Action<NetworkConnection, bool> OnAuthenticationResult;
/// <summary>
/// Initializes this script for use.
/// </summary>
/// <param name="networkManager"></param>
public virtual void InitializeOnce(NetworkManager networkManager)
{
NetworkManager = networkManager;
Initialized = true;
}
/// <summary>
/// Called on the server immediately after a client connects. Can be used to send data to the client for authentication.
/// </summary>
/// <param name="connection">Connection which is not yet authenticated.</param>
public virtual void OnRemoteConnection(NetworkConnection connection) { }
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,19 @@
using FishNet.Object.Helping;
namespace FishNet.Broadcast.Helping
{
internal static class BroadcastHelper
{
/// <summary>
/// Gets the key for a broadcast type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="broadcastType"></param>
/// <returns></returns>
public static ushort GetKey<T>()
{
return typeof(T).FullName.GetStableHash16();
}
}
}

View File

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

View File

@ -0,0 +1,8 @@

namespace FishNet.Broadcast
{
/// <summary>
/// Include this interface on types intended to be used with Broadcast.
/// </summary>
public interface IBroadcast { }
}

View File

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

View File

@ -0,0 +1 @@
{"StripReleaseBuilds":true}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9f1ece47c2d48194ea4827bf592a2279
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,259 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Serializing;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A byte buffer that automatically resizes.
/// </summary>
internal class ByteBuffer
{
/// <summary>
/// How many more bytes may fit into the buffer.
/// </summary>
internal int Remaining => (Size - Length);
/// <summary>
/// Buffer data.
/// </summary>
internal byte[] Data { get; private set; }
/// <summary>
/// How many bytes currently into Data. This will include the reserve.
/// </summary>
internal int Length { get; private set; }
/// <summary>
/// Size of the buffer. Data.Length may exceed this value as it uses a pooled array.
/// </summary>
internal int Size { get; private set; }
/// <summary>
/// True if data has been written.
/// </summary>
internal bool HasData { get; private set; }
/// <summary>
/// Bytes to reserve when resetting.
/// </summary>
private int _reserve;
internal ByteBuffer(int size, int reserve = 0)
{
Data = ByteArrayPool.Retrieve(size);
Size = size;
_reserve = reserve;
Reset();
}
public void Dispose()
{
if (Data != null)
ByteArrayPool.Store(Data);
Data = null;
}
/// <summary>
/// Resets instance without clearing Data.
/// </summary>
internal void Reset()
{
Length = _reserve;
HasData = false;
}
/// <summary>
/// Copies segments without error checking, including tick for the first time data is added.
/// </summary>
/// <param name="segment"></param>
internal void CopySegment(uint tick, ArraySegment<byte> segment)
{
/* If data has not been written to buffer yet
* then write tick to the start. */
if (!HasData)
{
int pos = 0;
WriterExtensions.WriteUInt32(Data, tick, ref pos);
}
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
Length += segment.Count;
HasData = true;
}
/// <summary>
/// Copies segments without error checking.
/// </summary>
/// <param name="segment"></param>
internal void CopySegment(ArraySegment<byte> segment)
{
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
Length += segment.Count;
HasData = true;
}
}
internal class PacketBundle
{
/// <summary>
/// True if data has been written.
/// </summary>
internal bool HasData => _buffers[0].HasData;
/// <summary>
/// All buffers written. Collection is not cleared when reset but rather the index in which to write is.
/// </summary>
private List<ByteBuffer> _buffers = new List<ByteBuffer>();
/// <summary>
/// Buffer which is being written to.
/// </summary>
private int _bufferIndex;
/// <summary>
/// Maximum size packet the transport can handle.
/// </summary>
private int _maximumTransportUnit;
/// <summary>
/// Number of buffers written to. Will return 0 if nothing has been written.
/// </summary>
public int WrittenBuffers => (!HasData) ? 0 : (_bufferIndex + 1);
/// <summary>
/// Number of bytes to reserve at the beginning of each buffer.
/// </summary>
private int _reserve;
/// <summary>
/// NetworkManager this is for.
/// </summary>
private NetworkManager _networkManager;
internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0)
{
//Allow bytes for the tick.
reserve += TransportManager.TICK_BYTES;
_networkManager = manager;
_maximumTransportUnit = mtu;
_reserve = reserve;
AddBuffer();
Reset();
}
public void Dispose()
{
for (int i = 0; i < _buffers.Count; i++)
_buffers[i].Dispose();
}
/// <summary>
/// Adds a buffer using current settings.
/// </summary>
private ByteBuffer AddBuffer()
{
ByteBuffer ba = new ByteBuffer(_maximumTransportUnit, _reserve);
_buffers.Add(ba);
return ba;
}
/// <summary>
/// Resets using current settings.
/// </summary>
internal void Reset()
{
_bufferIndex = 0;
for (int i = 0; i < _buffers.Count; i++)
_buffers[i].Reset();
}
/// <summary>
/// Writes a segment to this packet bundle using the current WriteIndex.
/// </summary>
/// <param name="forceNewBuffer">True to force data into a new buffer.</param>
internal void Write(ArraySegment<byte> segment, bool forceNewBuffer = false)
{
//Nothing to be written.
if (segment.Count == 0)
return;
/* If the segment count is larger than the mtu then
* something went wrong. Nothing should call this method
* directly except the TransportManager, which will automatically
* split packets that exceed MTU into reliable ordered. */
if (segment.Count > _maximumTransportUnit)
{
_networkManager.LogError($"Segment is length of {segment.Count} while MTU is {_maximumTransportUnit}. Packet was not split properly and will not be sent.");
return;
}
ByteBuffer ba = _buffers[_bufferIndex];
/* Make a new buffer if...
* forcing a new buffer and data has already been written to the current.
* or---
* segment.Count is more than what is remaining in the buffer. */
bool useNewBuffer = (forceNewBuffer && ba.Length > _reserve) ||
(segment.Count > ba.Remaining);
if (useNewBuffer)
{
_bufferIndex++;
//If need to make a new buffer then do so.
if (_buffers.Count <= _bufferIndex)
{
ba = AddBuffer();
}
else
{
ba = _buffers[_bufferIndex];
ba.Reset();
}
}
uint tick = _networkManager.TimeManager.LocalTick;
ba.CopySegment(tick, segment);
}
/// <summary>
/// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found.
/// </summary>
/// <param name="index">Index of the buffer to retrieve.</param>
/// <param name="bb">Buffer retrieved from the list. Null if the specified buffer was not found.</param>
internal bool GetBuffer(int index, out ByteBuffer bb)
{
bb = null;
if (index >= _buffers.Count || index < 0)
{
_networkManager.LogError($"Index of {index} is out of bounds. There are {_buffers.Count} available.");
return false;
}
if (index > _bufferIndex)
{
_networkManager.LogError($"Index of {index} exceeds the number of written buffers. There are {WrittenBuffers} written buffers.");
return false;
}
bb = _buffers[index];
return bb.HasData;
}
/// <summary>
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
/// </summary>
/// <param name="channel"></param>
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
internal static bool GetPacketBundle(int channel, List<PacketBundle> bundles, out PacketBundle mtuBuffer)
{
//Out of bounds.
if (channel >= bundles.Count)
{
mtuBuffer = null;
return false;
}
mtuBuffer = bundles[channel];
return mtuBuffer.HasData;
}
}
}

View File

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

View File

@ -0,0 +1,110 @@
using FishNet.Broadcast;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Transporting;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Connection
{
public partial class NetworkConnection
{
#region Private.
/// <summary>
/// PacketBundles to send to this connection. An entry will be made for each channel.
/// </summary>
private List<PacketBundle> _toClientBundles = new List<PacketBundle>();
/// <summary>
/// True if this object has been dirtied.
/// </summary>
private bool _serverDirtied;
#endregion
/// <summary>
/// Initializes this script.
/// </summary>
private void InitializeBuffer()
{
for (byte i = 0; i < TransportManager.CHANNEL_COUNT; i++)
{
int mtu = NetworkManager.TransportManager.GetLowestMTU(i);
_toClientBundles.Add(new PacketBundle(NetworkManager, mtu));
}
}
/// <summary>
/// Sends a broadcast to this connection.
/// </summary>
/// <typeparam name="T">Type of broadcast to send.</typeparam>
/// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
/// <param name="requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
/// <param name="channel">Channel to send on.</param>
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
{
if (!IsActive)
NetworkManager.LogError($"Connection is not valid, cannot send broadcast.");
else
NetworkManager.ServerManager.Broadcast<T>(this, message, requireAuthenticated, channel);
}
/// <summary>
/// Sends data from the server to a client.
/// </summary>
/// <param name="forceNewBuffer">True to force data into a new buffer.</param>
internal void SendToClient(byte channel, ArraySegment<byte> segment, bool forceNewBuffer = false)
{
//Cannot send data when disconnecting.
if (Disconnecting)
return;
if (!IsActive)
{
NetworkManager.LogWarning($"Data cannot be sent to connection {ClientId} because it is not active.");
return;
}
//If channel is out of bounds then default to the first channel.
if (channel >= _toClientBundles.Count)
channel = 0;
_toClientBundles[channel].Write(segment, forceNewBuffer);
ServerDirty();
}
/// <summary>
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
/// </summary>
/// <param name="channel"></param>
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
internal bool GetPacketBundle(int channel, out PacketBundle packetBundle)
{
return PacketBundle.GetPacketBundle(channel, _toClientBundles, out packetBundle);
}
/// <summary>
/// Indicates the server has data to send to this connection.
/// </summary>
private void ServerDirty()
{
bool wasDirty = _serverDirtied;
_serverDirtied = true;
//If not yet dirty then tell transport manager this is dirty.
if (!wasDirty)
NetworkManager.TransportManager.ServerDirty(this);
}
/// <summary>
/// Resets that there is data to send.
/// </summary>
internal void ResetServerDirty()
{
_serverDirtied = false;
}
}
}

View File

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

View File

@ -0,0 +1,48 @@
using FishNet.Object;
using System;
using System.Collections.Generic;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection : IEquatable<NetworkConnection>
{
/// <summary>
/// Level of detail for each NetworkObject.
/// Since this is called frequently this field is intentionally not an accessor to increase performance.
/// </summary>
public Dictionary<NetworkObject, byte> LevelOfDetails = new Dictionary<NetworkObject, byte>();
/// <summary>
/// Number oftimes this connection may send a forced LOD update.
/// </summary>
internal int AllowedForcedLodUpdates;
/// <summary>
/// Last tick an LOD was sent.
/// On client and clientHost this is LocalTick.
/// On server only this is LastPacketTick for the connection.
/// </summary>
internal uint LastLevelOfDetailUpdate;
/// <summary>
/// Returns if the client has not sent an LOD update for expectedInterval.
/// </summary>
/// <returns></returns>
internal bool IsLateForLevelOfDetail(uint expectedInterval)
{
//Local client is immune since server and client share ticks.
if (IsLocalClient)
return false;
return ((LastPacketTick - LastLevelOfDetailUpdate) > expectedInterval);
}
/// <summary>
/// Number of level of detail update infractions for this connection.
/// </summary>
internal int LevelOfDetailInfractions;
}
}

View File

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

View File

@ -0,0 +1,103 @@
using FishNet.Managing;
using FishNet.Managing.Timing;
using System;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection : IEquatable<NetworkConnection>
{
#pragma warning disable CS0414
#region Private.
/// <summary>
/// Last tick this connection sent a ping.
/// </summary>
private uint _lastPingTick;
/// <summary>
/// Number of times client has excessively sent a ping.
/// </summary>
private float _excessivePingCount;
/// <summary>
/// Ticks expected between each ping.
/// </summary>
private uint _requiredPingTicks;
#endregion
#region Const.
/// <summary>
/// Number of times a ping may occur excessively before server will punish connection.
/// </summary>
private const byte EXCESSIVE_PING_LIMIT = 10;
#endregion
#pragma warning restore CS0414
/// <summary>
/// Initializes for ping.
/// </summary>
private void InitializePing()
{
//Give the client some room for error.
float requiredInterval = (NetworkManager.TimeManager.PingInterval * 0.85f);
//Round down so required ticks is lower.
_requiredPingTicks = NetworkManager.TimeManager.TimeToTicks(requiredInterval, TickRounding.RoundDown);
}
/// <summary>
/// Resets PingPong values.
/// </summary>
private void ResetPingPong()
{
_excessivePingCount = 0;
_lastPingTick = 0;
}
/// <summary>
/// Called when a ping is received from this connection. Returns if can respond to ping.
/// </summary>
/// <returns>True to respond to ping, false to kick connection.</returns>
internal bool CanPingPong()
{
/* Only check ping conditions in build. Editors are prone to pausing which can
* improperly kick clients. */
#if UNITY_EDITOR
return true;
#else
TimeManager tm = (NetworkManager == null) ? InstanceFinder.TimeManager : NetworkManager.TimeManager;
//Server FPS is running low, timing isn't reliable enough to kick clients.
if (tm.LowFrameRate)
return true;
uint currentTick = tm.Tick;
uint difference = (currentTick - _lastPingTick);
_lastPingTick = currentTick;
//Ping sent too quickly.
if (difference < _requiredPingTicks)
{
_excessivePingCount += 1f;
//Ping limit hit.
if (_excessivePingCount >= EXCESSIVE_PING_LIMIT)
{
NetworkManager.LogWarning($"Kicked connectionId {ClientId} for excessive pings.");
Disconnect(true);
}
//Return to not send pong back.
return false;
}
//Ping isnt too fast.
else
{
_excessivePingCount = UnityEngine.Mathf.Max(0f, _excessivePingCount - 0.5f);
return true;
}
#endif
}
}
}

View File

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

View File

@ -0,0 +1,69 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Serializing;
using System;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection : IEquatable<NetworkConnection>
{
#region Public.
/// <summary>
/// Returns true if this connection is a clientHost.
/// </summary>
public bool IsHost => (NetworkManager == null) ? false : (NetworkManager.IsServer && (this == NetworkManager.ClientManager.Connection));
/// <summary>
/// Returns if this connection is for the local client.
/// </summary>
public bool IsLocalClient => (NetworkManager == null) ? false : (NetworkManager.ClientManager.Connection == this);
#endregion
/// <summary>
/// Returns the address of this connection.
/// </summary>
/// <returns></returns>
public string GetAddress()
{
if (!IsValid)
return string.Empty;
if (NetworkManager == null)
return string.Empty;
return NetworkManager.TransportManager.Transport.GetConnectionAddress(ClientId);
}
/// <summary>
/// Kicks a connection immediately while invoking OnClientKick.
/// </summary>
/// <param name="kickReason">Reason client is being kicked.</param>
/// <param name="loggingType">How to print logging as.</param>
/// <param name="log">Optional message to be debug logged.</param>
public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
{
NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log);
}
/// <summary>
/// Kicks a connection immediately while invoking OnClientKick.
/// </summary>
/// <param name="reader">Reader to clear before kicking.</param>
/// <param name="kickReason">Reason client is being kicked.</param>
/// <param name="loggingType">How to print logging as.</param>
/// <param name="log">Optional message to be debug logged.</param>
public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
{
NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log);
}
}
}

View File

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

View File

@ -0,0 +1,404 @@
using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Object;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.SceneManagement;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection : IEquatable<NetworkConnection>
{
#region Public.
/// <summary>
/// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
/// </summary>
public event Action<NetworkConnection, bool> OnLoadedStartScenes;
/// <summary>
/// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
/// </summary>
public event Action<NetworkObject> OnObjectAdded;
/// <summary>
/// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
/// </summary>
public event Action<NetworkObject> OnObjectRemoved;
/// <summary>
/// NetworkManager managing this class.
/// </summary>
public NetworkManager NetworkManager { get; private set; }
/// <summary>
/// True if connection has loaded start scenes. Available to this connection and server.
/// </summary>
public bool LoadedStartScenes() => (_loadedStartScenesAsServer || _loadedStartScenesAsClient);
/// <summary>
///
/// </summary>
public bool LoadedStartScenes(bool asServer)
{
if (asServer)
return _loadedStartScenesAsServer;
else
return _loadedStartScenesAsClient;
}
/// <summary>
/// True if loaded start scenes as server.
/// </summary>
private bool _loadedStartScenesAsServer;
/// <summary>
/// True if loaded start scenes as client.
/// </summary>
private bool _loadedStartScenesAsClient;
/// <summary>
/// ObjectIds to use for predicted spawning.
/// </summary>
internal Queue<int> PredictedObjectIds = new Queue<int>();
/// <summary>
/// True if this connection is authenticated. Only available to server.
/// </summary>
public bool Authenticated { get; private set; }
/// <summary>
/// True if this connection IsValid and not Disconnecting.
/// </summary>
public bool IsActive => (ClientId >= 0 && !Disconnecting);
/// <summary>
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
/// </summary>
public bool IsValid => (ClientId >= 0);
/// <summary>
/// Unique Id for this connection.
/// </summary>
public int ClientId = -1;
/// <summary>
///
/// </summary>
private HashSet<NetworkObject> _objects = new HashSet<NetworkObject>();
/// <summary>
/// Objects owned by this connection. Available to this connection and server.
/// </summary>
public IReadOnlyCollection<NetworkObject> Objects => _objects;
/// <summary>
/// The first object within Objects.
/// </summary>
public NetworkObject FirstObject { get; private set; }
/// <summary>
/// Scenes this connection is in. Available to this connection and server.
/// </summary>
public HashSet<Scene> Scenes { get; private set; } = new HashSet<Scene>();
/// <summary>
/// True if this connection is being disconnected. Only available to server.
/// </summary>
public bool Disconnecting { get; private set; }
/// <summary>
/// Tick when Disconnecting was set.
/// </summary>
internal uint DisconnectingTick { get; private set; }
/// <summary>
/// Custom data associated with this connection which may be modified by the user.
/// The value of this field are not synchronized over the network.
/// </summary>
public object CustomData = null;
/// <summary>
/// Local tick when the connection last replicated.
/// </summary>
public uint LocalReplicateTick { get; internal set; }
/// <summary>
/// Tick of the last packet received from this connection.
/// This value is only available on the server.
/// </summary>
/* This is not used internally. At this time it's just
* here for the users convienence. */
public uint LastPacketTick { get; private set; }
/// <summary>
/// Sets LastPacketTick value.
/// </summary>
/// <param name="value"></param>
internal void SetLastPacketTick(uint value)
{
//If new largest tick from the client then update client tick data.
if (value > LastPacketTick)
{
_latestTick = value;
_serverLatestTick = NetworkManager.TimeManager.Tick;
}
LastPacketTick = value;
}
/// <summary>
/// Latest tick that did not arrive out of order from this connection.
/// </summary>
private uint _latestTick;
/// <summary>
/// Tick on the server when latestTick was set.
/// </summary>
private uint _serverLatestTick;
[Obsolete("Use LocalTick instead.")] //Remove on 2023/06/01
public uint Tick => LocalTick;
/// <summary>
/// Current approximate local tick as it is on this connection.
/// </summary>
public uint LocalTick
{
get
{
NetworkManager nm = NetworkManager;
if (nm != null)
{
uint diff = (nm.TimeManager.Tick - _serverLatestTick);
return (diff + _latestTick);
}
//Fall through, could not process.
return 0;
}
}
#endregion
#region Const.
/// <summary>
/// Value used when ClientId has not been set.
/// </summary>
public const int UNSET_CLIENTID_VALUE = -1;
#endregion
#region Comparers.
public override bool Equals(object obj)
{
if (obj is NetworkConnection nc)
return (nc.ClientId == this.ClientId);
else
return false;
}
public bool Equals(NetworkConnection nc)
{
if (nc is null)
return false;
//If either is -1 Id.
if (this.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE || nc.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE)
return false;
//Same object.
if (System.Object.ReferenceEquals(this, nc))
return true;
return (this.ClientId == nc.ClientId);
}
public override int GetHashCode()
{
return ClientId;
}
public static bool operator ==(NetworkConnection a, NetworkConnection b)
{
if (a is null && b is null)
return true;
if (a is null && !(b is null))
return false;
return (b == null) ? a.Equals(b) : b.Equals(a);
}
public static bool operator !=(NetworkConnection a, NetworkConnection b)
{
return !(a == b);
}
#endregion
[APIExclude]
public NetworkConnection() { }
[APIExclude]
public NetworkConnection(NetworkManager manager, int clientId, bool asServer)
{
Initialize(manager, clientId, asServer);
}
public void Dispose()
{
foreach (PacketBundle p in _toClientBundles)
p.Dispose();
_toClientBundles.Clear();
}
/// <summary>
/// Initializes this for use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Initialize(NetworkManager nm, int clientId, bool asServer)
{
NetworkManager = nm;
ClientId = clientId;
//Only the server uses the ping and buffer.
if (asServer)
{
InitializeBuffer();
InitializePing();
}
}
/// <summary>
/// Resets this instance.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset()
{
_latestTick = 0;
_serverLatestTick = 0;
LastPacketTick = 0;
ClientId = -1;
ClearObjects();
Authenticated = false;
NetworkManager = null;
_loadedStartScenesAsClient = false;
_loadedStartScenesAsServer = false;
SetDisconnecting(false);
Scenes.Clear();
PredictedObjectIds.Clear();
ResetPingPong();
LevelOfDetails.Clear();
AllowedForcedLodUpdates = 0;
LastLevelOfDetailUpdate = 0;
LevelOfDetailInfractions = 0;
}
/// <summary>
/// Sets Disconnecting boolean for this connection.
/// </summary>
internal void SetDisconnecting(bool value)
{
Disconnecting = value;
if (Disconnecting)
DisconnectingTick = NetworkManager.TimeManager.LocalTick;
}
/// <summary>
/// Disconnects this connection.
/// </summary>
/// <param name="immediately">True to disconnect immediately. False to send any pending data first.</param>
public void Disconnect(bool immediately)
{
if (Disconnecting)
{
NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
return;
}
SetDisconnecting(true);
//If immediately then force disconnect through transport.
if (immediately)
NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
//Otherwise mark dirty so server will push out any pending information, and then disconnect.
else
ServerDirty();
}
/// <summary>
/// Returns if just loaded start scenes and sets them as loaded if not.
/// </summary>
/// <returns></returns>
internal bool SetLoadedStartScenes(bool asServer)
{
bool loadedToCheck = (asServer) ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
//Result becomes true if not yet loaded start scenes.
bool result = !loadedToCheck;
if (asServer)
_loadedStartScenesAsServer = true;
else
_loadedStartScenesAsClient = true;
OnLoadedStartScenes?.Invoke(this, asServer);
return result;
}
/// <summary>
/// Sets connection as authenticated.
/// </summary>
internal void ConnectionAuthenticated()
{
Authenticated = true;
}
/// <summary>
/// Adds to Objects owned by this connection.
/// </summary>
/// <param name="nob"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddObject(NetworkObject nob)
{
_objects.Add(nob);
//If adding the first object then set new FirstObject.
if (_objects.Count == 1)
FirstObject = nob;
OnObjectAdded?.Invoke(nob);
}
/// <summary>
/// Removes from Objects owned by this connection.
/// </summary>
/// <param name="nob"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RemoveObject(NetworkObject nob)
{
_objects.Remove(nob);
//If removing the first object then set a new one.
if (nob == FirstObject)
SetFirstObject();
OnObjectRemoved?.Invoke(nob);
}
/// <summary>
/// Clears all Objects.
/// </summary>
private void ClearObjects()
{
_objects.Clear();
FirstObject = null;
}
/// <summary>
/// Sets FirstObject using the first element in Objects.
/// </summary>
private void SetFirstObject()
{
if (_objects.Count == 0)
{
FirstObject = null;
}
else
{
foreach (NetworkObject nob in Objects)
{
FirstObject = nob;
break;
}
}
}
/// <summary>
/// Adds a scene to this connections Scenes.
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
internal bool AddToScene(Scene scene)
{
return Scenes.Add(scene);
}
/// <summary>
/// Removes a scene to this connections Scenes.
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
internal bool RemoveFromScene(Scene scene)
{
return Scenes.Remove(scene);
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,8 @@

using System;
namespace FishNet.Documenting
{
public class APIExcludeAttribute : Attribute { }
}

View File

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

View File

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

View File

@ -0,0 +1,118 @@

using FishNet.Configuring;
using System.IO;
using UnityEngine;
using System.Xml.Serialization;
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
#endif
namespace FishNet.Configuring
{
public class CodeStripping
{
/// <summary>
/// True if making a release build for client.
/// </summary>
public static bool ReleasingForClient => (Configuration.Configurations.CodeStripping.IsBuilding && !Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment);
/// <summary>
/// True if making a release build for server.
/// </summary>
public static bool ReleasingForServer => (Configuration.Configurations.CodeStripping.IsBuilding && Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment);
/// <summary>
/// Returns if to remove server logic.
/// </summary>
/// <returns></returns>
public static bool RemoveServerLogic
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// True if building and stripping is enabled.
/// </summary>
public static bool StripBuild
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// Technique to strip methods.
/// </summary>
public static StrippingTypes StrippingType => (StrippingTypes)Configuration.Configurations.CodeStripping.StrippingType;
private static object _compilationContext;
public int callbackOrder => 0;
#if UNITY_EDITOR
public void OnPreprocessBuild(BuildReport report)
{
Generator.IgnorePostProcess = true;
Generator.GenerateFull();
CompilationPipeline.compilationStarted += CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished += CompilationPipelineOnCompilationFinished;
}
/* Solution for builds ending with errors and not triggering OnPostprocessBuild.
* Link: https://gamedev.stackexchange.com/questions/181611/custom-build-failure-callback
*/
private void CompilationPipelineOnCompilationStarted(object compilationContext)
{
_compilationContext = compilationContext;
}
private void CompilationPipelineOnCompilationFinished(object compilationContext)
{
if (compilationContext != _compilationContext)
return;
_compilationContext = null;
CompilationPipeline.compilationStarted -= CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished -= CompilationPipelineOnCompilationFinished;
BuildingEnded();
}
private void BuildingEnded()
{
Generator.IgnorePostProcess = false;
}
public void OnPostprocessBuild(BuildReport report)
{
BuildingEnded();
}
#endif
}
}

View File

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

View File

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

View File

@ -0,0 +1,146 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace FishNet.Configuring
{
public enum StrippingTypes : int
{
Redirect = 0,
Empty_Experimental = 1,
}
public enum SearchScopeType : int
{
EntireProject = 0,
SpecificFolders = 1,
}
public class PrefabGeneratorConfigurations
{
public bool Enabled = true;
public bool LogToConsole = true;
public bool FullRebuild = false;
public bool SaveChanges = true;
public string DefaultPrefabObjectsPath = Path.Combine("Assets", "DefaultPrefabObjects.asset");
public int SearchScope = (int)SearchScopeType.EntireProject;
public List<string> ExcludedFolders = new List<string>();
public List<string> IncludedFolders = new List<string>();
}
public class CodeStrippingConfigurations
{
public bool IsBuilding = false;
public bool IsDevelopment = false;
public bool IsHeadless = false;
public bool StripReleaseBuilds = false;
public int StrippingType = (int)StrippingTypes.Redirect;
}
public class ConfigurationData
{
//Non serialized doesn't really do anything, its just for me.
[System.NonSerialized]
public bool Loaded;
public PrefabGeneratorConfigurations PrefabGenerator = new PrefabGeneratorConfigurations();
public CodeStrippingConfigurations CodeStripping = new CodeStrippingConfigurations();
}
public static class ConfigurationDataExtension
{
/// <summary>
/// Returns if a differs from b.
/// </summary>
public static bool HasChanged(this ConfigurationData a, ConfigurationData b)
{
return (a.CodeStripping.StripReleaseBuilds != b.CodeStripping.StripReleaseBuilds);
}
/// <summary>
/// Copies all values from source to target.
/// </summary>
public static void CopyTo(this ConfigurationData source, ConfigurationData target)
{
target.CodeStripping.StripReleaseBuilds = source.CodeStripping.StripReleaseBuilds;
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
string path = Configuration.GetAssetsPath(Configuration.CONFIG_FILE_NAME);
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new Exception($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, string path, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new Exception($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
}
}

View File

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

View File

@ -0,0 +1,126 @@
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using FishNet.Object;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public class ConfigurationEditor : EditorWindow
{
[MenuItem("Fish-Networking/Configuration", false, 0)]
public static void ShowConfiguration()
{
SettingsService.OpenProjectSettings("Project/Fish-Networking/Configuration");
}
}
public class RebuildSceneIdMenu : MonoBehaviour
{
/// <summary>
/// Rebuilds sceneIds for open scenes.
/// </summary>
[MenuItem("Fish-Networking/Rebuild SceneIds", false, 20)]
public static void RebuildSceneIds()
{
int generatedCount = 0;
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene s = SceneManager.GetSceneAt(i);
ListCache<NetworkObject> nobs;
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
for (int z = 0; z < nobs.Written; z++)
{
NetworkObject nob = nobs.Collection[z];
nob.TryCreateSceneID();
EditorUtility.SetDirty(nob);
}
generatedCount += nobs.Written;
ListCaches.StoreCache(nobs);
}
Debug.Log($"Generated sceneIds for {generatedCount} objects over {SceneManager.sceneCount} scenes. Please save your open scenes.");
}
}
public class RefreshDefaultPrefabsMenu : MonoBehaviour
{
/// <summary>
/// Rebuilds the DefaultPrefabsCollection file.
/// </summary>
[MenuItem("Fish-Networking/Refresh Default Prefabs", false, 22)]
public static void RebuildDefaultPrefabs()
{
Debug.Log("Refreshing default prefabs.");
Generator.GenerateFull(null, true);
}
}
public class RemoveDuplicateNetworkObjectsMenu : MonoBehaviour
{
/// <summary>
/// Iterates all network object prefabs in the project and open scenes, removing NetworkObject components which exist multiple times on a single object.
/// </summary>
[MenuItem("Fish-Networking/Remove Duplicate NetworkObjects", false, 21)]
public static void RemoveDuplicateNetworkObjects()
{
List<NetworkObject> foundNobs = new List<NetworkObject>();
foreach (string path in Generator.GetPrefabFiles("Assets", new HashSet<string>(), true))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
//Now add scene objects.
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene s = SceneManager.GetSceneAt(i);
ListCache<NetworkObject> nobs;
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
for (int z = 0; z < nobs.Written; z++)
{
NetworkObject nob = nobs.Collection[z];
nob.TryCreateSceneID();
EditorUtility.SetDirty(nob);
}
for (int z = 0; z < nobs.Written; z++)
foundNobs.Add(nobs.Collection[i]);
ListCaches.StoreCache(nobs);
}
//Remove duplicates.
int removed = 0;
foreach (NetworkObject nob in foundNobs)
{
int count = nob.RemoveDuplicateNetworkObjects();
if (count > 0)
removed += count;
}
Debug.Log($"Removed {removed} duplicate NetworkObjects. Please save your open scenes and project.");
}
}
}
#endif

View File

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

View File

@ -0,0 +1,102 @@
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
#endif
namespace FishNet.Configuring
{
public class Configuration
{
/// <summary>
///
/// </summary>
private static ConfigurationData _configurations;
/// <summary>
/// ConfigurationData to use.
/// </summary>
public static ConfigurationData Configurations
{
get
{
if (_configurations == null)
_configurations = LoadConfigurationData();
if (_configurations == null)
throw new System.Exception("Fish-Networking Configurations could not be loaded. Certain features such as code-stripping may not function.");
return _configurations;
}
private set
{
_configurations = value;
}
}
/// <summary>
/// File name for configuration disk data.
/// </summary>
public const string CONFIG_FILE_NAME = "FishNet.Config.XML";
/// <summary>
/// Returns the path for the configuration file.
/// </summary>
/// <returns></returns>
internal static string GetAssetsPath(string additional = "")
{
string a = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Assets");
if (additional != "")
a = Path.Combine(a, additional);
return a;
}
/// <summary>
/// Returns FishNetworking ConfigurationData.
/// </summary>
/// <returns></returns>
internal static ConfigurationData LoadConfigurationData()
{
//return new ConfigurationData();
if (_configurations == null || !_configurations.Loaded)
{
string configPath = GetAssetsPath(CONFIG_FILE_NAME);
//string configPath = string.Empty;
//File is on disk.
if (File.Exists(configPath))
{
FileStream fs = null;
try
{
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
fs = new FileStream(configPath, FileMode.Open, FileAccess.Read, FileShare.Read);
_configurations = (ConfigurationData)serializer.Deserialize(fs);
}
finally
{
fs?.Close();
}
_configurations.Loaded = true;
}
else
{
//If null then make a new instance.
if (_configurations == null)
_configurations = new ConfigurationData();
//Don't unset loaded, if its true then it should have proper info.
//_configurationData.Loaded = false;
}
}
return _configurations;
}
}
}

View File

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

View File

@ -0,0 +1,172 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class FishNetGettingStartedEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string SHOWED_GETTING_STARTED = "ShowedFishNetGettingStarted";
[MenuItem("Fish-Networking/Getting Started")]
public static void GettingStartedMenu()
{
FishNetGettingStartedEditor window = (FishNetGettingStartedEditor)EditorWindow.GetWindow(typeof(FishNetGettingStartedEditor));
window.position = new Rect(0, 0, 320, 355);
Rect mainPos;
#if UNITY_2020_1_OR_NEWER
mainPos = EditorGUIUtility.GetMainWindowPosition();
#else
mainPos = new Rect(Vector2.zero, Vector2.zero);
#endif
var pos = window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
window.position = pos;
window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
window._labelStyle = new GUIStyle("label");
window._labelStyle.fontSize = 24;
window._labelStyle.wordWrap = true;
//window.labelStyle.alignment = TextAnchor.MiddleCenter;
window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
window._reviewButtonStyle = new GUIStyle("button");
window._reviewButtonStyle.fontSize = 18;
window._reviewButtonStyle.fontStyle = FontStyle.Bold;
window._reviewButtonStyle.normal.background = window._reviewButtonBg;
window._reviewButtonStyle.active.background = window._reviewButtonBgHover;
window._reviewButtonStyle.focused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onFocused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.hover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onHover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
window._reviewButtonStyle.normal.textColor = new Color(1, 1, 1, 1);
}
private static bool _subscribed;
[InitializeOnLoadMethod]
private static void Initialize()
{
SubscribeToUpdate();
}
private static void SubscribeToUpdate()
{
if (Application.isBatchMode)
return;
if (!_subscribed && !EditorApplication.isPlayingOrWillChangePlaymode)
{
_subscribed = true;
EditorApplication.update += ShowGettingStarted;
}
}
private static void ShowGettingStarted()
{
EditorApplication.update -= ShowGettingStarted;
bool shown = EditorPrefs.GetBool(SHOWED_GETTING_STARTED, false);
if (!shown)
{
EditorPrefs.SetBool(SHOWED_GETTING_STARTED, true);
ReviewReminderEditor.ResetDateTimeReminded();
GettingStartedMenu();
}
//If was already shown then check review reminder instead.
else
{
ReviewReminderEditor.CheckRemindToReview();
}
}
void OnGUI()
{
GUILayout.Box(_fishnetLogo, GUILayout.Width(this.position.width), GUILayout.Height(128));
GUILayout.Space(20);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(280));
GUILayout.Space(10);
if (GUILayout.Button("Leave us a review!", _reviewButtonStyle))
{
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Documentation", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/");
}
if (GUILayout.Button("Discord", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("FishNet Pro", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/master/pro");
}
if (GUILayout.Button("Github", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://github.com/FirstGearGames/FishNet");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Pro Downloads", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://www.firstgeargames.com/");
}
if (GUILayout.Button("Examples", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/manual/tutorials/example-projects");
}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
//private string[] showOnStartupOptions = new string[] { "Always", "On new version", "Never", };
//private int _showOnStartupSelected = 1;
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new Texture2D(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif

View File

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

View File

@ -0,0 +1,171 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class ReviewReminderEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string DATETIME_REMINDED = "ReviewDateTimeReminded";
private const string CHECK_REMIND_COUNT = "CheckRemindCount";
private const string IS_ENABLED = "ReminderEnabled";
private static ReviewReminderEditor _window;
internal static void CheckRemindToReview()
{
bool reminderEnabled = EditorPrefs.GetBool(IS_ENABLED, true);
if (!reminderEnabled)
return;
/* Require at least two opens and 10 days
* to be passed before reminding. */
int checkRemindCount = (EditorPrefs.GetInt(CHECK_REMIND_COUNT, 0) + 1);
EditorPrefs.SetInt(CHECK_REMIND_COUNT, checkRemindCount);
//Not enough checks.
if (checkRemindCount < 2)
return;
string dtStr = EditorPrefs.GetString(DATETIME_REMINDED, string.Empty);
//Somehow got cleared. Reset.
if (string.IsNullOrWhiteSpace(dtStr))
{
ResetDateTimeReminded();
return;
}
long binary;
//Failed to parse.
if (!long.TryParse(dtStr, out binary))
{
ResetDateTimeReminded();
return;
}
//Not enough time passed.
DateTime dt = DateTime.FromBinary(binary);
if ((DateTime.Now - dt).TotalDays < 10)
return;
//If here then the reminder can be shown.
EditorPrefs.SetInt(CHECK_REMIND_COUNT, 0);
ShowReminder();
}
internal static void ResetDateTimeReminded()
{
EditorPrefs.SetString(DATETIME_REMINDED, DateTime.Now.ToBinary().ToString());
}
private static void ShowReminder()
{
InitializeWindow();
}
static void InitializeWindow()
{
if (_window != null)
return;
_window = (ReviewReminderEditor)EditorWindow.GetWindow(typeof(ReviewReminderEditor));
_window.position = new Rect(0f, 0f, 320f, 300f);
Rect mainPos;
#if UNITY_2020_1_OR_NEWER
mainPos = EditorGUIUtility.GetMainWindowPosition();
#else
mainPos = new Rect(Vector2.zero, Vector2.zero);
#endif
var pos = _window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
_window.position = pos;
}
static void StyleWindow()
{
InitializeWindow();
_window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
_window._labelStyle = new GUIStyle("label");
_window._labelStyle.fontSize = 24;
_window._labelStyle.wordWrap = true;
//window.labelStyle.alignment = TextAnchor.MiddleCenter;
_window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
_window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
_window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
_window._reviewButtonStyle = new GUIStyle("button");
_window._reviewButtonStyle.fontSize = 18;
_window._reviewButtonStyle.fontStyle = FontStyle.Bold;
_window._reviewButtonStyle.normal.background = _window._reviewButtonBg;
_window._reviewButtonStyle.active.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.focused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onFocused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.hover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onHover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
_window._reviewButtonStyle.normal.textColor = new Color(1, 1, 1, 1);
}
void OnGUI()
{
float thisWidth = this.position.width;
StyleWindow();
GUILayout.Box(_fishnetLogo, GUILayout.Width(this.position.width), GUILayout.Height(160f));
EditorGUILayout.BeginHorizontal();
GUILayout.Space(8f);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(thisWidth * 0.95f));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Don't Ask Again", GUILayout.Width(this.position.width)))
{
this.Close();
EditorPrefs.SetBool(IS_ENABLED, false);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Ask Later", GUILayout.Width(this.position.width)))
{
this.Close();
Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Leave A Review", GUILayout.Width(this.position.width)))
{
this.Close();
EditorPrefs.SetBool(IS_ENABLED, false);
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new Texture2D(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif

View File

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

View File

@ -0,0 +1,86 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using FishNet.Configuring;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using System.Collections.Generic;
namespace FishNet.Configuring.Editing
{
internal static class SettingsProvider
{
private static Vector2 _scrollView;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new UnitySettingsProvider("Project/Fish-Networking/Configuration", SettingsScope.Project)
{
label = "Configuration",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Configuration",
},
};
}
private static void OnGUI(string searchContext)
{
ConfigurationData configuration = Configuration.LoadConfigurationData();
if (configuration == null)
{
EditorGUILayout.HelpBox("Unable to load configuration data.", MessageType.Error);
return;
}
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new GUIStyle()
{
padding = new RectOffset(10, 10, 10, 10),
};
_scrollView = GUILayout.BeginScrollView(_scrollView, scrollViewStyle);
EditorGUILayout.BeginHorizontal();
GUIStyle toggleStyle = new GUIStyle(EditorStyles.toggle)
{
richText = true,
};
configuration.CodeStripping.StripReleaseBuilds = GUILayout.Toggle(configuration.CodeStripping.StripReleaseBuilds, $"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StripReleaseBuilds))} <color=yellow>(Pro Only)</color>", toggleStyle);
EditorGUILayout.EndHorizontal();
if (configuration.CodeStripping.StripReleaseBuilds)
{
EditorGUI.indentLevel++;
//Stripping Method.
List<string> enumStrings = new List<string>();
foreach (string item in System.Enum.GetNames(typeof(StrippingTypes)))
enumStrings.Add(item);
configuration.CodeStripping.StrippingType = EditorGUILayout.Popup($"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StrippingType))}", (int)configuration.CodeStripping.StrippingType, enumStrings.ToArray());
EditorGUILayout.HelpBox("Development builds will not have code stripped. Additionally, if you plan to run as host disable code stripping.", MessageType.Warning);
EditorGUI.indentLevel--;
}
GUILayout.EndScrollView();
if (EditorGUI.EndChangeCheck()) Configuration.Configurations.Write(true);
}
}
}
#endif

View File

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

View File

@ -0,0 +1,13 @@
using FishNet.Documenting;
namespace FishNet.Editing
{
[APIExclude]
public static class EditingConstants
{
public const string PRO_ASSETS_LOCKED_TEXT = "Fields marked with * are only active with Fish-Networking Pro.";
public const string PRO_ASSETS_UNLOCKED_TEXT = "Thank you for supporting Fish-Networking! Pro asset features are unlocked.";
}
}

View File

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

View File

@ -0,0 +1,126 @@
//#if UNITY_EDITOR
//using FishNet.Managing.Object;
//using FishNet.Object;
//using System.Collections.Generic;
//using System.IO;
//using UnityEditor;
//using UnityEngine;
//namespace FishNet.Editing
//{
// [InitializeOnLoad]
// internal static class DefaultPrefabsFinder
// {
// /// <summary>
// /// True if initialized.
// /// </summary>
// [System.NonSerialized]
// private static bool _initialized;
// /// <summary>
// /// Found default prefabs.
// /// </summary>
// private static DefaultPrefabObjects _defaultPrefabs;
// static DefaultPrefabsFinder()
// {
// EditorApplication.update += InitializeOnce;
// }
// /// <summary>
// /// Finds and sets the default prefabs reference.
// /// </summary>
// internal static DefaultPrefabObjects GetDefaultPrefabsFile(out bool justPopulated)
// {
// if (_defaultPrefabs == null)
// {
// List<UnityEngine.Object> results = Finding.GetScriptableObjects<DefaultPrefabObjects>(true, true);
// if (results.Count > 0)
// _defaultPrefabs = (DefaultPrefabObjects)results[0];
// }
// justPopulated = false;
// //If not found then try to create file.
// if (_defaultPrefabs == null)
// {
// if (DefaultPrefabObjects.CanAutomate)
// {
// DefaultPrefabObjects dpo = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
// //Get save directory.
// string savePath = Finding.GetFishNetRuntimePath(true);
// AssetDatabase.CreateAsset(dpo, Path.Combine(savePath, $"{nameof(DefaultPrefabObjects)}.asset"));
// }
// else
// {
// Debug.LogError($"Cannot create DefaultPrefabs because auto create is blocked.");
// }
// }
// //If still null.
// if (_defaultPrefabs == null)
// Debug.LogWarning($"DefaultPrefabObjects not found. Prefabs list will not be automatically populated.");
// else
// justPopulated = PopulateDefaultPrefabs();
// return _defaultPrefabs;
// }
// /// <summary>
// /// Initializes the default prefab.
// /// </summary>
// private static void InitializeOnce()
// {
// if (_initialized)
// return;
// _initialized = true;
// Finding.GetFishNetRuntimePath(false);
// GetDefaultPrefabsFile(out _);
// if (_defaultPrefabs != null)
// {
// //Populate any missing.
// if (_defaultPrefabs.GetObjectCount() == 0)
// PopulateDefaultPrefabs();
// }
// }
// /// <summary>
// /// Finds all NetworkObjects in project and adds them to defaultPrefabs.
// /// </summary>
// /// <returns>True if was populated from assets.</returns>
// internal static bool PopulateDefaultPrefabs(bool log = true, bool clear = false)
// {
// if (_defaultPrefabs == null)
// return false;
// if (!DefaultPrefabObjects.CanAutomate)
// return false;
// if (clear)
// _defaultPrefabs.Clear();
// if (_defaultPrefabs.GetObjectCount() > 0)
// return false;
// List<GameObject> gameObjects = Finding.GetGameObjects(true, true, false);
// foreach (GameObject go in gameObjects)
// {
// if (go.TryGetComponent(out NetworkObject nob))
// _defaultPrefabs.AddObject(nob);
// }
// _defaultPrefabs.Sort();
// int entriesAdded = _defaultPrefabs.GetObjectCount();
// //Only print if some were added.
// if (log && entriesAdded > 0)
// Debug.Log($"Default prefabs was populated with {entriesAdded} prefabs.");
// EditorUtility.SetDirty(_defaultPrefabs);
// return true;
// }
// }
//}
//#endif

View File

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

View File

@ -0,0 +1,216 @@
#if UNITY_EDITOR
using FishNet.Utility.Constant;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public static class Finding
{
#region Private.
/// <summary>
/// Path where the FishNet.Runtime assembly is.
/// </summary>
[System.NonSerialized]
private static string _fishNetRuntimePath = string.Empty;
/// <summary>
/// Path where the FishNet.Generated assembly is.
/// </summary>
private static string _fishNetGeneratedPath = string.Empty;
#endregion
/// <summary>
/// Sets FishNet assembly paths.
/// </summary>
/// <param name="error"></param>
private static void SetPaths(bool error)
{
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
string[] guids = AssetDatabase.FindAssets("t:asmdef", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
string runtimeName = (UtilityConstants.RUNTIME_ASSEMBLY_NAME + ".asmdef").ToLower();
string generatedName = (UtilityConstants.GENERATED_ASSEMBLY_NAME + ".asmdef").ToLower();
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
//Found directory to create object in.
if (item.ToLower().Contains(runtimeName))
_fishNetRuntimePath = System.IO.Path.GetDirectoryName(item);
else if (item.ToLower().Contains(generatedName))
_fishNetGeneratedPath = System.IO.Path.GetDirectoryName(item);
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
}
}
/// <summary>
/// Gets path for where the FishNet.Runtime assembly is.
/// </summary>
/// <returns></returns>
public static string GetFishNetRuntimePath(bool error)
{
SetPaths(error);
return _fishNetRuntimePath;
}
/// <summary>
/// Gets path for where the FishNet.Generated assembly is.
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static string GetFishNetGeneratedPath(bool error)
{
SetPaths(error);
return _fishNetGeneratedPath;
}
/// <summary>
/// Gets all GameObjects in Assets and optionally scenes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<GameObject> GetGameObjects(bool userAssemblies, bool fishNetAssembly, bool includeScenes, string[] ignoredPaths = null)
{
List<GameObject> results = new List<GameObject>();
string[] guids;
string[] objectPaths;
guids = AssetDatabase.FindAssets("t:GameObject", null);
objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
foreach (string item in objectPaths)
{
bool inFishNet = item.Contains(_fishNetRuntimePath);
if (inFishNet && !fishNetAssembly)
continue;
if (!inFishNet && !userAssemblies)
continue;
if (ignoredPaths != null)
{
bool ignore = false;
foreach (string path in ignoredPaths)
{
if (item.Contains(path))
{
ignore = true;
break;
}
}
if (ignore)
continue;
}
GameObject go = (GameObject)AssetDatabase.LoadAssetAtPath(item, typeof(GameObject));
results.Add(go);
}
if (includeScenes)
results.AddRange(GetSceneGameObjects());
return results;
}
/// <summary>
/// Gets all GameObjects in all open scenes.
/// </summary>
/// <returns></returns>
private static List<GameObject> GetSceneGameObjects()
{
List<GameObject> results = new List<GameObject>();
for (int i = 0; i < SceneManager.sceneCount; i++)
results.AddRange(GetSceneGameObjects(SceneManager.GetSceneAt(i)));
return results;
}
/// <summary>
/// Gets all GameObjects in a scene.
/// </summary>
private static List<GameObject> GetSceneGameObjects(Scene s)
{
List<GameObject> results = new List<GameObject>();
List<Transform> buffer = new List<Transform>();
//Iterate all root objects for the scene.
GameObject[] gos = s.GetRootGameObjects();
for (int i = 0; i < gos.Length; i++)
{
/* Get GameObjects within children of each
* root object then add them to the cache. */
gos[i].GetComponentsInChildren<Transform>(true, buffer);
foreach (Transform t in buffer)
results.Add(t.gameObject);
}
return results;
}
/// <summary>
/// Gets created ScriptableObjects of T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<UnityEngine.Object> GetScriptableObjects<T>(bool fishNetAssembly, bool breakOnFirst = false)
{
System.Type tType = typeof(T);
List<UnityEngine.Object> results = new List<UnityEngine.Object>();
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
/* This might be faster than using directory comparers.
* Don't really care since this occurs only at edit. */
List<string> fishNetPaths = new List<string>();
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"\", @"/"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"\", @"/"));
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
//This will skip hidden unity types.
if (!item.EndsWith(".asset"))
continue;
if (fishNetAssembly)
{
bool found = false;
foreach (string path in fishNetPaths)
{
if (item.Contains(path))
{
found = true;
break;
}
}
if (!found)
continue;
}
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(item, tType);
if (obj != null && tType != null && obj.GetType() == tType)
{
results.Add(obj);
if (breakOnFirst)
return results;
}
}
return results;
}
}
}
#endif

View File

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

View File

@ -0,0 +1,51 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
[InitializeOnLoad]
public class PlayModeTracker
{
static PlayModeTracker()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
~PlayModeTracker()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
/// <summary>
/// DateTime when the editor last exited playmode.
/// </summary>
private static DateTime _quitTime = DateTime.MaxValue;
/// <summary>
/// True if the editor has exited playmode within past.
/// </summary>
/// <param name="past"></param>
/// <returns></returns>
internal static bool QuitRecently(float past)
{
past *= 1000;
return ((DateTime.Now - _quitTime).TotalMilliseconds < past);
}
private static void OnPlayModeStateChanged(PlayModeStateChange stateChange)
{
switch (stateChange)
{
case (PlayModeStateChange.ExitingPlayMode):
_quitTime = DateTime.Now;
break;
}
}
}
}
#endif

View File

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

View File

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

View File

@ -0,0 +1,619 @@
#if UNITY_EDITOR
using FishNet.Configuring;
using FishNet.Managing.Object;
using FishNet.Object;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityDebug = UnityEngine.Debug;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal sealed class Generator : AssetPostprocessor
{
public Generator()
{
if (!_subscribed)
{
_subscribed = true;
EditorApplication.update += OnEditorUpdate;
}
}
~Generator()
{
if (_subscribed)
{
_subscribed = false;
EditorApplication.update -= OnEditorUpdate;
}
}
#region Types.
private struct SpecifiedFolder
{
public string Path;
public bool Recursive;
public SpecifiedFolder(string path, bool recursive)
{
Path = path;
Recursive = recursive;
}
}
#endregion
#region Public.
/// <summary>
/// True to ignore post process changes.
/// </summary>
public static bool IgnorePostProcess = false;
#endregion
#region Private.
/// <summary>
/// Last asset to import when there was only one imported asset and no other changes.
/// </summary>
private static string _lastSingleImportedAsset = string.Empty;
/// <summary>
/// Cached DefaultPrefabObjects reference.
/// </summary>
private static DefaultPrefabObjects _cachedDefaultPrefabs;
/// <summary>
/// True to refresh prefabs next update.
/// </summary>
private static bool _retryRefreshDefaultPrefabs;
/// <summary>
/// True if already subscribed to EditorApplication.Update.
/// </summary>
private static bool _subscribed;
/// <summary>
/// True if ran once since editor started.
/// </summary>
[System.NonSerialized]
private static bool _ranOnce;
/// <summary>
/// Last paths of updated nobs during a changed update.
/// </summary>
[System.NonSerialized]
private static List<string> _lastUpdatedNamePaths = new List<string>();
/// <summary>
/// Last frame changed was updated.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedFrame = -1;
/// <summary>
/// Length of assets strings during the last update.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedLengths = -1;
#endregion
public static string[] GetPrefabFiles(string startingPath, HashSet<string> excludedPaths, bool recursive)
{
//Opportunity to exit early if there are no excluded paths.
if (excludedPaths.Count == 0)
{
string[] strResults = Directory.GetFiles(startingPath, "*.prefab", SearchOption.AllDirectories);
return strResults;
}
//starting path is excluded.
if (excludedPaths.Contains(startingPath))
return new string[0];
//Folders remaining to be iterated.
List<string> enumeratedCollection = new List<string>() { startingPath };
//Only check other directories if recursive.
if (recursive)
{
//Find all folders which aren't excluded.
for (int i = 0; i < enumeratedCollection.Count; i++)
{
string[] allFolders = Directory.GetDirectories(enumeratedCollection[i], "*", SearchOption.TopDirectoryOnly);
for (int z = 0; z < allFolders.Length; z++)
{
string current = allFolders[z];
//Not excluded.
if (!excludedPaths.Contains(current))
enumeratedCollection.Add(current);
}
}
}
//Valid prefab files.
List<string> results = new List<string>();
//Build files from folders.
int count = enumeratedCollection.Count;
for (int i = 0; i < count; i++)
{
string[] r = Directory.GetFiles(enumeratedCollection[i], "*.prefab", SearchOption.TopDirectoryOnly);
results.AddRange(r);
}
return results.ToArray();
}
/// <summary>
/// Removes paths which may overlap each other, such as sub directories.
/// </summary>
private static void RemoveOverlappingFolders(List<SpecifiedFolder> folders)
{
for (int z = 0; z < folders.Count; z++)
{
for (int i = 0; i < folders.Count; i++)
{
//Do not check against self.
if (i == z)
continue;
//Duplicate.
if (folders[z].Path.Equals(folders[i].Path, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"The same path is specified multiple times in the DefaultPrefabGenerator settings. Remove the duplicate to clear this error.");
folders.RemoveAt(i);
break;
}
/* We are checking if i can be within
* z. This is only possible if i is longer
* than z. */
if (folders[i].Path.Length < folders[z].Path.Length)
continue;
/* Do not need to check if not recursive.
* Only recursive needs to be checked because
* a shorter recursive path could contain
* a longer path. */
if (!folders[z].Recursive)
continue;
//Compare paths.
string zPath = GetPathWithSeparator(folders[z].Path);
string iPath = zPath.Substring(0, zPath.Length);
//If paths match.
if (iPath.Equals(zPath, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"Path {folders[i].Path} is included within recursive path {folders[z].Path}. Remove path {folders[i].Path} to clear this error.");
folders.RemoveAt(i);
break;
}
}
}
string GetPathWithSeparator(string txt)
{
return txt.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
}
}
/// <summary>
/// Returns a message to attach to logs if objects were dirtied.
/// </summary>
private static string GetDirtiedMessage(PrefabGeneratorConfigurations settings, bool dirtied)
{
if (!settings.SaveChanges && dirtied)
return " One or more NetworkObjects were dirtied. Please save your project.";
else
return string.Empty;
}
/// <summary>
/// Updates prefabs by using only changed information.
/// </summary>
public static void GenerateChanged(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, PrefabGeneratorConfigurations settings = null)
{
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
if (!settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
//No need to error if nto found, GetDefaultPrefabObjects will.
if (prefabCollection == null)
return;
int assetsLength = (importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length);
List<string> changedNobPaths = new List<string>();
System.Type goType = typeof(UnityEngine.GameObject);
IterateAssetCollection(importedAssets);
IterateAssetCollection(movedAssets);
//True if dirtied by changes.
bool dirtied;
//First remove null entries.
int startCount = prefabCollection.GetObjectCount();
prefabCollection.RemoveNull();
dirtied = (prefabCollection.GetObjectCount() != startCount);
//First index which new objects will be added to.
int firstAddIndex = (prefabCollection.GetObjectCount() - 1);
//Iterates strings adding prefabs to collection.
void IterateAssetCollection(string[] c)
{
foreach (string item in c)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
if (assetType != goType)
continue;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(item);
if (nob != null)
{
changedNobPaths.Add(item);
prefabCollection.AddObject(nob, true);
dirtied = true;
}
}
}
//To prevent out of range.
if (firstAddIndex < 0 || firstAddIndex >= prefabCollection.GetObjectCount())
firstAddIndex = 0;
dirtied |= prefabCollection.SetAssetPathHashes(firstAddIndex);
if (log && dirtied)
UnityDebug.Log($"Default prefab generator updated prefabs in {sw.ElapsedMilliseconds}ms.{GetDirtiedMessage(settings, dirtied)}");
//Check for redundancy.
int frameCount = Time.frameCount;
int changedCount = changedNobPaths.Count;
if (frameCount == _lastUpdatedFrame && assetsLength == _lastUpdatedLengths && (changedCount == _lastUpdatedNamePaths.Count) && changedCount > 0)
{
bool allMatch = true;
for (int i = 0; i < changedCount; i++)
{
if (changedNobPaths[i] != _lastUpdatedNamePaths[i])
{
allMatch = false;
break;
}
}
/* If the import results are the same as the last attempt, on the same frame
* then there is likely an issue saving the assets. */
if (allMatch)
{
//Unset dirtied to prevent a save.
dirtied = false;
//Log this no matter what, it's critical.
UnityDebug.LogError($"Default prefab generator had a problem saving one or more assets. " +
$"This usually occurs when the assets cannot be saved due to missing scripts or serialization errors. " +
$"Please see above any prefabs which could not save any make corrections.");
}
}
//Set last values.
_lastUpdatedFrame = Time.frameCount;
_lastUpdatedNamePaths = changedNobPaths;
_lastUpdatedLengths = assetsLength;
EditorUtility.SetDirty(prefabCollection);
if (dirtied && settings.SaveChanges)
AssetDatabase.SaveAssets();
}
/// <summary>
/// Generates prefabs by iterating all files within settings parameters.
/// </summary>
public static void GenerateFull(PrefabGeneratorConfigurations settings = null, bool forced = false)
{
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
if (!forced && !settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
List<NetworkObject> foundNobs = new List<NetworkObject>();
HashSet<string> excludedPaths = new HashSet<string>(settings.ExcludedFolders);
//If searching the entire project.
if (settings.SearchScope == (int)SearchScopeType.EntireProject)
{
foreach (string path in GetPrefabFiles("Assets", excludedPaths, true))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
}
//Specific folders.
else if (settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
List<SpecifiedFolder> folders = GetSpecifiedFolders(settings.IncludedFolders.ToList());
RemoveOverlappingFolders(folders);
foreach (SpecifiedFolder sf in folders)
{
//If specified folder doesn't exist then continue.
if (!Directory.Exists(sf.Path))
continue;
foreach (string path in GetPrefabFiles(sf.Path, excludedPaths, sf.Recursive))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
}
}
//Unhandled.
else
{
UnityDebug.LogError($"{settings.SearchScope} is not handled; default prefabs will not generator properly.");
}
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
//No need to error if not found, GetDefaultPrefabObjects will throw.
if (prefabCollection == null)
return;
//Clear and add built list.
prefabCollection.Clear();
prefabCollection.AddObjects(foundNobs, false);
bool dirtied = prefabCollection.SetAssetPathHashes(0);
int newCount = prefabCollection.GetObjectCount();
if (log)
{
string dirtiedMessage = (newCount > 0) ? GetDirtiedMessage(settings, dirtied) : string.Empty;
UnityDebug.Log($"Default prefab generator found {newCount} prefabs in {sw.ElapsedMilliseconds}ms.{dirtiedMessage}");
}
//Only set dirty if and save if prefabs were found.
if (newCount > 0)
{
EditorUtility.SetDirty(prefabCollection);
if (settings.SaveChanges)
AssetDatabase.SaveAssets();
}
}
/// <summary>
/// Iterates folders building them into SpecifiedFolders.
/// </summary>
private static List<SpecifiedFolder> GetSpecifiedFolders(List<string> folders)
{
List<SpecifiedFolder> results = new List<SpecifiedFolder>();
//Remove astericks.
foreach (string path in folders)
{
int pLength = path.Length;
if (pLength == 0)
continue;
bool recursive;
string p;
//If the last character indicates resursive.
if (path.Substring(pLength - 1, 1) == "*")
{
p = path.Substring(0, pLength - 1);
recursive = true;
}
else
{
p = path;
recursive = false;
}
results.Add(new SpecifiedFolder(p, recursive));
}
return results;
}
/// <summary>
/// Returns the DefaultPrefabObjects file.
/// </summary>
private static DefaultPrefabObjects GetDefaultPrefabObjects(PrefabGeneratorConfigurations settings = null)
{
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
//Load the prefab collection
string defaultPrefabsPath = settings.DefaultPrefabObjectsPath;
defaultPrefabsPath = defaultPrefabsPath.Replace(@"\", "/");
string fullDefaultPrefabsPath = (defaultPrefabsPath.Length > 0) ? Path.GetFullPath(defaultPrefabsPath) : string.Empty;
//If cached prefabs is not the same path as assetPath.
if (_cachedDefaultPrefabs != null)
{
string unityAssetPath = AssetDatabase.GetAssetPath(_cachedDefaultPrefabs);
string fullCachedPath = (unityAssetPath.Length > 0) ? Path.GetFullPath(unityAssetPath) : string.Empty;
if (fullCachedPath != fullDefaultPrefabsPath)
_cachedDefaultPrefabs = null;
}
//If cached is null try to get it.
if (_cachedDefaultPrefabs == null)
{
//Only try to load it if file exist.
if (File.Exists(fullDefaultPrefabsPath))
{
_cachedDefaultPrefabs = AssetDatabase.LoadAssetAtPath<DefaultPrefabObjects>(defaultPrefabsPath);
if (_cachedDefaultPrefabs == null)
{
//If already retried then throw an error.
if (_retryRefreshDefaultPrefabs)
{
UnityDebug.LogError("DefaultPrefabObjects file exists but it could not be loaded by Unity. Use the Fish-Networking menu to Refresh Default Prefabs.");
}
else
{
UnityDebug.Log("DefaultPrefabObjects file exists but it could not be loaded by Unity. Trying to reload the file next frame.");
_retryRefreshDefaultPrefabs = true;
}
return null;
}
}
}
if (_cachedDefaultPrefabs == null)
{
string fullPath = Path.GetFullPath(defaultPrefabsPath);
UnityDebug.Log($"Creating a new DefaultPrefabsObject at {fullPath}.");
string directory = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
AssetDatabase.Refresh();
}
_cachedDefaultPrefabs = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
AssetDatabase.CreateAsset(_cachedDefaultPrefabs, defaultPrefabsPath);
AssetDatabase.SaveAssets();
}
if (_cachedDefaultPrefabs != null && _retryRefreshDefaultPrefabs)
UnityDebug.Log("DefaultPrefabObjects found on the second iteration.");
return _cachedDefaultPrefabs;
}
/// <summary>
/// Called every frame the editor updates.
/// </summary>
private static void OnEditorUpdate()
{
if (!_retryRefreshDefaultPrefabs)
return;
GenerateFull();
_retryRefreshDefaultPrefabs = false;
}
/// <summary>
/// Called by Unity when assets are modified.
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (Application.isPlaying)
return;
//If retrying next frame don't bother updating, next frame will do a full refresh.
if (_retryRefreshDefaultPrefabs)
return;
//Post process is being ignored. Could be temporary or user has disabled this feature.
if (IgnorePostProcess)
return;
/* Don't iterate if updating or compiling as that could cause an infinite loop
* due to the prefabs being generated during an update, which causes the update
* to start over, which causes the generator to run again, which... you get the idea. */
if (EditorApplication.isCompiling)
return;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects();
if (prefabCollection == null)
return;
PrefabGeneratorConfigurations settings = Configuration.Configurations.PrefabGenerator;
if (prefabCollection.GetObjectCount() == 0)
{
//If there are no prefabs then do a full rebuild. Odds of there being none are pretty much nill.
GenerateFull(settings);
}
else
{
int totalChanges = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;
//Nothing has changed. This shouldn't occur but unity is funny so we're going to check anyway.
if (totalChanges == 0)
return;
//normalizes path.
string dpoPath = Path.GetFullPath(settings.DefaultPrefabObjectsPath);
//If total changes is 1 and the only changed file is the default prefab collection then do nothing.
if (totalChanges == 1)
{
//Do not need to check movedFromAssetPaths because that's not possible for this check.
if ((importedAssets.Length == 1 && Path.GetFullPath(importedAssets[0]) == dpoPath)
|| (deletedAssets.Length == 1 && Path.GetFullPath(deletedAssets[0]) == dpoPath)
|| (movedAssets.Length == 1 && Path.GetFullPath(movedAssets[0]) == dpoPath))
return;
/* If the only change is an import then check if the imported file
* is the same as the last, and if so check into returning early.
* For some reason occasionally when files are saved unity runs postprocess
* multiple times on the same file. */
string imported = (importedAssets.Length == 1) ? importedAssets[0] : null;
if (imported != null && imported == _lastSingleImportedAsset)
{
//If here then the file is the same. Make sure it's already in the collection before returning.
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(imported);
//Not a gameObject, no reason to continue.
if (assetType != typeof(GameObject))
return;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(imported);
//If is a networked object.
if (nob != null)
{
//Already added!
if (prefabCollection.Prefabs.Contains(nob))
return;
}
}
else if (imported != null)
{
_lastSingleImportedAsset = imported;
}
}
bool fullRebuild = settings.FullRebuild;
/* If updating FN. This needs to be done a better way.
* Parsing the actual version file would be better.
* I'll get to it next release. */
if (!_ranOnce)
{
_ranOnce = true;
fullRebuild = true;
}
else
{
CheckForVersionFile(importedAssets);
CheckForVersionFile(deletedAssets);
CheckForVersionFile(movedAssets);
CheckForVersionFile(movedFromAssetPaths);
}
/* See if any of the changed files are the version file.
* A new version file suggests an update. Granted, this could occur if
* other assets imported a new version file as well but better
* safe than sorry. */
void CheckForVersionFile(string[] arr)
{
string targetText = "VERSION.txt".ToLower();
int targetLength = targetText.Length;
for (int i = 0; i < arr.Length; i++)
{
string item = arr[i];
int itemLength = item.Length;
if (itemLength < targetLength)
continue;
item = item.ToLower();
int startIndex = (itemLength - targetLength);
if (item.Substring(startIndex, targetLength) == targetText)
{
fullRebuild = true;
return;
}
}
}
if (fullRebuild)
GenerateFull(settings);
else
GenerateChanged(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths, settings);
}
}
}
}
#endif

View File

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

View File

@ -0,0 +1,103 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal sealed class PrefabCollectionGeneratorConfigurationsz
{
#region Types.
public enum SearchScopeType : byte
{
EntireProject = 0,
SpecificFolders = 1
}
#endregion
//#region Public.
///// <summary>
///// True if prefab generation is enabled.
///// </summary>
//public bool Enabled;
///// <summary>
///// True to rebuild all prefabs during any change. False to only check changed prefabs.
///// </summary>
//public bool FullRebuild;
///// <summary>
///// True to log results to console.
///// </summary>
//public bool LogToConsole;
///// <summary>
///// True to automatically save assets when default prefabs change.
///// </summary>
//public bool SaveChanges;
///// <summary>
///// Path where prefabs file is created.
///// </summary>
//public string AssetPath;
///// <summary>
///// How to search for files.
///// </summary>
//public SearchScopeType SearchScope = SearchScopeType.EntireProject;
///// <summary>
///// Folders to exclude when using SearchScopeType.SpecificFolders.
///// </summary>
//public List<string> ExcludedFolders = new List<string>();
///// <summary>
///// Folders to include when using SearchScopeType.SpecificFolders.
///// </summary>
//public List<string> IncludedFolders = new List<string>();
//#endregion
//#region Private.
///// <summary>
///// Library folder for project. Presumably where files are saved, but this is changing. This is going away in favor of FN config. //fnconfig.
///// </summary>
//private static string DirectoryPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Library");
///// <summary>
///// Full path of settings file. This is going away in favor of FN config. //fnconfig.
///// </summary>
//private static string FilePath => Path.Combine(DirectoryPath, $"FishNet.Runtime.Editor.PrefabObjects.Generation.{nameof(Settings)}.json");
// #endregion
// public Settings()
//{
// Enabled = true;
// LogToConsole = true;
// FullRebuild = false;
// SaveChanges = true;
// SearchScope = SearchScopeType.EntireProject;
// AssetPath = $"Assets{Path.DirectorySeparatorChar}FishNet{Path.DirectorySeparatorChar}Runtime{Path.DirectorySeparatorChar}DefaultPrefabObjects.asset";
//}
//public void Save()
//{
// //Create save folder if it doesn't exist. This is going away in favor of FN config. //fnconfig.
// if (!Directory.Exists(DirectoryPath))
// Directory.CreateDirectory(DirectoryPath);
// File.WriteAllText(FilePath, JsonUtility.ToJson(this));
//}
//public static Settings Load()
//{
// try
// {
// if (File.Exists(FilePath))
// return JsonUtility.FromJson<Settings>(File.ReadAllText(FilePath));
// }
// catch (Exception ex)
// {
// Debug.LogError($"An error has occurred when loading the prefab collection generator settings: {ex}");
// }
// return new Settings();
//}
}
}
#endif

View File

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

View File

@ -0,0 +1,238 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using FishNet.Configuring;
using System.Linq;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal static class SettingsProvider
{
private static readonly Regex SlashRegex = new Regex(@"[\\//]");
private static PrefabGeneratorConfigurations _settings;
private static GUIContent _folderIcon;
private static GUIContent _deleteIcon;
private static Vector2 _scrollVector;
private static bool _showFolders;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new UnitySettingsProvider("Project/Fish-Networking/Prefab Objects Generator", SettingsScope.Project)
{
label = "Prefab Objects Generator",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Prefab",
"Objects",
"Generator",
},
};
}
private static void OnGUI(string searchContext)
{
if (_settings == null)
_settings = Configuration.Configurations.PrefabGenerator;
if (_folderIcon == null)
_folderIcon = EditorGUIUtility.IconContent("d_FolderOpened Icon");
if (_deleteIcon == null)
_deleteIcon = EditorGUIUtility.IconContent("P4_DeletedLocal");
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new GUIStyle()
{
padding = new RectOffset(10, 10, 10, 10),
};
_scrollVector = EditorGUILayout.BeginScrollView(_scrollVector, scrollViewStyle);
_settings.Enabled = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.Enabled)), _settings.Enabled);
_settings.LogToConsole = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.LogToConsole)), _settings.LogToConsole);
_settings.FullRebuild = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.FullRebuild)), _settings.FullRebuild);
_settings.SaveChanges = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.SaveChanges)), _settings.SaveChanges);
GUILayoutOption iconWidthConstraint = GUILayout.MaxWidth(32.0f);
GUILayoutOption iconHeightConstraint = GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight);
EditorGUILayout.BeginHorizontal();
string oldAssetPath = _settings.DefaultPrefabObjectsPath;
string newAssetPath = EditorGUILayout.DelayedTextField(ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath)), oldAssetPath);
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TrySaveFilePathInsideAssetsFolder(null, Application.dataPath, "DefaultPrefabObjects", "asset", out string result))
newAssetPath = result;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
if (!newAssetPath.Equals(oldAssetPath, StringComparison.OrdinalIgnoreCase))
{
if (newAssetPath.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
{
if (File.Exists(newAssetPath))
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent("Another asset already exists at the new path."));
}
else
{
Generator.IgnorePostProcess = true;
if (File.Exists(oldAssetPath))
AssetDatabase.MoveAsset(oldAssetPath, newAssetPath);
_settings.DefaultPrefabObjectsPath = newAssetPath;
Generator.IgnorePostProcess = false;
}
}
else
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
}
EditorGUILayout.EndHorizontal();
int currentSearchScope = _settings.SearchScope;
SearchScopeType searchScopeType = (SearchScopeType)EditorGUILayout.EnumPopup(ValueToSearchScope(_settings.SearchScope));
_settings.SearchScope = (int)searchScopeType;
SearchScopeType ValueToSearchScope(int value) => (SearchScopeType)value;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
EditorGUILayout.HelpBox("Searching the entire project for prefabs can become very slow. Consider switching the search scope to specific folders instead.", MessageType.Warning);
if (GUILayout.Button("Switch"))
_settings.SearchScope = (int)SearchScopeType.SpecificFolders;
}
//If search scope changed then update prefabs.
if (currentSearchScope != _settings.SearchScope && (SearchScopeType)_settings.SearchScope == SearchScopeType.EntireProject)
Generator.GenerateFull();
List<string> folders = null;
string foldersName = null;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
folders = _settings.ExcludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.ExcludedFolders));
}
else if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
folders = _settings.IncludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.IncludedFolders));
}
string folderName = foldersName.Substring(0, foldersName.Length - 1);
if ((_showFolders = EditorGUILayout.Foldout(_showFolders, $"{foldersName} ({folders.Count})")) && folders != null)
{
EditorGUI.indentLevel++;
for (int i = 0; i < folders.Count; i++)
{
EditorGUILayout.BeginHorizontal();
string oldFolder = folders[i];
string newFolder = SlashRegex.Replace(EditorGUILayout.DelayedTextField(oldFolder), Path.DirectorySeparatorChar.ToString());
if (!newFolder.Equals(oldFolder, StringComparison.OrdinalIgnoreCase))
{
if (newFolder.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
folders[i] = newFolder;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
folders[i] = result;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_deleteIcon, iconWidthConstraint, iconHeightConstraint)) folders.RemoveAt(i);
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders) EditorGUILayout.HelpBox("You can include subfolders by appending an asterisk (*) to a path.", MessageType.None);
if (GUILayout.Button("Browse"))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
{
folders.Add(result);
}
else
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
}
}
if (EditorGUI.EndChangeCheck())
Configuration.Configurations.Write(true);
if (GUILayout.Button("Generate"))
Generator.GenerateFull();
EditorGUILayout.HelpBox("Consider pressing 'Generate' after changing the settings.", MessageType.Info);
EditorGUILayout.EndScrollView();
}
private static bool TrySaveFilePathInsideAssetsFolder(string title, string directory, string name, string extension, out string result)
{
result = null;
string selectedPath = EditorUtility.SaveFilePanel(title, directory, name, extension);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
private static bool TryOpenFolderPathInsideAssetsFolder(string title, string folder, string name, out string result)
{
result = null;
string selectedPath = EditorUtility.OpenFolderPanel(title, folder, name);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
}
}
#endif

View File

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

View File

@ -0,0 +1,85 @@
//#if UNITY_EDITOR
//using FishNet.Managing.Object;
//using FishNet.Object;
//using UnityEditor;
//using UnityEngine;
//namespace FishNet.Editing
//{
// internal class PrefabProcessor : AssetPostprocessor
// {
// #region Private.
// /// <summary>
// /// ScriptableObject to store default prefabs.
// /// </summary>
// private static DefaultPrefabObjects _defaultPrefabs;
// #endregion
// /// <summary>
// /// Called after assets are created or imported.
// /// </summary>
// /// <param name="importedAssets"></param>
// /// <param name="deletedAssets"></param>
// /// <param name="movedAssets"></param>
// /// <param name="movedFromAssetPaths"></param>
//#if UNITY_2021_3_OR_NEWER
// private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
//#else
// private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
//#endif
// {
//#if UNITY_2021_3_OR_NEWER
// if (didDomainReload)
// return;
//#endif
// bool justPopulated;
// if (_defaultPrefabs == null)
// _defaultPrefabs = DefaultPrefabsFinder.GetDefaultPrefabsFile(out justPopulated);
// else
// justPopulated = DefaultPrefabsFinder.PopulateDefaultPrefabs();
// //Not found.
// if (_defaultPrefabs == null)
// return;
// //True if null must be removed as well.
// bool removeNull = (deletedAssets.Length > 0 || movedAssets.Length > 0 || movedFromAssetPaths.Length > 0);
// if (removeNull)
// _defaultPrefabs.RemoveNull();
// /* Only need to add new prefabs if not justPopulated.
// * justPopulated would have already picked up the new prefabs. */
// if (justPopulated)
// return;
// System.Type goType = typeof(UnityEngine.GameObject);
// foreach (string item in importedAssets)
// {
// System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
// if (assetType != goType)
// continue;
// GameObject go = (GameObject)AssetDatabase.LoadAssetAtPath(item, typeof(GameObject));
// //If is a gameobject.
// if (go != null)
// {
// NetworkObject nob;
// //Not a network object.
// if (!go.TryGetComponent<NetworkObject>(out nob))
// continue;
// /* Check for duplicates because adding a component to a prefab will also call this function
// * which will result in this function calling multiple times for the same object. */
// _defaultPrefabs.AddObject(nob, true);
// }
// }
// EditorUtility.SetDirty(_defaultPrefabs);
// }
// }
//}
//#endif

View File

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

View File

@ -0,0 +1,74 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace FishNet
{
internal static class ScriptingDefines
{
[InitializeOnLoadMethod]
public static void AddDefineSymbols()
{
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
/* Convert current defines into a hashset. This is so we can
* determine if any of our defines were added. Only save playersettings
* when a define is added. */
HashSet<string> definesHs = new HashSet<string>();
string[] currentArr = currentDefines.Split(';');
//Add current defines into hs.
foreach (string item in currentArr)
definesHs.Add(item);
string proDefine = "FISHNET_PRO";
string versionPrefix = "FISHNET_V";
string thisVersion = $"{versionPrefix}3";
string[] fishNetDefines = new string[]
{
"FISHNET",
thisVersion,
};
bool modified = false;
//Now add FN defines.
foreach (string item in fishNetDefines)
modified |= definesHs.Add(item);
/* Remove pro define if not on pro. This might look a little
* funny because the code below varies depending on if pro or not. */
#pragma warning disable CS0162 // Unreachable code detected
modified |= definesHs.Remove(proDefine);
#pragma warning restore CS0162 // Unreachable code detected
List<string> definesToRemove = new List<string>();
int versionPrefixLength = versionPrefix.Length;
//Remove old versions.
foreach (string item in definesHs)
{
//Do not remove this version.
if (item == thisVersion)
continue;
//If length is possible to be a version prefix and is so then remove it.
if (item.Length >= versionPrefixLength && item.Substring(0, versionPrefixLength) == versionPrefix)
definesToRemove.Add(item);
}
modified |= (definesToRemove.Count > 0);
foreach (string item in definesToRemove)
{
definesHs.Remove(item);
Debug.Log($"Removed unused Fish-Networking define {item}.");
}
if (modified)
{
Debug.Log("Added or removed Fish-Networking defines within player settings.");
string changedDefines = string.Join(";", definesHs);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, changedDefines);
}
}
}
}
#endif

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

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

View File

@ -0,0 +1,17 @@
{
"name": "FishNet.Runtime",
"references": [
"GUID:9e24947de15b9834991c9d8411ea37cf",
"GUID:84651a3751eca9349aac36a66bba901b",
"GUID:69448af7b92c7f342b298e06a37122aa"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,187 @@
#if UNITY_EDITOR
using FishNet.Editing;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace FishNet.Component.Animating.Editing
{
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : Editor
{
private SerializedProperty _animator;
private SerializedProperty _interpolation;
//private SerializedProperty _synchronizeInterval;
private SerializedProperty _smoothFloats;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private RuntimeAnimatorController _lastRuntimeAnimatorController;
private AnimatorController _lastAnimatorController;
protected virtual void OnEnable()
{
_animator = serializedObject.FindProperty("_animator");
_interpolation = serializedObject.FindProperty("_interpolation");
//_synchronizeInterval = serializedObject.FindProperty("_synchronizeInterval");
_smoothFloats = serializedObject.FindProperty("_smoothFloats");
_clientAuthoritative = serializedObject.FindProperty("_clientAuthoritative");
_sendToOwner = serializedObject.FindProperty("_sendToOwner");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
NetworkAnimator na = (NetworkAnimator)target;
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(na), typeof(NetworkAnimator), false);
GUI.enabled = true;
#pragma warning disable CS0162 // Unreachable code detected
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
#pragma warning restore CS0162 // Unreachable code detected
//Animator
EditorGUILayout.LabelField("Animator", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_animator, new GUIContent("Animator", "The animator component to synchronize."));
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Synchronization Processing.
EditorGUILayout.LabelField("Synchronization Processing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interpolation);
//EditorGUILayout.PropertyField(_synchronizeInterval, new GUIContent("Synchronize Interval", "How often to synchronize this animator."));
EditorGUILayout.PropertyField(_smoothFloats, new GUIContent("Smooth Floats", "True to smooth floats on spectators rather than snap to their values immediately. Commonly set to true for smooth blend tree animations."));
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative, new GUIContent("Client Authoritative", "True if using client authoritative movement."));
if (_clientAuthoritative.boolValue == false)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner, new GUIContent("Synchronize To Owner", "True to synchronize server results back to owner. Typically used when you are sending inputs to the server and are relying on the server response to move the transform."));
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
DrawParameters(na);
serializedObject.ApplyModifiedProperties();
}
private void DrawParameters(NetworkAnimator na)
{
EditorGUILayout.LabelField("* Synchronized Parameters", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("This setting allows you to optionally completely prevent the synchronization of certain parameters. Both Fish-Networking free and Pro will only synchronize changes as they occur.", MessageType.Info);
if (Application.isPlaying)
{
EditorGUILayout.HelpBox("This feature can only be configured while out of play mode.", MessageType.Info);
return;
}
if (na == null)
return;
Animator animator = na.Animator;
if (animator == null)
return;
RuntimeAnimatorController runtimeController = (animator.runtimeAnimatorController is AnimatorOverrideController aoc) ? aoc.runtimeAnimatorController : animator.runtimeAnimatorController;
if (runtimeController == null)
{
na.IgnoredParameters.Clear();
return;
}
/* If runtime controller changed
* or editor controller is null
* then get new editor controller. */
if (runtimeController != _lastRuntimeAnimatorController || _lastAnimatorController == null)
_lastAnimatorController = (AnimatorController)AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(runtimeController), typeof(AnimatorController));
_lastRuntimeAnimatorController = runtimeController;
Color defaultColor = GUI.backgroundColor;
float width = Screen.width;
float spacePerEntry = 125f;
//Buttons seem to be longer than spacePerEntry. Why, because who knows...
float extraSpaceJustBecause = 60;
float spacer = 20f;
width -= spacer;
int entriesPerWidth = Mathf.Max(1, Mathf.FloorToInt(width / (spacePerEntry + extraSpaceJustBecause)));
List<AnimatorControllerParameter> aps = new List<AnimatorControllerParameter>();
//Create a parameter detail for each parameter that can be synchronized.
int count = 0;
foreach (AnimatorControllerParameter item in _lastAnimatorController.parameters)
{
count++;
//Over 240 parameters; who would do this!?
if (count >= 240)
continue;
aps.Add(item);
}
int apsCount = aps.Count;
for (int i = 0; i < apsCount; i++)
{
using (GUILayout.HorizontalScope hs = new GUILayout.HorizontalScope())
{
GUILayout.Space(spacer);
int z = 0;
while (z < entriesPerWidth && (z + i < apsCount))
{
//If this z+i would exceed entries then break.
if (z + i >= apsCount)
break;
AnimatorControllerParameter item = aps[i + z];
string parameterName = item.name;
bool ignored = na.IgnoredParameters.Contains(parameterName);
Color c = (ignored) ? Color.gray : Color.green;
GUI.backgroundColor = c;
if (GUILayout.Button(item.name, GUILayout.Width(spacePerEntry)))
{
if (Application.isPlaying)
{
Debug.Log("Synchronized parameters may not be changed while playing.");
}
else
{
if (ignored)
na.IgnoredParameters.Remove(parameterName);
else
na.IgnoredParameters.Add(parameterName);
}
}
z++;
}
i += (z - 1);
}
GUI.backgroundColor = defaultColor;
}
}
}
}
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8cac635f24954048aad3a6ff9110beb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@ -0,0 +1,134 @@
#if UNITY_EDITOR
using FishNet.Editing;
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Editing
{
[CustomEditor(typeof(NetworkTransform), true)]
[CanEditMultipleObjects]
public class NetworkTransformEditor : Editor
{
private SerializedProperty _componentConfiguration;
private SerializedProperty _synchronizeParent;
private SerializedProperty _packing;
private SerializedProperty _interpolation;
private SerializedProperty _extrapolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private SerializedProperty _synchronizePosition;
private SerializedProperty _positionSnapping;
private SerializedProperty _synchronizeRotation;
private SerializedProperty _rotationSnapping;
private SerializedProperty _synchronizeScale;
private SerializedProperty _scaleSnapping;
protected virtual void OnEnable()
{
_componentConfiguration = serializedObject.FindProperty(nameof(_componentConfiguration));
_synchronizeParent = serializedObject.FindProperty("_synchronizeParent");
_packing = serializedObject.FindProperty("_packing");
_interpolation = serializedObject.FindProperty("_interpolation");
_extrapolation = serializedObject.FindProperty("_extrapolation");
_enableTeleport = serializedObject.FindProperty("_enableTeleport");
_teleportThreshold = serializedObject.FindProperty("_teleportThreshold");
_clientAuthoritative = serializedObject.FindProperty("_clientAuthoritative");
_sendToOwner = serializedObject.FindProperty("_sendToOwner");
_synchronizePosition = serializedObject.FindProperty("_synchronizePosition");
_positionSnapping = serializedObject.FindProperty("_positionSnapping");
_synchronizeRotation = serializedObject.FindProperty("_synchronizeRotation");
_rotationSnapping = serializedObject.FindProperty("_rotationSnapping");
_synchronizeScale = serializedObject.FindProperty("_synchronizeScale");
_scaleSnapping = serializedObject.FindProperty("_scaleSnapping");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTransform)target), typeof(NetworkTransform), false);
GUI.enabled = true;
#pragma warning disable CS0162 // Unreachable code detected
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
#pragma warning restore CS0162 // Unreachable code detected
//Misc.
EditorGUILayout.LabelField("Misc", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_componentConfiguration);
EditorGUILayout.PropertyField(_synchronizeParent, new GUIContent("* Synchronize Parent"));
EditorGUILayout.PropertyField(_packing);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Smoothing.
EditorGUILayout.LabelField("Smoothing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interpolation);
EditorGUILayout.PropertyField(_extrapolation, new GUIContent("* Extrapolation"));
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_teleportThreshold);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative);
if (!_clientAuthoritative.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Synchronizing.
EditorGUILayout.LabelField("Synchronizing.", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
//Position.
EditorGUILayout.PropertyField(_synchronizePosition);
if (_synchronizePosition.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_positionSnapping);
EditorGUI.indentLevel -= 2;
}
//Rotation.
EditorGUILayout.PropertyField(_synchronizeRotation);
if (_synchronizeRotation.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_rotationSnapping);
EditorGUI.indentLevel -= 2;
}
//Scale.
EditorGUILayout.PropertyField(_synchronizeScale);
if (_synchronizeScale.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_scaleSnapping);
EditorGUI.indentLevel -= 2;
}
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2836e36774ca1c4bbbee976e17b649c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
namespace FishNet.Component.Transforming
{
public enum SynchronizedProperty : byte
{
None = 0,
Parent = 1,
Position = 2,
Rotation = 4,
Scale = 8
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,152 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using static FishNet.Component.Prediction.PredictedObject;
namespace FishNet.Component.Prediction
{
[CustomEditor(typeof(PredictedObject), true)]
[CanEditMultipleObjects]
public class PredictedObjectEditor : Editor
{
private SerializedProperty _implementsPredictionMethods;
private SerializedProperty _graphicalObject;
private SerializedProperty _ownerSmoothPosition;
private SerializedProperty _ownerSmoothRotation;
private SerializedProperty _ownerInterpolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
private SerializedProperty _predictionType;
private SerializedProperty _rigidbody;
private SerializedProperty _rigidbody2d;
private SerializedProperty _spectatorSmoothPosition;
private SerializedProperty _spectatorSmoothRotation;
private SerializedProperty _spectatorSmoothingType;
private SerializedProperty _customSmoothingData;
private SerializedProperty _preconfiguredSmoothingDataPreview;
private SerializedProperty _maintainedVelocity;
private SerializedProperty _resendType;
private SerializedProperty _resendInterval;
private SerializedProperty _networkTransform;
protected virtual void OnEnable()
{
_implementsPredictionMethods = serializedObject.FindProperty(nameof(_implementsPredictionMethods));
_graphicalObject = serializedObject.FindProperty(nameof(_graphicalObject));
_ownerSmoothPosition = serializedObject.FindProperty(nameof(_ownerSmoothPosition));
_ownerSmoothRotation = serializedObject.FindProperty(nameof(_ownerSmoothRotation));
_ownerInterpolation = serializedObject.FindProperty(nameof(_ownerInterpolation));
_enableTeleport = serializedObject.FindProperty(nameof(_enableTeleport));
_teleportThreshold = serializedObject.FindProperty(nameof(_teleportThreshold));
_predictionType = serializedObject.FindProperty(nameof(_predictionType));
_rigidbody = serializedObject.FindProperty(nameof(_rigidbody));
_rigidbody2d = serializedObject.FindProperty(nameof(_rigidbody2d));
_spectatorSmoothPosition = serializedObject.FindProperty(nameof(_spectatorSmoothPosition));
_spectatorSmoothRotation = serializedObject.FindProperty(nameof(_spectatorSmoothRotation));
_spectatorSmoothingType = serializedObject.FindProperty(nameof(_spectatorSmoothingType));
_customSmoothingData = serializedObject.FindProperty(nameof(_customSmoothingData));
_preconfiguredSmoothingDataPreview = serializedObject.FindProperty(nameof(_preconfiguredSmoothingDataPreview));
_maintainedVelocity = serializedObject.FindProperty(nameof(_maintainedVelocity));
_resendType = serializedObject.FindProperty(nameof(_resendType));
_resendInterval = serializedObject.FindProperty(nameof(_resendInterval));
_networkTransform = serializedObject.FindProperty(nameof(_networkTransform));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((PredictedObject)target), typeof(PredictedObject), false);
GUI.enabled = true;
EditorGUILayout.PropertyField(_implementsPredictionMethods);
EditorGUILayout.PropertyField(_graphicalObject);
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_teleportThreshold);
EditorGUI.indentLevel--;
}
EditorGUILayout.LabelField("Owner Settings");
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_ownerSmoothPosition, new GUIContent("Smooth Position"));
EditorGUILayout.PropertyField(_ownerSmoothRotation, new GUIContent("Smooth Rotation"));
EditorGUILayout.PropertyField(_ownerInterpolation, new GUIContent("Interpolation"));
EditorGUI.indentLevel--;
EditorGUILayout.PropertyField(_predictionType);
PredictedObject.PredictionType movementType = (PredictedObject.PredictionType)_predictionType.intValue;
if (movementType != PredictedObject.PredictionType.Other)
{
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox("When using physics prediction do not include a NetworkTransform; this component will synchronize instead.", MessageType.Info);
if (movementType == PredictedObject.PredictionType.Rigidbody)
EditorGUILayout.PropertyField(_rigidbody);
else
EditorGUILayout.PropertyField(_rigidbody2d, new GUIContent("Rigidbody2D", "Rigidbody2D to predict."));
EditorGUILayout.LabelField("Spectator Settings");
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Smoothing");
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_spectatorSmoothPosition, new GUIContent("Smooth Position"));
EditorGUILayout.PropertyField(_spectatorSmoothRotation, new GUIContent("Smooth Rotation"));
EditorGUILayout.PropertyField(_spectatorSmoothingType, new GUIContent("Smoothing Type"));
//Custom.
if ((SpectatorSmoothingType)_spectatorSmoothingType.intValue == SpectatorSmoothingType.Custom)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_customSmoothingData);
EditorGUI.indentLevel--;
}
//Preconfigured.
else
{
EditorGUI.indentLevel++;
GUI.enabled = false;
EditorGUILayout.PropertyField(_preconfiguredSmoothingDataPreview);
GUI.enabled = true;
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.PropertyField(_maintainedVelocity);
EditorGUILayout.PropertyField(_resendType);
PredictedObject.ResendType resendType = (PredictedObject.ResendType)_resendType.intValue;
if (resendType == PredictedObject.ResendType.Interval)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_resendInterval, new GUIContent("Interval"));
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUI.indentLevel--;
}
else
{
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox("When other is selected another component, such as NetworkTransform, must be used to synchronize.", MessageType.Info);
EditorGUILayout.PropertyField(_networkTransform);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

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

View File

@ -0,0 +1,130 @@
using FishNet.Managing.Predicting;
using FishNet.Managing.Timing;
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Prediction
{
public partial class OfflineRigidbody : MonoBehaviour
{
#region Serialized.
/// <summary>
/// Type of prediction movement which is being used.
/// </summary>
[Tooltip("Type of prediction movement which is being used.")]
[SerializeField]
private RigidbodyType _rigidbodyType;
/// <summary>
/// GraphicalObject to unparent when pausing.
/// </summary>
private Transform _graphicalObject;
/// <summary>
/// Sets GraphicalObject.
/// </summary>
/// <param name="value"></param>
public void SetGraphicalObject(Transform value)
{
_graphicalObject = value;
UpdateRigidbodies();
}
/// <summary>
/// True to also get rigidbody components within children.
/// </summary>
[Tooltip("True to also get rigidbody components within children.")]
[SerializeField]
private bool _getInChildren;
#endregion
#region Private.
/// <summary>
/// Pauser for rigidbodies.
/// </summary>
private RigidbodyPauser _rigidbodyPauser = new RigidbodyPauser();
/// <summary>
/// TimeManager subscribed to.
/// </summary>
private PredictionManager _predictionManager;
#endregion
private void Awake()
{
InitializeOnce();
}
private void OnDestroy()
{
ChangeSubscription(false);
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void InitializeOnce()
{
_predictionManager = InstanceFinder.PredictionManager;
UpdateRigidbodies();
ChangeSubscription(true);
}
/// <summary>
/// Sets a new TimeManager to use.
/// </summary>
/// <param name="tm"></param>
public void SetPredictionManager(PredictionManager pm)
{
if (pm == _predictionManager)
return;
//Unsub from current.
ChangeSubscription(false);
//Sub to newest.
_predictionManager = pm;
ChangeSubscription(true);
}
/// <summary>
/// Finds and assigns rigidbodie using configured settings.
/// </summary>
public void UpdateRigidbodies()
{
_rigidbodyPauser.UpdateRigidbodies(transform, _rigidbodyType, _getInChildren, _graphicalObject);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscription(bool subscribe)
{
if (_predictionManager == null)
return;
if (subscribe)
{
_predictionManager.OnPreReconcile += _predictionManager_OnPreReconcile;
_predictionManager.OnPostReconcile += _predictionManager_OnPostReconcile;
}
else
{
_predictionManager.OnPreReconcile -= _predictionManager_OnPreReconcile;
_predictionManager.OnPostReconcile -= _predictionManager_OnPostReconcile;
}
}
private void _predictionManager_OnPreReconcile(NetworkBehaviour obj)
{
//Make rbs all kinematic/!simulated before reconciling, which would also result in replays.
_rigidbodyPauser.Pause();
}
private void _predictionManager_OnPostReconcile(NetworkBehaviour obj)
{
_rigidbodyPauser.Unpause();
}
}
}

View File

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

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