Add StickGame Assets

This commit is contained in:
Dzejkobik007
2024-03-24 22:21:16 +01:00
parent 5a5812c0c7
commit 6c8b523d1f
6643 changed files with 596260 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,72 @@
fileFormatVersion: 2
guid: 1907658b89c1bbe42a0063df40b7ca24
labels:
- RoslynAnalyzer
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- UNITY_2020_3_OR_NEWER
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 620557a5e202e644cb322b8fcc9422ea
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
Git URL:
https://github.com/Abdelfattah-Radwan/FishNet.CodeAnalysis

View File

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

View File

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

View File

@@ -0,0 +1,59 @@
2.1.2
- Added deinitializer to FishySteamworks.
2.1.1
- Updated SetServerBindAddress signature to Fish-Networking 2.0.0.
2.1.0
- Increased version to match other projects with Fish-Networking 2.0.0 support.
- Fixed ClientId 32767 not found error when being used with Multipass.
1.7.0
- Fish-Networking 2.0.0 Support
1.6.0
- GetConnectionAddress now works for clientHost as well.
- Changed from ConcurrentQueue to Queue.
- Shutting down the socket properly recycles pending packets now.
1.5.0
- Fish-Networking 1.3.1 Multipass transport support.
1.4.4
- Removed obsolete method.
1.4.3
- Added a few sanity checks.
1.4.2
- Fish-Networking 0.1.5.Nightly.10 Support
1.4.1
- Fixed server incorrectly rejecting connections due to maximum clients met.
- Fixed client giving false connection started response.
- Added support for Steamworks.Net 20.0.0.
1.4.0
- Added client-host support.
- Organized folder structure.
- Removed ChannelData, it's not needed.
1.3.2
- Fixed HUD displaying incorrect states when connecting server.
1.3.1
- Fixed an allocation.
1.3.0
- Moved to it's own git. https://github.com/FirstGearGames/FishySteamworks/
1.2.0
- Support for Steamworks 15.0.1 changes.
1.1.0
- Made changes to function with Heathen Engineering - Steamworks v2: Foundation, Steamworks v2: Complete
https://assetstore.unity.com/packages/tools/integration/steamworks-v2-foundation-186949
https://assetstore.unity.com/packages/tools/integration/steamworks-v2-complete-190316
1.0.0
- Initial release.

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
using System.Collections;
using System.Collections.Generic;
namespace FishySteamworks
{
public class BidirectionalDictionary<T1, T2> : IEnumerable
{
private Dictionary<T1, T2> t1ToT2Dict = new Dictionary<T1, T2>();
private Dictionary<T2, T1> t2ToT1Dict = new Dictionary<T2, T1>();
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public int Count => t1ToT2Dict.Count;
public Dictionary<T1, T2> First => t1ToT2Dict;
public Dictionary<T2, T1> Second => t2ToT1Dict;
public void Add(T1 key, T2 value)
{
if (t1ToT2Dict.ContainsKey(key))
{
Remove(key);
}
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
public void Add(T2 key, T1 value)
{
if (t2ToT1Dict.ContainsKey(key))
{
Remove(key);
}
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
public T2 Get(T1 key) => t1ToT2Dict[key];
public T1 Get(T2 key) => t2ToT1Dict[key];
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
public void Remove(T1 key)
{
if (Contains(key))
{
T2 val = t1ToT2Dict[key];
t1ToT2Dict.Remove(key);
t2ToT1Dict.Remove(val);
}
}
public void Remove(T2 key)
{
if (Contains(key))
{
T1 val = t2ToT1Dict[key];
t1ToT2Dict.Remove(val);
t2ToT1Dict.Remove(key);
}
}
public T1 this[T2 key]
{
get => t2ToT1Dict[key];
set
{
Add(key, value);
}
}
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
Add(key, value);
}
}
}
}

View File

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

View File

@@ -0,0 +1,130 @@
#if !FISHYSTEAMWORKS
using FishNet.Transporting;
using FishNet.Utility.Performance;
using FishySteamworks.Server;
using System;
using System.Collections.Generic;
namespace FishySteamworks.Client
{
/// <summary>
/// Creates a fake client connection to interact with the ServerSocket when acting as host.
/// </summary>
public class ClientHostSocket : CommonSocket
{
#region Private.
/// <summary>
/// Socket for the server.
/// </summary>
private ServerSocket _server;
/// <summary>
/// Incomimg data.
/// </summary>
private Queue<LocalPacket> _incoming = new Queue<LocalPacket>();
#endregion
/// <summary>
/// Checks to set localCLient started.
/// </summary>
internal void CheckSetStarted()
{
//Check to set as started.
if (_server != null && base.GetLocalConnectionState() == LocalConnectionState.Starting)
{
if (_server.GetLocalConnectionState() == LocalConnectionState.Started)
SetLocalConnectionState(LocalConnectionState.Started, false);
}
}
/// <summary>
/// Starts the client connection.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="channelsCount"></param>
/// <param name="pollTime"></param>
internal bool StartConnection(ServerSocket serverSocket)
{
_server = serverSocket;
_server.SetClientHostSocket(this);
if (_server.GetLocalConnectionState() != LocalConnectionState.Started)
return false;
SetLocalConnectionState(LocalConnectionState.Starting, false);
return true;
}
/// <summary>
/// Sets a new connection state.
/// </summary>
protected override void SetLocalConnectionState(LocalConnectionState connectionState, bool server)
{
base.SetLocalConnectionState(connectionState, server);
if (connectionState == LocalConnectionState.Started)
_server.OnClientHostState(true);
else
_server.OnClientHostState(false);
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return false;
base.ClearQueue(_incoming);
//Immediately set stopped since no real connection exists.
SetLocalConnectionState(LocalConnectionState.Stopping, false);
SetLocalConnectionState(LocalConnectionState.Stopped, false);
_server.SetClientHostSocket(null);
return true;
}
/// <summary>
/// Iterations data received.
/// </summary>
internal void IterateIncoming()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
while (_incoming.Count > 0)
{
LocalPacket packet = _incoming.Dequeue();
ArraySegment<byte> segment = new ArraySegment<byte>(packet.Data, 0, packet.Length);
base.Transport.HandleClientReceivedDataArgs(new ClientReceivedDataArgs(segment, (Channel)packet.Channel, Transport.Index));
ByteArrayPool.Store(packet.Data);
}
}
/// <summary>
/// Called when the server sends the local client data.
/// </summary>
internal void ReceivedFromLocalServer(LocalPacket packet)
{
_incoming.Enqueue(packet);
}
/// <summary>
/// Queues data to be sent to server.
/// </summary>
internal void SendToServer(byte channelId, ArraySegment<byte> segment)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
if (_server.GetLocalConnectionState() != LocalConnectionState.Started)
return;
LocalPacket packet = new LocalPacket(segment, channelId);
_server.ReceivedFromClientHost(packet);
}
}
}
#endif // !DISABLESTEAMWORKS

View File

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

View File

@@ -0,0 +1,230 @@
#if !FISHYSTEAMWORKS
using FishNet.Managing.Logging;
using FishNet.Transporting;
using Steamworks;
using System;
using System.Threading;
using UnityEngine;
namespace FishySteamworks.Client
{
public class ClientSocket : CommonSocket
{
#region Private.
/// <summary>
/// Called when local connection state changes.
/// </summary>
private Callback<SteamNetConnectionStatusChangedCallback_t> _onLocalConnectionStateCallback = null;
/// <summary>
/// SteamId for host.
/// </summary>
private CSteamID _hostSteamID = CSteamID.Nil;
/// <summary>
/// Socket to use.
/// </summary>
private HSteamNetConnection _socket;
/// <summary>
/// Thread used to check for timeout.
/// </summary>
private Thread _timeoutThread = null;
/// <summary>
/// When connect should timeout in unscaled time.
/// </summary>
private float _connectTimeout = -1f;
#endregion
#region Const.
/// <summary>
/// Maximum time to wait before a timeout occurs when trying ot connect.
/// </summary>
private const float CONNECT_TIMEOUT_DURATION = 8000;
#endregion
/// <summary>
/// Checks of a connect attempt should time out.
/// </summary>
private void CheckTimeout()
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
do
{
//Timeout occurred.
if ((sw.ElapsedMilliseconds / 1000) > _connectTimeout)
StopConnection();
Thread.Sleep(50);
} while (base.GetLocalConnectionState() == LocalConnectionState.Starting);
sw.Stop();
//If here then the thread no longer needs to run. Can abort itself.
_timeoutThread.Abort();
}
/// <summary>
/// Starts the client connection.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="channelsCount"></param>
/// <param name="pollTime"></param>
internal bool StartConnection(string address, ushort port, bool peerToPeer)
{
try
{
if (_onLocalConnectionStateCallback == null)
_onLocalConnectionStateCallback = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnLocalConnectionState);
base.PeerToPeer = peerToPeer;
//If address is required then make sure it can be parsed.
byte[] ip = (!peerToPeer) ? base.GetIPBytes(address) : null;
if (!peerToPeer && ip == null)
{
base.SetLocalConnectionState(LocalConnectionState.Stopped, false);
return false;
}
base.SetLocalConnectionState(LocalConnectionState.Starting, false);
_connectTimeout = Time.unscaledTime + CONNECT_TIMEOUT_DURATION;
_timeoutThread = new Thread(CheckTimeout);
_timeoutThread.Start();
_hostSteamID = new CSteamID(UInt64.Parse(address));
SteamNetworkingIdentity smi = new SteamNetworkingIdentity();
smi.SetSteamID(_hostSteamID);
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
if (base.PeerToPeer)
{
_socket = SteamNetworkingSockets.ConnectP2P(ref smi, 0, options.Length, options);
}
else
{
SteamNetworkingIPAddr addr = new SteamNetworkingIPAddr();
addr.Clear();
addr.SetIPv6(ip, port);
_socket = SteamNetworkingSockets.ConnectByIPAddress(ref addr, 0, options);
}
}
catch
{
base.SetLocalConnectionState(LocalConnectionState.Stopped, false);
return false;
}
return true;
}
/// <summary>
/// Called when local connection state changes.
/// </summary>
private void OnLocalConnectionState(SteamNetConnectionStatusChangedCallback_t args)
{
if (args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
base.SetLocalConnectionState(LocalConnectionState.Started, false);
}
else if (args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection was closed by peer, {args.m_info.m_szEndDebug}");
StopConnection();
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection state changed: {args.m_info.m_eState.ToString()} - {args.m_info.m_szEndDebug}");
}
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
//Manually abort thread to close it down quicker.
if (_timeoutThread != null && _timeoutThread.IsAlive)
_timeoutThread.Abort();
/* Try to close the socket before exiting early
* We never want to leave sockets open. */
if (_socket != HSteamNetConnection.Invalid)
{
//Reset callback.
if (_onLocalConnectionStateCallback != null)
{
_onLocalConnectionStateCallback.Dispose();
_onLocalConnectionStateCallback = null;
}
SteamNetworkingSockets.CloseConnection(_socket, 0, string.Empty, false);
_socket = HSteamNetConnection.Invalid;
}
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return false;
base.SetLocalConnectionState(LocalConnectionState.Stopping, false);
base.SetLocalConnectionState(LocalConnectionState.Stopped, false);
return true;
}
/// <summary>
/// Iterations data received.
/// </summary>
internal void IterateIncoming()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
int messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(_socket, base.MessagePointers, MAX_MESSAGES);
if (messageCount > 0)
{
for (int i = 0; i < messageCount; i++)
{
base.GetMessage(base.MessagePointers[i], InboundBuffer, out ArraySegment<byte> segment, out byte channel);
base.Transport.HandleClientReceivedDataArgs(new ClientReceivedDataArgs(segment, (Channel)channel, Transport.Index));
}
}
}
/// <summary>
/// Queues data to be sent to server.
/// </summary>
/// <param name="channelId"></param>
/// <param name="segment"></param>
internal void SendToServer(byte channelId, ArraySegment<byte> segment)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
EResult res = base.Send(_socket, segment, channelId);
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection to server was lost.");
StopConnection();
}
else if (res != EResult.k_EResultOK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Could not send: {res.ToString()}");
}
}
/// <summary>
/// Sends queued data to server.
/// </summary>
internal void IterateOutgoing()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
SteamNetworkingSockets.FlushMessagesOnConnection(_socket);
}
}
}
#endif // !DISABLESTEAMWORKS

View File

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

View File

@@ -0,0 +1,210 @@
#if !FISHYSTEAMWORKS
using FishNet.Managing.Logging;
using FishNet.Transporting;
using FishNet.Utility.Performance;
using Steamworks;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using UnityEngine;
namespace FishySteamworks
{
public abstract class CommonSocket
{
#region Public.
/// <summary>
/// Current ConnectionState.
/// </summary>
private LocalConnectionState _connectionState = LocalConnectionState.Stopped;
/// <summary>
/// Returns the current ConnectionState.
/// </summary>
/// <returns></returns>
internal LocalConnectionState GetLocalConnectionState()
{
return _connectionState;
}
/// <summary>
/// Sets a new connection state.
/// </summary>
/// <param name="connectionState"></param>
protected virtual void SetLocalConnectionState(LocalConnectionState connectionState, bool server)
{
//If state hasn't changed.
if (connectionState == _connectionState)
return;
_connectionState = connectionState;
if (server)
Transport.HandleServerConnectionState(new ServerConnectionStateArgs(connectionState, Transport.Index));
else
Transport.HandleClientConnectionState(new ClientConnectionStateArgs(connectionState, Transport.Index));
}
#endregion
#region Protected.
/// <summary>
/// True if using PeerToPeer.
/// </summary>
protected bool PeerToPeer = false;
/// <summary>
/// Transport controlling this socket.
/// </summary>
protected Transport Transport = null;
/// <summary>
/// Pointers for received messages per connection.
/// </summary>
protected IntPtr[] MessagePointers = new IntPtr[MAX_MESSAGES];
/// <summary>
/// Buffer used to receive data.
/// </summary>
protected byte[] InboundBuffer = null;
#endregion
#region Const.
/// <summary>
/// Maximum number of messages which can be received per connection.
/// </summary>
protected const int MAX_MESSAGES = 256;
#endregion
/// <summary>
/// Initializes this for use.
/// </summary>
/// <param name="t"></param>
internal virtual void Initialize(Transport t)
{
Transport = t;
//Get whichever channel has max MTU and resize buffer.
int maxMTU = Transport.GetMTU(0);
maxMTU = Math.Max(maxMTU, Transport.GetMTU(1));
InboundBuffer = new byte[maxMTU];
}
/// <summary>
/// Gets bytes for address.
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
protected byte[] GetIPBytes(string address)
{
//If address is required then make sure it can be parsed.
if (!string.IsNullOrEmpty(address))
{
if (!IPAddress.TryParse(address, out IPAddress result))
{
if (Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Could not parse address {address} to IPAddress.");
return null;
}
else
{
return result.GetAddressBytes();
}
}
else
{
return null;
}
}
/// <summary>
/// Sends data over the steamConnection.
/// </summary>
/// <param name="steamConnection"></param>
/// <param name="segment"></param>
/// <param name="channelId"></param>
/// <returns></returns>
protected EResult Send(HSteamNetConnection steamConnection, ArraySegment<byte> segment, byte channelId)
{
/* Have to resize array to include channel index
* if array isn't large enough to fit it. This is because
* we don't know what channel data comes in on so
* the channel has to be packed into the data sent.
* Odds of the array having to resize are extremely low
* so while this is not ideal, it's still very low risk. */
if ((segment.Array.Length - 1) <= (segment.Offset + segment.Count))
{
byte[] arr = segment.Array;
Array.Resize(ref arr, arr.Length + 1);
arr[arr.Length - 1] = channelId;
}
//If large enough just increase the segment and set the channel byte.
else
{
segment.Array[segment.Offset + segment.Count] = channelId;
}
//Make a new segment so count is right.
segment = new ArraySegment<byte>(segment.Array, segment.Offset, segment.Count + 1);
GCHandle pinnedArray = GCHandle.Alloc(segment.Array, GCHandleType.Pinned);
IntPtr pData = pinnedArray.AddrOfPinnedObject() + segment.Offset;
int sendFlag = (channelId == (byte)Channel.Unreliable) ? Constants.k_nSteamNetworkingSend_Unreliable : Constants.k_nSteamNetworkingSend_Reliable;
#if UNITY_SERVER
EResult result = SteamGameServerNetworkingSockets.SendMessageToConnection(steamConnection, pData, (uint)segment.Count, sendFlag, out long _);
#else
EResult result = SteamNetworkingSockets.SendMessageToConnection(steamConnection, pData, (uint)segment.Count, sendFlag, out long _);
#endif
if (result != EResult.k_EResultOK)
{
if (Transport.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Send issue: {result}");
}
pinnedArray.Free();
return result;
}
/// <summary>
/// Clears a queue.
/// </summary>
/// <param name="queue"></param>
internal void ClearQueue(ConcurrentQueue<LocalPacket> queue)
{
while (queue.TryDequeue(out LocalPacket p))
ByteArrayPool.Store(p.Data);
}
/// <summary>
/// Clears a queue.
/// </summary>
/// <param name="queue"></param>
internal void ClearQueue(Queue<LocalPacket> queue)
{
while (queue.Count > 0)
{
LocalPacket p = queue.Dequeue();
ByteArrayPool.Store(p.Data);
}
}
/// <summary>
/// Returns a message from the steam network.
/// </summary>
/// <param name="ptr"></param>
/// <param name="buffer"></param>
/// <returns></returns>
protected void GetMessage(IntPtr ptr, byte[] buffer, out ArraySegment<byte> segment, out byte channel)
{
SteamNetworkingMessage_t data = Marshal.PtrToStructure<SteamNetworkingMessage_t>(ptr);
int packetLength = data.m_cbSize;
Marshal.Copy(data.m_pData, buffer, 0, packetLength);
//data.Release();
SteamNetworkingMessage_t.Release(ptr);
//Channel will be at the end of the packet.
channel = buffer[packetLength - 1];
//Set segment to length - 1 to exclude channel.
segment = new ArraySegment<byte>(buffer, 0, packetLength - 1);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,21 @@

using FishNet.Utility.Performance;
using System;
namespace FishySteamworks
{
internal struct LocalPacket
{
public byte[] Data;
public int Length;
public byte Channel;
public LocalPacket(ArraySegment<byte> data, byte channel)
{
Data = ByteArrayPool.Retrieve(data.Count);
Length = data.Count;
Buffer.BlockCopy(data.Array, data.Offset, Data, 0, Length);
Channel = channel;
}
}
}

View File

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

View File

@@ -0,0 +1,473 @@
#if !FISHYSTEAMWORKS
using FishNet.Managing.Logging;
using FishNet.Transporting;
using FishySteamworks.Client;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishySteamworks.Server
{
public class ServerSocket : CommonSocket
{
#region Public.
/// <summary>
/// Gets the current ConnectionState of a remote client on the server.
/// </summary>
/// <param name="connectionId">ConnectionId to get ConnectionState for.</param>
internal RemoteConnectionState GetConnectionState(int connectionId)
{
//Remote clients can only have Started or Stopped states since we cannot know in between.
if (_steamConnections.Second.ContainsKey(connectionId))
return RemoteConnectionState.Started;
else
return RemoteConnectionState.Stopped;
}
#endregion
#region Private.
/// <summary>
/// SteamConnections for ConnectionIds.
/// </summary>
private BidirectionalDictionary<HSteamNetConnection, int> _steamConnections = new BidirectionalDictionary<HSteamNetConnection, int>();
/// <summary>
/// SteamIds for ConnectionIds.
/// </summary>
private BidirectionalDictionary<CSteamID, int> _steamIds = new BidirectionalDictionary<CSteamID, int>();
/// <summary>
/// Maximum number of remote connections.
/// </summary>
private int _maximumClients;
/// <summary>
/// Next Id to use for a connection.
/// </summary>
private int _nextConnectionId;
/// <summary>
/// Socket for the connection.
/// </summary>
private HSteamListenSocket _socket = new HSteamListenSocket(0);
/// <summary>
/// Packets received from local client.
/// </summary>
private Queue<LocalPacket> _clientHostIncoming = new Queue<LocalPacket>();
/// <summary>
/// Contains state of the client host. True is started, false is stopped.
/// </summary>
private bool _clientHostStarted = false;
/// <summary>
/// Called when a remote connection state changes.
/// </summary>
private Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t> _onRemoteConnectionStateCallback;
/// <summary>
/// ConnectionIds which can be reused.
/// </summary>
private Queue<int> _cachedConnectionIds = new Queue<int>();
/// <summary>
/// Socket for client host. Will be null if not being used.
/// </summary>
private ClientHostSocket _clientHost;
#endregion
/// <summary>
/// Resets the socket if invalid.
/// </summary>
internal void ResetInvalidSocket()
{
/* Force connection state to stopped if listener is invalid.
* Not sure if steam may change this internally so better
* safe than sorry and check before trying to connect
* rather than being stuck in the incorrect state. */
if (_socket == HSteamListenSocket.Invalid)
base.SetLocalConnectionState(LocalConnectionState.Stopped, true);
}
/// <summary>
/// Starts the server.
/// </summary>
internal bool StartConnection(string address, ushort port, int maximumClients, bool peerToPeer)
{
try
{
if (_onRemoteConnectionStateCallback == null)
_onRemoteConnectionStateCallback = Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnRemoteConnectionState);
base.PeerToPeer = peerToPeer;
//If address is required then make sure it can be parsed.
byte[] ip = (!peerToPeer) ? base.GetIPBytes(address) : null;
base.PeerToPeer = peerToPeer;
SetMaximumClients(maximumClients);
_nextConnectionId = 0;
_cachedConnectionIds.Clear();
base.SetLocalConnectionState(LocalConnectionState.Starting, true);
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
if (base.PeerToPeer)
{
#if UNITY_SERVER
_socket = SteamGameServerNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
#else
_socket = SteamNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
#endif
}
else
{
SteamNetworkingIPAddr addr = new SteamNetworkingIPAddr();
addr.Clear();
if (ip != null)
addr.SetIPv6(ip, port);
#if UNITY_SERVER
_socket = SteamGameServerNetworkingSockets.CreateListenSocketIP(ref addr, 0, options);
#else
_socket = SteamNetworkingSockets.CreateListenSocketIP(ref addr, 0, options);
#endif
}
}
catch
{
base.SetLocalConnectionState(LocalConnectionState.Stopped, true);
return false;
}
base.SetLocalConnectionState(LocalConnectionState.Started, true);
return true;
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
/* Try to close the socket before exiting early
* We never want to leave sockets open. */
if (_socket != HSteamListenSocket.Invalid)
{
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseListenSocket(_socket);
#else
SteamNetworkingSockets.CloseListenSocket(_socket);
#endif
if (_onRemoteConnectionStateCallback != null)
{
_onRemoteConnectionStateCallback.Dispose();
_onRemoteConnectionStateCallback = null;
}
_socket = HSteamListenSocket.Invalid;
}
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped)
return false;
base.SetLocalConnectionState(LocalConnectionState.Stopping, true);
base.SetLocalConnectionState(LocalConnectionState.Stopped, true);
return true;
}
/// <summary>
/// Stops a remote client from the server, disconnecting the client.
/// </summary>
/// <param name="connectionId">ConnectionId of the client to disconnect.</param>
internal bool StopConnection(int connectionId)
{
if (connectionId == FishySteamworks.CLIENT_HOST_ID)
{
if (_clientHost != null)
{
_clientHost.StopConnection();
return true;
}
else
{
return false;
}
}
//Remote client.
else
{
if (_steamConnections.Second.TryGetValue(connectionId, out HSteamNetConnection steamConn))
{
return StopConnection(connectionId, steamConn);
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Steam connection not found for connectionId {connectionId}.");
return false;
}
}
}
/// <summary>
/// Stops a remote client from the server, disconnecting the client.
/// </summary>
/// <param name="connectionId"></param>
/// <param name="socket"></param>
private bool StopConnection(int connectionId, HSteamNetConnection socket)
{
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseConnection(socket, 0, string.Empty, false);
#else
SteamNetworkingSockets.CloseConnection(socket, 0, string.Empty, false);
#endif
_steamConnections.Remove(connectionId);
_steamIds.Remove(connectionId);
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Client with ConnectionID {connectionId} disconnected.");
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Stopped, connectionId, Transport.Index));
_cachedConnectionIds.Enqueue(connectionId);
return true;
}
/// <summary>
/// Called when a remote connection state changes.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void OnRemoteConnectionState(SteamNetConnectionStatusChangedCallback_t args)
{
ulong clientSteamID = args.m_info.m_identityRemote.GetSteamID64();
if (args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting)
{
if (_steamConnections.Count >= GetMaximumClients())
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Incoming connection {clientSteamID} was rejected because would exceed the maximum connection count.");
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseConnection(args.m_hConn, 0, "Max Connection Count", false);
#else
SteamNetworkingSockets.CloseConnection(args.m_hConn, 0, "Max Connection Count", false);
#endif
return;
}
#if UNITY_SERVER
EResult res = SteamGameServerNetworkingSockets.AcceptConnection(args.m_hConn);
#else
EResult res = SteamNetworkingSockets.AcceptConnection(args.m_hConn);
#endif
if (res == EResult.k_EResultOK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Accepting connection {clientSteamID}");
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}");
}
}
else if (args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
int connectionId = (_cachedConnectionIds.Count > 0) ? _cachedConnectionIds.Dequeue() : _nextConnectionId++;
_steamConnections.Add(args.m_hConn, connectionId);
_steamIds.Add(args.m_info.m_identityRemote.GetSteamID(), connectionId);
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Started, connectionId, Transport.Index));
}
else if (args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || args.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
if (_steamConnections.TryGetValue(args.m_hConn, out int connId))
{
StopConnection(connId, args.m_hConn);
}
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection {clientSteamID} state changed: {args.m_info.m_eState.ToString()}");
}
}
/// <summary>
/// Allows for Outgoing queue to be iterated.
/// </summary>
internal void IterateOutgoing()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
foreach (HSteamNetConnection conn in _steamConnections.FirstTypes)
{
#if UNITY_SERVER
SteamGameServerNetworkingSockets.FlushMessagesOnConnection(conn);
#else
SteamNetworkingSockets.FlushMessagesOnConnection(conn);
#endif
}
}
/// <summary>
/// Iterates the Incoming queue.
/// </summary>
/// <param name="transport"></param>
internal void IterateIncoming()
{
//Stopped or trying to stop.
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return;
//Iterate local client packets first.
while (_clientHostIncoming.Count > 0)
{
LocalPacket packet = _clientHostIncoming.Dequeue();
ArraySegment<byte> segment = new ArraySegment<byte>(packet.Data, 0, packet.Length);
base.Transport.HandleServerReceivedDataArgs(new ServerReceivedDataArgs(segment, (Channel)packet.Channel, FishySteamworks.CLIENT_HOST_ID, Transport.Index));
}
foreach (KeyValuePair<HSteamNetConnection, int> item in _steamConnections.First)
{
HSteamNetConnection steamNetConn = item.Key;
int connectionId = item.Value;
int messageCount;
#if UNITY_SERVER
messageCount = SteamGameServerNetworkingSockets.ReceiveMessagesOnConnection(steamNetConn, base.MessagePointers, MAX_MESSAGES);
#else
messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(steamNetConn, base.MessagePointers, MAX_MESSAGES);
#endif
if (messageCount > 0)
{
for (int i = 0; i < messageCount; i++)
{
base.GetMessage(base.MessagePointers[i], InboundBuffer, out ArraySegment<byte> segment, out byte channel);
base.Transport.HandleServerReceivedDataArgs(new ServerReceivedDataArgs(segment, (Channel)channel, connectionId, Transport.Index));
}
}
}
}
/// <summary>
/// Sends data to a client.
/// </summary>
/// <param name="channelId"></param>
/// <param name="segment"></param>
/// <param name="connectionId"></param>
internal void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
//Check if sending local client first, send and exit if so.
if (connectionId == FishySteamworks.CLIENT_HOST_ID)
{
if (_clientHost != null)
{
LocalPacket packet = new LocalPacket(segment, channelId);
_clientHost.ReceivedFromLocalServer(packet);
}
return;
}
if (_steamConnections.TryGetValue(connectionId, out HSteamNetConnection steamConn))
{
EResult res = base.Send(steamConn, segment, channelId);
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection to {connectionId} was lost.");
StopConnection(connectionId, steamConn);
}
else if (res != EResult.k_EResultOK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Could not send: {res.ToString()}");
}
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"ConnectionId {connectionId} does not exist, data will not be sent.");
}
}
/// <summary>
/// Gets the address of a remote connection Id.
/// </summary>
/// <param name="connectionId"></param>
/// <returns></returns>
internal string GetConnectionAddress(int connectionId)
{
if (_steamIds.TryGetValue(connectionId, out CSteamID steamId))
{
return steamId.ToString();
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"ConnectionId {connectionId} is invalid; address cannot be returned.");
return string.Empty;
}
}
/// <summary>
/// Sets maximum number of clients allowed to connect to the server. If applied at runtime and clients exceed this value existing clients will stay connected but new clients may not connect.
/// </summary>
internal void SetMaximumClients(int value)
{
_maximumClients = Math.Min(value, FishySteamworks.CLIENT_HOST_ID - 1);
}
/// <summary>
/// Returns maximum number of allowed clients.
/// </summary>
/// <returns></returns>
internal int GetMaximumClients()
{
return _maximumClients;
}
#region ClientHost (local client).
/// <summary>
/// Sets ClientHost value.
/// </summary>
/// <param name="socket"></param>
internal void SetClientHostSocket(ClientHostSocket socket)
{
_clientHost = socket;
}
/// <summary>
/// Called when the local client state changes.
/// </summary>
internal void OnClientHostState(bool started)
{
FishySteamworks fs = (FishySteamworks)base.Transport;
CSteamID steamId = new CSteamID(fs.LocalUserSteamID);
//If not started but was previously flush incoming from local client.
if (!started && _clientHostStarted)
{
base.ClearQueue(_clientHostIncoming);
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Stopped, FishySteamworks.CLIENT_HOST_ID, Transport.Index));
_steamIds.Remove(steamId);
}
//If started.
else if (started)
{
_steamIds[steamId] = FishySteamworks.CLIENT_HOST_ID;
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Started, FishySteamworks.CLIENT_HOST_ID, Transport.Index));
}
_clientHostStarted = started;
}
/// <summary>
/// Queues a received packet from the local client.
/// </summary>
internal void ReceivedFromClientHost(LocalPacket packet)
{
if (!_clientHostStarted)
return;
_clientHostIncoming.Enqueue(packet);
}
#endregion
}
}
#endif // !DISABLESTEAMWORKS

View File

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

View File

@@ -0,0 +1,585 @@
#if !FISHYSTEAMWORKS
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Transporting;
using Steamworks;
using System;
using System.IO;
using UnityEngine;
namespace FishySteamworks
{
public class FishySteamworks : Transport
{
~FishySteamworks()
{
Shutdown();
}
#region Public.
/// <summary>
/// The SteamId for the local user after connecting to or starting the server. This is populated automatically.
/// </summary>
[System.NonSerialized]
public ulong LocalUserSteamID = 0;
#endregion
#region Serialized.
/// <summary>
/// Steam application Id.
/// </summary>
[Tooltip("Steam application Id.")]
[SerializeField]
private ulong _steamAppID = 480;
/// <summary>
/// Address server should bind to.
/// </summary>
[Tooltip("Address server should bind to.")]
[SerializeField]
private string _serverBindAddress = string.Empty;
/// <summary>
/// Port to use.
/// </summary>
[Tooltip("Port to use.")]
[SerializeField]
private ushort _port = 7770;
/// <summary>
/// Maximum number of players which may be connected at once.
/// </summary>
[Tooltip("Maximum number of players which may be connected at once.")]
[Range(1, ushort.MaxValue)]
[SerializeField]
private ushort _maximumClients = 9001;
/// <summary>
/// True if using peer to peer socket.
/// </summary>
[Tooltip("True if using peer to peer socket.")]
[SerializeField]
private bool _peerToPeer = false;
/// <summary>
/// Address client should connect to.
/// </summary>
[Tooltip("Address client should connect to.")]
[SerializeField]
private string _clientAddress = string.Empty;
#endregion
#region Private.
/// <summary>
/// MTUs for each channel.
/// </summary>
private int[] _mtus;
/// <summary>
/// Client when acting as client only.
/// </summary>
private Client.ClientSocket _client;
/// <summary>
/// Client when acting as host.
/// </summary>
private Client.ClientHostSocket _clientHost;
/// <summary>
/// Server for the transport.
/// </summary>
private Server.ServerSocket _server;
#endregion
#region Const.
/// <summary>
/// Id to use for client when acting as host.
/// </summary>
internal const int CLIENT_HOST_ID = short.MaxValue;
#endregion
public override void Initialize(NetworkManager networkManager, int transportIndex)
{
base.Initialize(networkManager, transportIndex);
_client = new Client.ClientSocket();
_clientHost = new Client.ClientHostSocket();
_server = new Server.ServerSocket();
CreateChannelData();
WriteSteamAppId();
_client.Initialize(this);
_clientHost.Initialize(this);
_server.Initialize(this);
}
private void OnDestroy()
{
Shutdown();
}
private void Update()
{
_clientHost.CheckSetStarted();
}
#region Setup.
/// <summary>
/// Creates ChannelData for the transport.
/// </summary>
private void CreateChannelData()
{
_mtus = new int[2]
{
1048576,
1200
};
}
/// <summary>
/// Writes SteamAppId to file.
/// </summary>
private void WriteSteamAppId()
{
string fileName = "steam_appid.txt";
string appIdText = _steamAppID.ToString();
try
{
if (File.Exists(fileName))
{
string content = File.ReadAllText(fileName);
if (content != appIdText)
{
File.WriteAllText(fileName, appIdText);
Debug.Log($"SteamId has been updated from {content} to {appIdText} within {fileName}.");
}
}
else
{
File.WriteAllText(fileName, appIdText);
Debug.Log($"SteamId {appIdText} has been set within {fileName}.");
}
}
catch (Exception ex)
{
Debug.LogError($"There was an exception when trying to write {appIdText} to {fileName}: {ex.Message}");
}
}
/// <summary>
/// Tries to initialize steam network access.
/// </summary>
private bool InitializeRelayNetworkAccess()
{
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
if (IsNetworkAccessAvailable())
LocalUserSteamID = SteamUser.GetSteamID().m_SteamID;
#endif
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Returns if network access is available.
/// </summary>
public bool IsNetworkAccessAvailable()
{
try
{
#if UNITY_SERVER
InteropHelp.TestIfAvailableGameServer();
#else
InteropHelp.TestIfAvailableClient();
#endif
return true;
}
catch
{
return false;
}
}
#endregion
#region ConnectionStates.
/// <summary>
/// Gets the IP address of a remote connection Id.
/// </summary>
/// <param name="connectionId"></param>
/// <returns></returns>
public override string GetConnectionAddress(int connectionId)
{
return _server.GetConnectionAddress(connectionId);
}
/// <summary>
/// Called when a connection state changes for the local client.
/// </summary>
public override event Action<ClientConnectionStateArgs> OnClientConnectionState;
/// <summary>
/// Called when a connection state changes for the local server.
/// </summary>
public override event Action<ServerConnectionStateArgs> OnServerConnectionState;
/// <summary>
/// Called when a connection state changes for a remote client.
/// </summary>
public override event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
/// <summary>
/// Gets the current local ConnectionState.
/// </summary>
/// <param name="server">True if getting ConnectionState for the server.</param>
public override LocalConnectionState GetConnectionState(bool server)
{
if (server)
return _server.GetLocalConnectionState();
else
return _client.GetLocalConnectionState();
}
/// <summary>
/// Gets the current ConnectionState of a remote client on the server.
/// </summary>
/// <param name="connectionId">ConnectionId to get ConnectionState for.</param>
public override RemoteConnectionState GetConnectionState(int connectionId)
{
return _server.GetConnectionState(connectionId);
}
/// <summary>
/// Handles a ConnectionStateArgs for the local client.
/// </summary>
/// <param name="connectionStateArgs"></param>
public override void HandleClientConnectionState(ClientConnectionStateArgs connectionStateArgs)
{
OnClientConnectionState?.Invoke(connectionStateArgs);
}
/// <summary>
/// Handles a ConnectionStateArgs for the local server.
/// </summary>
/// <param name="connectionStateArgs"></param>
public override void HandleServerConnectionState(ServerConnectionStateArgs connectionStateArgs)
{
OnServerConnectionState?.Invoke(connectionStateArgs);
}
/// <summary>
/// Handles a ConnectionStateArgs for a remote client.
/// </summary>
/// <param name="connectionStateArgs"></param>
public override void HandleRemoteConnectionState(RemoteConnectionStateArgs connectionStateArgs)
{
OnRemoteConnectionState?.Invoke(connectionStateArgs);
}
#endregion
#region Iterating.
/// <summary>
/// Processes data received by the socket.
/// </summary>
/// <param name="server">True to process data received on the server.</param>
public override void IterateIncoming(bool server)
{
if (server)
{
_server.IterateIncoming();
}
else
{
_client.IterateIncoming();
_clientHost.IterateIncoming();
}
}
/// <summary>
/// Processes data to be sent by the socket.
/// </summary>
/// <param name="server">True to process data received on the server.</param>
public override void IterateOutgoing(bool server)
{
if (server)
_server.IterateOutgoing();
else
_client.IterateOutgoing();
}
#endregion
#region ReceivedData.
/// <summary>
/// Called when client receives data.
/// </summary>
public override event Action<ClientReceivedDataArgs> OnClientReceivedData;
/// <summary>
/// Handles a ClientReceivedDataArgs.
/// </summary>
/// <param name="receivedDataArgs"></param>
public override void HandleClientReceivedDataArgs(ClientReceivedDataArgs receivedDataArgs)
{
OnClientReceivedData?.Invoke(receivedDataArgs);
}
/// <summary>
/// Called when server receives data.
/// </summary>
public override event Action<ServerReceivedDataArgs> OnServerReceivedData;
/// <summary>
/// Handles a ClientReceivedDataArgs.
/// </summary>
/// <param name="receivedDataArgs"></param>
public override void HandleServerReceivedDataArgs(ServerReceivedDataArgs receivedDataArgs)
{
OnServerReceivedData?.Invoke(receivedDataArgs);
}
#endregion
#region Sending.
/// <summary>
/// Sends to the server or all clients.
/// </summary>
/// <param name="channelId">Channel to use.</param>
/// /// <param name="segment">Data to send.</param>
public override void SendToServer(byte channelId, ArraySegment<byte> segment)
{
_client.SendToServer(channelId, segment);
_clientHost.SendToServer(channelId, segment);
}
/// <summary>
/// Sends data to a client.
/// </summary>
/// <param name="channelId"></param>
/// <param name="segment"></param>
/// <param name="connectionId"></param>
public override void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
{
//uint tick = BitConverter.ToUInt32(segment.Array, 0);
//ushort val = BitConverter.ToUInt16(segment.Array, 4);
//Debug.Log(tick + ", " + val);
_server.SendToClient(channelId, segment, connectionId);
}
#endregion
#region Configuration.
/// <summary>
/// Returns the maximum number of clients allowed to connect to the server. If the transport does not support this method the value -1 is returned.
/// </summary>
/// <returns></returns>
public override int GetMaximumClients()
{
return _server.GetMaximumClients();
}
/// <summary>
/// Sets maximum number of clients allowed to connect to the server. If applied at runtime and clients exceed this value existing clients will stay connected but new clients may not connect.
/// </summary>
/// <param name="value"></param>
public override void SetMaximumClients(int value)
{
_server.SetMaximumClients(value);
}
/// <summary>
/// Sets which address the client will connect to.
/// </summary>
/// <param name="address"></param>
public override void SetClientAddress(string address)
{
_clientAddress = address;
}
/// <summary>
/// Sets which address the server will bind to.
/// </summary>
/// <param name="address"></param>
public override void SetServerBindAddress(string address, IPAddressType addressType)
{
_serverBindAddress = address;
}
/// <summary>
/// Sets which port to use.
/// </summary>
/// <param name="port"></param>
public override void SetPort(ushort port)
{
_port = port;
}
#endregion
#region Start and stop.
/// <summary>
/// Starts the local server or client using configured settings.
/// </summary>
/// <param name="server">True to start server.</param>
public override bool StartConnection(bool server)
{
if (server)
return StartServer();
else
return StartClient(_clientAddress);
}
/// <summary>
/// Stops the local server or client.
/// </summary>
/// <param name="server">True to stop server.</param>
public override bool StopConnection(bool server)
{
if (server)
return StopServer();
else
return StopClient();
}
/// <summary>
/// Stops a remote client from the server, disconnecting the client.
/// </summary>
/// <param name="connectionId">ConnectionId of the client to disconnect.</param>
/// <param name="immediately">True to abrutly stp the client socket without waiting socket thread.</param>
public override bool StopConnection(int connectionId, bool immediately)
{
return StopClient(connectionId, immediately);
}
/// <summary>
/// Stops both client and server.
/// </summary>
public override void Shutdown()
{
//Stops client then server connections.
StopConnection(false);
StopConnection(true);
}
#region Privates.
/// <summary>
/// Starts server.
/// </summary>
/// <returns>True if there were no blocks. A true response does not promise a socket will or has connected.</returns>
private bool StartServer()
{
if (!InitializeRelayNetworkAccess())
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"RelayNetworkAccess could not be initialized.");
return false;
}
if (!IsNetworkAccessAvailable())
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("Server network access is not available.");
return false;
}
_server.ResetInvalidSocket();
if (_server.GetLocalConnectionState() != LocalConnectionState.Stopped)
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("Server is already running.");
return false;
}
bool clientRunning = (_client.GetLocalConnectionState() != LocalConnectionState.Stopped);
/* If remote _client is running then stop it
* and start the client host variant. */
if (clientRunning)
_client.StopConnection();
bool result = _server.StartConnection(_serverBindAddress, _port, _maximumClients, _peerToPeer);
//If need to restart client.
if (result && clientRunning)
StartConnection(false);
return result;
}
/// <summary>
/// Stops server.
/// </summary>
private bool StopServer()
{
if (_server != null)
return _server.StopConnection();
return false;
}
/// <summary>
/// Starts the client.
/// </summary>
/// <param name="address"></param>
/// <returns>True if there were no blocks. A true response does not promise a socket will or has connected.</returns>
private bool StartClient(string address)
{
//If not acting as a host.
if (_server.GetLocalConnectionState() == LocalConnectionState.Stopped)
{
if (_client.GetLocalConnectionState() != LocalConnectionState.Stopped)
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("Client is already running.");
return false;
}
//Stop client host if running.
if (_clientHost.GetLocalConnectionState() != LocalConnectionState.Stopped)
_clientHost.StopConnection();
//Initialize.
if (!InitializeRelayNetworkAccess())
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"RelayNetworkAccess could not be initialized.");
return false;
}
if (!IsNetworkAccessAvailable())
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("Client network access is not available.");
return false;
}
//SetUserSteamID();
_client.StartConnection(address, _port, _peerToPeer);
}
//Acting as host.
else
{
_clientHost.StartConnection(_server);
}
return true;
}
/// <summary>
/// Stops the client.
/// </summary>
private bool StopClient()
{
bool result = false;
if (_client != null)
result |= _client.StopConnection();
if (_clientHost != null)
result |= _clientHost.StopConnection();
return result;
}
/// <summary>
/// Stops a remote client on the server.
/// </summary>
/// <param name="connectionId"></param>
/// <param name="immediately">True to abrutly stp the client socket without waiting socket thread.</param>
private bool StopClient(int connectionId, bool immediately)
{
return _server.StopConnection(connectionId);
}
#endregion
#endregion
#region Channels.
/// <summary>
/// Gets the MTU for a channel. This should take header size into consideration.
/// For example, if MTU is 1200 and a packet header for this channel is 10 in size, this method should return 1190.
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
public override int GetMTU(byte channel)
{
if (channel >= _mtus.Length)
{
Debug.LogError($"Channel {channel} is out of bounds.");
return 0;
}
return _mtus[channel];
}
#endregion
}
}
#endif // !DISABLESTEAMWORKS

View File

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

View File

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

View File

@@ -0,0 +1 @@
2.1.2

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
{
"name": "GameKit.Dependencies"
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,488 @@
#if UNITY_EDITOR
// Project : UNITY FOLDOUT
// Contacts : Pix - ask@pixeye.games
// https://github.com/PixeyeHQ/InspectorFoldoutGroup
// MIT license https://github.com/PixeyeHQ/InspectorFoldoutGroup/blob/master/LICENSE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace GameKit.Dependencies.Inspectors
{
[CustomEditor(typeof(Object), true, isFallback = true)]
[CanEditMultipleObjects]
public class EditorOverride : Editor
{
public override VisualElement CreateInspectorGUI()
{
return base.CreateInspectorGUI();
}
//===============================//
// Members
//===============================//
Dictionary<string, CacheFoldProp> cacheFolds = new Dictionary<string, CacheFoldProp>();
List<SerializedProperty> props = new List<SerializedProperty>();
List<MethodInfo> methods = new List<MethodInfo>();
bool initialized;
//===============================//
// Logic
//===============================//
void OnEnable()
{
initialized = false;
}
void OnDisable()
{
//if (Application.wantsToQuit)
//if (applicationIsQuitting) return;
// if (Toolbox.isQuittingOrChangingScene()) return;
if (target != null)
foreach (var c in cacheFolds)
{
EditorPrefs.SetBool(string.Format($"{c.Value.atr.name}{c.Value.props[0].name}{target.GetInstanceID()}"), c.Value.expanded);
c.Value.Dispose();
}
}
public override bool RequiresConstantRepaint()
{
return EditorFramework.needToRepaint;
}
public override void OnInspectorGUI()
{
serializedObject.Update();
Setup();
if (props.Count == 0)
{
DrawDefaultInspector();
return;
}
Header();
Body();
serializedObject.ApplyModifiedProperties();
void Header()
{
using (new EditorGUI.DisabledScope("m_Script" == props[0].propertyPath))
{
EditorGUILayout.Space();
EditorGUILayout.PropertyField(props[0], true);
EditorGUILayout.Space();
}
}
void Body()
{
foreach (var pair in cacheFolds)
{
this.UseVerticalLayout(() => Foldout(pair.Value), StyleFramework.box);
EditorGUI.indentLevel = 0;
}
EditorGUILayout.Space();
for (var i = 1; i < props.Count; i++)
{
// if (props[i].isArray)
// {
// DrawPropertySortableArray(props[i]);
// }
// else
// {
EditorGUILayout.PropertyField(props[i], true);
//}
}
EditorGUILayout.Space();
if (methods == null) return;
foreach (MethodInfo memberInfo in methods)
{
this.UseButton(memberInfo);
}
}
void Foldout(CacheFoldProp cache)
{
cache.expanded = EditorGUILayout.Foldout(cache.expanded, cache.atr.name, true,
StyleFramework.foldout);
if (cache.expanded)
{
EditorGUI.indentLevel = 1;
for (int i = 0; i < cache.props.Count; i++)
{
this.UseVerticalLayout(() => Child(i), StyleFramework.boxChild);
}
}
void Child(int i)
{
// if (cache.props[i].isArray)
// {
// DrawPropertySortableArray(cache.props[i]);
// }
// else
// {
EditorGUILayout.PropertyField(cache.props[i], new GUIContent(ObjectNames.NicifyVariableName(cache.props[i].name)), true);
//}
}
}
void Setup()
{
EditorFramework.currentEvent = Event.current;
if (!initialized)
{
// SetupButtons();
List<FieldInfo> objectFields;
GroupAttribute prevFold = default;
var length = EditorTypes.Get(target, out objectFields);
for (var i = 0; i < length; i++)
{
#region FOLDERS
var fold = Attribute.GetCustomAttribute(objectFields[i], typeof(GroupAttribute)) as GroupAttribute;
CacheFoldProp c;
if (fold == null)
{
if (prevFold != null && prevFold.foldEverything)
{
if (!cacheFolds.TryGetValue(prevFold.name, out c))
{
cacheFolds.Add(prevFold.name, new CacheFoldProp { atr = prevFold, types = new HashSet<string> { objectFields[i].Name } });
}
else
{
c.types.Add(objectFields[i].Name);
}
}
continue;
}
prevFold = fold;
if (!cacheFolds.TryGetValue(fold.name, out c))
{
var expanded = EditorPrefs.GetBool(string.Format($"{fold.name}{objectFields[i].Name}{target.GetInstanceID()}"), false);
cacheFolds.Add(fold.name, new CacheFoldProp { atr = fold, types = new HashSet<string> { objectFields[i].Name }, expanded = expanded });
}
else c.types.Add(objectFields[i].Name);
#endregion
}
var property = serializedObject.GetIterator();
var next = property.NextVisible(true);
if (next)
{
do
{
HandleFoldProp(property);
} while (property.NextVisible(false));
}
initialized = true;
}
}
// void SetupButtons()
// {
// var members = GetButtonMembers(target);
//
// foreach (var memberInfo in members)
// {
// var method = memberInfo as MethodInfo;
// if (method == null)
// {
// continue;
// }
//
// if (method.GetParameters().Length > 0)
// {
// continue;
// }
//
// if (methods == null) methods = new List<MethodInfo>();
// methods.Add(method);
// }
// }
}
public void HandleFoldProp(SerializedProperty prop)
{
bool shouldBeFolded = false;
foreach (var pair in cacheFolds)
{
if (pair.Value.types.Contains(prop.name))
{
var pr = prop.Copy();
shouldBeFolded = true;
pair.Value.props.Add(pr);
break;
}
}
if (shouldBeFolded == false)
{
var pr = prop.Copy();
props.Add(pr);
}
}
// IEnumerable<MemberInfo> GetButtonMembers(object target)
// {
// return target.GetType()
// .GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
// .Where(CheckButtonAttribute);
// }
// bool CheckButtonAttribute(MemberInfo memberInfo)
// {
// return Attribute.IsDefined(memberInfo, typeof(ButtonAttribute));
// }
class CacheFoldProp
{
public HashSet<string> types = new HashSet<string>();
public List<SerializedProperty> props = new List<SerializedProperty>();
public GroupAttribute atr;
public bool expanded;
public void Dispose()
{
props.Clear();
types.Clear();
atr = null;
}
}
}
static class EditorUIHelper
{
public static void UseVerticalLayout(this Editor e, Action action, GUIStyle style)
{
EditorGUILayout.BeginVertical(style);
action();
EditorGUILayout.EndVertical();
}
public static void UseButton(this Editor e, MethodInfo m)
{
if (GUILayout.Button(m.Name))
{
m.Invoke(e.target, null);
}
}
}
static class StyleFramework
{
public static GUIStyle box;
public static GUIStyle boxChild;
public static GUIStyle foldout;
public static GUIStyle button;
public static GUIStyle text;
static StyleFramework()
{
bool pro = EditorGUIUtility.isProSkin;
var uiTex_in = UnityEngine.Resources.Load<Texture2D>("IN foldout focus-6510");
var uiTex_in_on = UnityEngine.Resources.Load<Texture2D>("IN foldout focus on-5718");
var c_on = pro ? Color.white : new Color(51 / 255f, 102 / 255f, 204 / 255f, 1);
button = new GUIStyle(EditorStyles.miniButton);
button.font = Font.CreateDynamicFontFromOSFont(new[] { "Terminus (TTF) for Windows", "Calibri" }, 17);
text = new GUIStyle(EditorStyles.label);
text.richText = true;
text.contentOffset = new Vector2(0, 5);
text.font = Font.CreateDynamicFontFromOSFont(new[] { "Terminus (TTF) for Windows", "Calibri" }, 14);
foldout = new GUIStyle(EditorStyles.foldout);
foldout.overflow = new RectOffset(-10, 0, 3, 0);
foldout.padding = new RectOffset(25, 0, -3, 0);
foldout.active.textColor = c_on;
foldout.active.background = uiTex_in;
foldout.onActive.textColor = c_on;
foldout.onActive.background = uiTex_in_on;
foldout.focused.textColor = c_on;
foldout.focused.background = uiTex_in;
foldout.onFocused.textColor = c_on;
foldout.onFocused.background = uiTex_in_on;
foldout.hover.textColor = c_on;
foldout.hover.background = uiTex_in;
foldout.onHover.textColor = c_on;
foldout.onHover.background = uiTex_in_on;
box = new GUIStyle(GUI.skin.box);
box.padding = new RectOffset(10, 0, 10, 0);
boxChild = new GUIStyle(GUI.skin.box);
boxChild.active.textColor = c_on;
boxChild.active.background = uiTex_in;
boxChild.onActive.textColor = c_on;
boxChild.onActive.background = uiTex_in_on;
boxChild.focused.textColor = c_on;
boxChild.focused.background = uiTex_in;
boxChild.onFocused.textColor = c_on;
boxChild.onFocused.background = uiTex_in_on;
EditorStyles.foldout.active.textColor = c_on;
EditorStyles.foldout.active.background = uiTex_in;
EditorStyles.foldout.onActive.textColor = c_on;
EditorStyles.foldout.onActive.background = uiTex_in_on;
EditorStyles.foldout.focused.textColor = c_on;
EditorStyles.foldout.focused.background = uiTex_in;
EditorStyles.foldout.onFocused.textColor = c_on;
EditorStyles.foldout.onFocused.background = uiTex_in_on;
EditorStyles.foldout.hover.textColor = c_on;
EditorStyles.foldout.hover.background = uiTex_in;
EditorStyles.foldout.onHover.textColor = c_on;
EditorStyles.foldout.onHover.background = uiTex_in_on;
}
public static string FirstLetterToUpperCase(this string s)
{
if (string.IsNullOrEmpty(s))
return string.Empty;
var a = s.ToCharArray();
a[0] = char.ToUpper(a[0]);
return new string(a);
}
public static IList<Type> GetTypeTree(this Type t)
{
var types = new List<Type>();
while (t.BaseType != null)
{
types.Add(t);
t = t.BaseType;
}
return types;
}
}
static class EditorTypes
{
public static Dictionary<int, List<FieldInfo>> fields = new Dictionary<int, List<FieldInfo>>(FastComparable.Default);
public static int Get(Object target, out List<FieldInfo> objectFields)
{
var t = target.GetType();
var hash = t.GetHashCode();
if (!fields.TryGetValue(hash, out objectFields))
{
var typeTree = t.GetTypeTree();
objectFields = target.GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
.OrderByDescending(x => typeTree.IndexOf(x.DeclaringType))
.ToList();
fields.Add(hash, objectFields);
}
return objectFields.Count;
}
}
class FastComparable : IEqualityComparer<int>
{
public static FastComparable Default = new FastComparable();
public bool Equals(int x, int y)
{
return x == y;
}
public int GetHashCode(int obj)
{
return obj.GetHashCode();
}
}
[InitializeOnLoad]
public static class EditorFramework
{
internal static bool needToRepaint;
internal static Event currentEvent;
internal static float t;
static EditorFramework()
{
EditorApplication.update += Updating;
}
static void Updating()
{
CheckMouse();
if (needToRepaint)
{
t += Time.deltaTime;
if (t >= 0.3f)
{
t -= 0.3f;
needToRepaint = false;
}
}
}
static void CheckMouse()
{
var ev = currentEvent;
if (ev == null) return;
if (ev.type == EventType.MouseMove)
needToRepaint = true;
}
}
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 07896d08487bbb049b494e9e216360ad
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
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
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
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
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 795626f49ade9024a86e0c1a58aa004b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
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
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
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
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
// Project : UNITY FOLDOUT
// Contacts : Pix - ask@pixeye.games
// https://github.com/PixeyeHQ/InspectorFoldoutGroup
// MIT license https://github.com/PixeyeHQ/InspectorFoldoutGroup/blob/master/LICENSE
using System;
using UnityEngine;
namespace GameKit.Dependencies.Inspectors
{
public class GroupAttribute : PropertyAttribute
{
public string name;
public bool foldEverything;
/// <summary>Adds the property to the specified foldout group.</summary>
/// <param name="name">Name of the foldout group.</param>
/// <param name="foldEverything">Toggle to put all properties to the specified group</param>
public GroupAttribute(string name, bool foldEverything = false)
{
this.foldEverything = foldEverything;
this.name = name;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,118 @@
#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace GameKit.Dependencies.Inspectors
{
/// <summary>
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
/// </summary>
[CustomPropertyDrawer(typeof(ShowIfAttribute))]
public class ShowIfPropertyDrawer : PropertyDrawer
{
#region Fields
// Reference to the attribute on the property.
ShowIfAttribute drawIf;
// Field that is being compared.
SerializedProperty comparedField;
#endregion
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (!ShowMe(property) && drawIf.disablingType == ShowIfAttribute.DisablingType.DontDraw)
{
return -EditorGUIUtility.standardVerticalSpacing;
}
else
{
if (property.propertyType == SerializedPropertyType.Generic)
{
int numChildren = 0;
float totalHeight = 0.0f;
IEnumerator children = property.GetEnumerator();
HashSet<SerializedProperty> drawnprops = new HashSet<SerializedProperty>();
while (children.MoveNext())
{
SerializedProperty child = children.Current as SerializedProperty;
if (drawnprops.Contains(child))
{
continue;
}
drawnprops.Add(child);
GUIContent childLabel = new GUIContent(child.displayName);
totalHeight += EditorGUI.GetPropertyHeight(child, childLabel) + EditorGUIUtility.standardVerticalSpacing;
numChildren++;
}
// Remove extra space at end, (we only want spaces between items)
totalHeight -= EditorGUIUtility.standardVerticalSpacing;
return totalHeight;
}
return EditorGUI.GetPropertyHeight(property, label);
}
}
/// <summary>
/// Errors default to showing the property.
/// </summary>
private bool ShowMe(SerializedProperty property)
{
drawIf = attribute as ShowIfAttribute;
// Replace propertyname to the value from the parameter
string path = property.propertyPath.Contains(".") ? System.IO.Path.ChangeExtension(property.propertyPath, drawIf.comparedPropertyName) : drawIf.comparedPropertyName;
comparedField = property.serializedObject.FindProperty(path);
if (comparedField == null)
{
Debug.LogError("Cannot find property with name: " + path);
return true;
}
// get the value & compare based on types
switch (comparedField.type)
{ // Possible extend cases to support your own type
case "bool":
return comparedField.boolValue.Equals(drawIf.comparedValue);
case "Enum":
return comparedField.enumValueIndex.Equals((int)drawIf.comparedValue);
default:
Debug.LogError("Error: " + comparedField.type + " is not supported of " + path);
return true;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// If the condition is met, simply draw the field.
if (ShowMe(property))
{
EditorGUI.PropertyField(position, property);
} //...check if the disabling type is read only. If it is, draw it disabled
else if (drawIf.disablingType == ShowIfAttribute.DisablingType.ReadOnly)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property);
GUI.enabled = true;
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,45 @@
using UnityEngine;
using System;
namespace GameKit.Dependencies.Inspectors
{
/// <summary>
/// Draws the field/property ONLY if the compared property compared by the comparison type with the value of comparedValue returns true.
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class ShowIfAttribute : PropertyAttribute
{
#region Fields
public string comparedPropertyName { get; private set; }
public object comparedValue { get; private set; }
public DisablingType disablingType { get; private set; }
/// <summary>
/// Types of comperisons.
/// </summary>
public enum DisablingType
{
ReadOnly = 2,
DontDraw = 3
}
#endregion
/// <summary>
/// Only draws the field only if a condition is met. Supports enum and bools.
/// </summary>
/// <param name="comparedPropertyName">The name of the property that is being compared (case sensitive).</param>
/// <param name="comparedValue">The value the property is being compared to.</param>
/// <param name="disablingType">The type of disabling that should happen if the condition is NOT met. Defaulted to DisablingType.DontDraw.</param>
public ShowIfAttribute(string comparedPropertyName, object comparedValue, DisablingType disablingType = DisablingType.DontDraw)
{
this.comparedPropertyName = comparedPropertyName;
this.comparedValue = comparedValue;
this.disablingType = disablingType;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GameKit.Utilities
{
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public static class ApplicationState
{
#if !UNITY_EDITOR
/// <summary>
/// True if application is quitting.
/// </summary>
private static bool _isQuitting;
#endif
static ApplicationState()
{
#if !UNITY_EDITOR
_isQuitting = false;
#endif
Application.quitting -= Application_quitting;
Application.quitting += Application_quitting;
}
private static void Application_quitting()
{
#if !UNITY_EDITOR
_isQuitting = true;
#endif
}
/// <summary>
/// Returns if the application is quitting for editor or builds.
/// </summary>
/// <returns></returns>
public static bool IsQuitting()
{
#if UNITY_EDITOR
if (!EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying)
return true;
else
return false;
#else
return _isQuitting;
#endif
}
/// <summary>
/// Returns if the application is playing for editor or builds.
/// </summary>
/// <returns></returns>
public static bool IsPlaying()
{
#if UNITY_EDITOR
return EditorApplication.isPlaying;
#else
return Application.isPlaying;
#endif
}
/// <summary>
/// Quits the application for editor or builds.
/// </summary>
public static void Quit()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,97 @@
using System.Collections.Generic;
namespace GameKit.Utilities
{
public static class Arrays
{
/// <summary>
/// Randomizer used for shuffling.
/// </summary>
private static System.Random _random = new System.Random();
/// <summary>
/// Adds an entry to a list if it does not exist already.
/// </summary>
/// <returns>True if the entry was added.</returns>
public static bool AddUnique<T>(this List<T> list, T value)
{
bool contains = list.Contains((T)value);
if (!contains)
list.Add((T)value);
return !contains;
}
/// <summary>
/// Removes an object from a list through re-ordering. This breaks the order of the list for a faster remove.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool FastReferenceRemove<T>(this List<T> list, object value)
{
for (int i = 0; i < list.Count; i++)
{
if (object.ReferenceEquals(list[i], value))
{
FastIndexRemove(list, i);
return true;
}
}
return false;
}
/// <summary>
/// Removes an index from a list through re-ordering. This breaks the order of the list for a faster remove.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="index"></param>
public static void FastIndexRemove<T>(this List<T> list, int index)
{
list[index] = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
}
/// <summary>
/// Shuffles an array.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="array"></param>
public static void Shuffle<T>(this T[] array)
{
int n = array.Length;
for (int i = 0; i < (n - 1); i++)
{
int r = i + _random.Next(n - i);
T t = array[r];
array[r] = array[i];
array[i] = t;
}
}
/// <summary>
/// Shuffles a list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="lst"></param>
public static void Shuffle<T>(this List<T> lst)
{
int n = lst.Count;
for (int i = 0; i < (n - 1); i++)
{
int r = i + _random.Next(n - i);
T t = lst[r];
lst[r] = lst[i];
lst[i] = t;
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1b93eae9ff81b3e4b892128ca4b392ed
timeCreated: 1530140103
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
namespace GameKit.Utilities
{
public static class Booleans
{
/// <summary>
/// Converts a boolean to an integer, 1 for true 0 for false.
/// </summary>
public static int ToInt(this bool b)
{
return (b) ? 1 : 0;
}
}
}

View File

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

View File

@@ -0,0 +1,93 @@
using UnityEngine;
namespace GameKit.Utilities
{
/// <summary>
/// Ways a CanvasGroup can have it's blocking properties modified.
/// </summary>
public enum CanvasGroupBlockingType
{
Unchanged = 0,
DoNotBlock = 1,
Block = 2,
}
public static class CanvaseGroups
{
public static void SetBlockingType(this CanvasGroup group, CanvasGroupBlockingType blockingType)
{
if (blockingType == CanvasGroupBlockingType.Unchanged)
return;
bool block = (blockingType == CanvasGroupBlockingType.Block);
group.blocksRaycasts = block;
group.interactable = block;
}
/// <summary>
/// Sets a CanvasGroup blocking type and alpha.
/// </summary>
/// <param name="blockingType">How to handle interactions.</param>
/// <param name="alpha">Alpha for CanvasGroup.</param>
public static void SetActive(this CanvasGroup group, CanvasGroupBlockingType blockingType, float alpha)
{
group.SetBlockingType(blockingType);
group.alpha = alpha;
}
/// <summary>
/// Sets a canvasGroup active with specified alpha.
/// </summary>
public static void SetActive(this CanvasGroup group, float alpha)
{
group.SetActive(true, false);
group.alpha = alpha;
}
/// <summary>
/// Sets a canvasGroup inactive with specified alpha.
/// </summary>
public static void SetInactive(this CanvasGroup group, float alpha)
{
group.SetActive(false, false);
group.alpha = alpha;
}
/// <summary>
/// Sets a group active state by changing alpha and interaction toggles.
/// </summary>
public static void SetActive(this CanvasGroup group, bool active, bool setAlpha)
{
if (group == null)
return;
if (setAlpha)
{
if (active)
group.alpha = 1f;
else
group.alpha = 0f;
}
group.interactable = active;
group.blocksRaycasts = active;
}
/// <summary>
/// Sets a group active state by changing alpha and interaction toggles with a custom alpha.
/// </summary>
public static void SetActive(this CanvasGroup group, bool active, float alpha)
{
if (group == null)
return;
group.alpha = alpha;
group.interactable = active;
group.blocksRaycasts = active;
}
}
}

View File

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

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace GameKit.Utilities
{
public static class DictionaryFN
{
/// <summary>
/// Uses a hacky way to TryGetValue on a dictionary when using IL2CPP and on mobile.
/// This is to support older devices that don't properly handle IL2CPP builds.
/// </summary>
public static bool TryGetValueIL2CPP<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, out TValue value)
{
#if ENABLE_IL2CPP && UNITY_IOS || UNITY_ANDROID
if (dict.ContainsKey(key))
{
value = dict[key];
return true;
}
else
{
value = default;
return false;
}
#else
return dict.TryGetValue(key, out value);
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,102 @@
using System;
using System.IO;
using UnityEngine;
namespace GameKit.Utilities
{
public static class Disks
{
/// <summary>
/// Writes specified text to a file path.
/// </summary>
/// <param name="text"></param>
/// <param name="path"></param>
/// <param name="formatPath">True to format the path to the current platform.</param>
public static void WriteToFile(string text, string path, bool formatPath = true)
{
//If to format the path for the platform.
if (formatPath)
path = FormatPlatformPath(path);
//Path came back or was passed in as an empty string.
if (path == string.Empty)
{
Debug.LogError("Path cannot be null.");
return;
}
try
{
//Get directory path.
string directory = Path.GetDirectoryName(path);
//If directory doesn't exist try to create it.
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
//Try to write the file data.
using (FileStream fs = new FileStream(path, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(fs))
writer.Write(text);
}
}
catch (Exception ex)
{
Debug.LogError($"An error occured during a file write. Error: {ex.Message} {Environment.NewLine} File path: {path} {Environment.NewLine} Text: {text}");
}
/* If within the editor then refresh the asset database so changes
* reflect in the project folder. */
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
}
/// <summary>
/// Formats a file path to the current platform.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string FormatPlatformPath(string path)
{
//No path specified.
if (path == string.Empty)
{
Debug.LogError("Path cannot be empty.");
return string.Empty;
}
string convertedPath = string.Empty;
//Get the directories as an array.
string[] directories = path.Split(Path.DirectorySeparatorChar);
//Go through each directory.
for (int i = 0; i < directories.Length; i++)
{
/* If only one entry in array then the path
* is in the root of the Resources folder. */
if (directories.Length == 1)
{
//Append to converted path and break from the loop.
convertedPath = directories[i];
break;
}
//More than one entry, meaning there are sub paths.
else
{
/* Set converted path to the current
* convertedPath combined with the next directory. */
convertedPath = Path.Combine(convertedPath, directories[i]);
}
}
return convertedPath;
}
}
}

View File

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

View File

@@ -0,0 +1,80 @@

#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace GameKit.Utilities
{
public enum EditorLayoutEnableType
{
Enabled = 0,
Disabled = 1,
DisabledWhilePlaying = 2
}
public static class Editing
{
/// <summary>
/// Adds a property field.
/// </summary>
public static void AddPropertyField(SerializedProperty sp, GUIContent guiContent, EditorLayoutEnableType enableType = EditorLayoutEnableType.Enabled, params GUILayoutOption[] options)
{
bool disable = DisableLayout(enableType);
if (disable)
GUI.enabled = false;
EditorGUILayout.PropertyField(sp, guiContent, options);
if (disable)
GUI.enabled = true;
}
/// <summary>
/// Adds an object field.
/// </summary>
public static void AddObjectField(string label, MonoScript ms, Type type, bool allowSceneObjects, EditorLayoutEnableType enableType = EditorLayoutEnableType.Enabled, params GUILayoutOption[] options)
{
bool disable = DisableLayout(enableType);
if (disable)
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", ms, type, allowSceneObjects, options);
if (disable)
GUI.enabled = true;
}
/// <summary>
/// Disables GUI if playing.
/// </summary>
public static void DisableGUIIfPlaying()
{
if (Application.isPlaying)
GUI.enabled = false;
}
/// <summary>
/// Enables GUI if playing.
/// </summary>
public static void EnableGUIIfPlaying()
{
if (Application.isPlaying)
GUI.enabled = true;
}
/// <summary>
/// Returns if a layout field should be disabled.
/// </summary>
/// <param name="enableType"></param>
/// <returns></returns>
private static bool DisableLayout(EditorLayoutEnableType enableType)
{
return (enableType == EditorLayoutEnableType.Disabled || (enableType == EditorLayoutEnableType.DisabledWhilePlaying && Application.isPlaying));
}
}
}
#endif

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: dd42f76391fc1254f82767dbf1a4bc8b
timeCreated: 1525378031
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,130 @@
using System;
namespace GameKit.Utilities
{
public static class Enums
{
/// <summary>
/// Determine an enum value from a given string. This can be an expensive function.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="text">Text of string.</param>
/// <param name="defaultValue">Default value if enum couldn't be found.</param>
/// <returns>Enum found or default value if no enum is found.</returns>
public static T FromString<T>(string text, T defaultValue)
{
//If string is empty or null return default value.
if (string.IsNullOrEmpty(text))
return defaultValue;
//If enum isn't defined return default value.
if (!Enum.IsDefined(typeof(T), (string)text))
return defaultValue;
//Return parsed value.
return (T)Enum.Parse(typeof(T), text, true);
}
/// <summary>
/// Returns if whole(extended enum) has any of the part values.
/// </summary>
/// <param name="whole"></param>
/// <param name="part">Values to check for within whole.</param>
/// <returns>Returns true part is within whole.</returns>
public static bool Contains(this Enum whole, Enum part)
{
//If not the same type of Enum return false.
/* Commented out for performance. Designer
* should know better than to compare two different
* enums. */
//if (!SameType(value, target))
// return false;
/* Convert enum values to ulong. With so few
* values a uint would be safe, but should
* the options expand ulong is safer. */
ulong wholeNum = Convert.ToUInt64(whole);
ulong partNum = Convert.ToUInt64(part);
return ((wholeNum & partNum) != 0);
}
/// <summary>
/// Returns if part values contains any of whole(extended enum).
/// </summary>
/// <param name="whole"></param>
/// <param name="part"></param>
/// <returns>Returns true whole is within part.</returns>
public static bool ReverseContains(this Enum whole, Enum part)
{
//If not the same type of Enum return false.
/* Commented out for performance. Designer
* should know better than to compare two different
* enums. */
//if (!SameType(value, target))
// return false;
/* Convert enum values to ulong. With so few
* values a uint would be safe, but should
* the options expand ulong is safer. */
ulong wholeNum = Convert.ToUInt64(whole);
ulong partNum = Convert.ToUInt64(part);
return ((partNum & wholeNum) != 0);
}
/// <summary>
/// Returns if an enum equals a specified value.
/// </summary>
/// <param name="value"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool Equals(this Enum value, Enum target)
{
//If not the same type of Enum return false.
/* Commented out for performance. Designer
* should know better than to compare two different
* enums. */
//if (!SameType(value, target))
// return false;
ulong valueNum = Convert.ToUInt64(value);
ulong wholeNum = Convert.ToUInt64(target);
return (valueNum == wholeNum);
}
/// <summary>
/// Returns if a is the same Enum as b.
/// </summary>
/// <param name="a"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool SameType(Enum a, Enum b)
{
return (a.GetType() == b.GetType());
}
/// <summary>
/// Returns the highest numeric value for T.
/// </summary>
public static int GetHighestValue<T>()
{
Type enumType = typeof(T);
/* Brute force enum values.
* Linq Last/Max lookup throws for IL2CPP. */
int highestValue = 0;
Array pidValues = Enum.GetValues(enumType);
foreach (T pid in pidValues)
{
object obj = Enum.Parse(enumType, pid.ToString());
int value = Convert.ToInt32(obj);
highestValue = Math.Max(highestValue, value);
}
return highestValue;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e6c66aec505f9254491b2b126a2d4745
timeCreated: 1522959833
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
using System;
using UnityEngine;
namespace GameKit.Utilities
{
public static class Floats
{
/// <summary>
/// Used to randomize float values.
/// </summary>
private static System.Random _random = new System.Random();
/// <summary>
/// Sets a source float to value if equal to or greater than tolerance.
/// </summary>
/// <param name="source">Float to check against tolerance.</param>
/// <param name="tolerance">Tolerance float must be equal to or greater than to change to value.</param>
/// <param name="value">Value source is set to when breaking tolerance.</param>
public static float SetIfOverTolerance(this float source, float tolerance, float value)
{
if (source >= tolerance)
source = value;
return source;
}
/// <summary>
/// Sets a source float to value if equal to or less than tolerance.
/// </summary>
/// <param name="source">Float to check against tolerance.</param>
/// <param name="tolerance">Tolerance float must be equal to or less than to change to value.</param>
/// <param name="value">Value source is set to when breaking tolerance.</param>
public static float SetIfUnderTolerance(this float source, float tolerance, float value)
{
if (source <= tolerance)
source = value;
return source;
}
/// <summary>
/// Returns how much time is left on an endTime. Returns -1 if no time is left.
/// </summary>
/// <returns></returns>
public static float TimeRemainingValue(this float endTime)
{
float remaining = endTime - Time.time;
//None remaining.
if (remaining < 0f)
return -1f;
return (endTime - Time.time);
}
/// <summary>
/// Returns how much time is left on an endTime. Returns -1 if no time is left.
/// </summary>
/// <returns></returns>
public static int TimeRemainingValue(this float endTime, bool useFloor = true)
{
float remaining = endTime - Time.time;
//None remaining.
if (remaining < 0f)
return -1;
float result = (endTime - Time.time);
return (useFloor) ? Mathf.FloorToInt(result) : Mathf.CeilToInt(result);
}
/// <summary>
/// Returns time remaining as a string using hh:mm:ss.
/// </summary>
/// <param name="value"></param>
/// <param name="segments">Number of places to return. 1 is seconds, 2 is minutes, 3 is hours. If a placement does not exist it is replaced with 00.</param>
/// <param name="emptyOnZero">True to return an empty string when value is 0 or less.</param>
/// <returns></returns>
public static string TimeRemainingText(this float value, byte segments, bool emptyOnZero = false)
{
if (emptyOnZero && value <= 0f)
return string.Empty;
int timeRounded = Math.Max(Mathf.RoundToInt(value), 0);
TimeSpan t = TimeSpan.FromSeconds(timeRounded);
int hours = Mathf.FloorToInt(t.Hours);
int minutes = Mathf.FloorToInt(t.Minutes);
int seconds = Mathf.FloorToInt(t.Seconds);
string timeText;
if (segments == 1)
{
seconds += (minutes * 60);
seconds += (hours * 3600);
timeText = string.Format("{0:D2}", seconds);
}
else if (segments == 2)
{
minutes += (hours * 60);
timeText = string.Format("{0:D2}:{1:D2}", minutes, seconds);
}
else
{
timeText = string.Format("{0:D2}:{1:D2}:{2:D2}", hours, minutes, seconds);
}
return timeText;
}
/// <summary>
/// Provides a random inclusive int within a given range. Preferred over Unity's Random to eliminate confusion as Unity uses inclusive for floats max, and exclusive for int max.
/// </summary>
/// <param name="minimum">Inclusive minimum value.</param>
/// <param name="maximum">Inclusive maximum value.</param>
/// <returns></returns>
public static float RandomInclusiveRange(float minimum, float maximum)
{
double min = Convert.ToDouble(minimum);
double max = Convert.ToDouble(maximum);
double result = (_random.NextDouble() * (max - min)) + min;
return Convert.ToSingle(result);
}
/// <summary>
/// Returns a random float between 0f and 1f.
/// </summary>
/// <returns></returns>
public static float Random01()
{
return RandomInclusiveRange(0f, 1f);
}
/// <summary>
/// Returns if a target float is within variance of the source float.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="tolerance"></param>
public static bool Near(this float a, float b, float tolerance = 0.01f)
{
return (Mathf.Abs(a - b) <= tolerance);
}
/// <summary>
/// Clamps a float and returns if the float required clamping.
/// </summary>
/// <param name="value"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="clamped"></param>
/// <returns></returns>
public static float Clamp(float value, float min, float max, ref bool clamped)
{
clamped = (value < min);
if (clamped)
return min;
clamped = (value > min);
if (clamped)
return max;
clamped = false;
return value;
}
/// <summary>
/// Returns a float after being adjusted by the specified variance.
/// </summary>
/// <param name="source"></param>
/// <param name="variance"></param>
/// <returns></returns>
public static float Variance(this float source, float variance)
{
float pickedVariance = RandomInclusiveRange(1f - variance, 1f + variance);
return (source * pickedVariance);
}
/// <summary>
/// Sets a float value to result after being adjusted by the specified variance.
/// </summary>
/// <param name="source"></param>
/// <param name="variance"></param>
/// <returns></returns>
public static void Variance(this float source, float variance, ref float result)
{
float pickedVariance = RandomInclusiveRange(1f - variance, 1f + variance);
result = (source * pickedVariance);
}
/// <summary>
/// Returns negative-one, zero, or postive-one of a value instead of just negative-one or positive-one.
/// </summary>
/// <param name="value">Value to sign.</param>
/// <returns>Precise sign.</returns>
public static float PreciseSign(float value)
{
if (value == 0f)
return 0f;
else
return (Mathf.Sign(value));
}
/// <summary>
/// Returns if a float is within a range.
/// </summary>
/// <param name="source">Value of float.</param>
/// <param name="rangeMin">Minimum of range.</param>
/// <param name="rangeMax">Maximum of range.</param>
/// <returns></returns>
public static bool InRange(this float source, float rangeMin, float rangeMax)
{
return (source >= rangeMin && source <= rangeMax);
}
/// <summary>
/// Randomly flips a float value.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static float RandomlyFlip(this float value)
{
if (Ints.RandomInclusiveRange(0, 1) == 0)
return value;
else
return (value *= -1f);
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 4ab517aa5c3b6e34ca20461339adda04
timeCreated: 1526172456
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
{
"name": "GameKit.Utilities",
"rootNamespace": "",
"references": [
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:36bccdedfe0feeb4daf43bef9e43b65b"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.textmeshpro",
"expression": "",
"define": "TEXTMESHPRO"
}
],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,85 @@
namespace GameKit.Utilities
{
public static class Hashing
{
private const uint FNV_offset_basis32 = 2166136261;
private const uint FNV_prime32 = 16777619;
private const ulong FNV_offset_basis64 = 14695981039346656037;
private const ulong FNV_prime64 = 1099511628211;
/// <summary>
/// non cryptographic stable hash code,
/// it will always return the same hash for the same
/// string.
///
/// This is simply an implementation of FNV-1 32 bit xor folded to 16 bit
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
/// <returns>The stable hash32.</returns>
/// <param name="txt">Text.</param>
public static ushort GetStableHashU16(this string txt)
{
uint hash32 = txt.GetStableHashU32();
return (ushort)((hash32 >> 16) ^ hash32);
}
/// <summary>
/// non cryptographic stable hash code,
/// it will always return the same hash for the same
/// string.
///
/// This is simply an implementation of FNV-1 32 bit
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
/// <returns>The stable hash32.</returns>
/// <param name="txt">Text.</param>
public static uint GetStableHashU32(this string txt)
{
unchecked
{
uint hash = FNV_offset_basis32;
for (int i = 0; i < txt.Length; i++)
{
uint ch = txt[i];
hash = hash * FNV_prime32;
hash = hash ^ ch;
}
return hash;
}
}
/// <summary>
/// non cryptographic stable hash code,
/// it will always return the same hash for the same
/// string.
///
/// This is simply an implementation of FNV-1 64 bit
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
/// <returns>The stable hash32.</returns>
/// <param name="txt">Text.</param>
public static ulong GetStableHashU64(this string txt)
{
unchecked
{
ulong hash = FNV_offset_basis64;
for (int i = 0; i < txt.Length; i++)
{
ulong ch = txt[i];
hash = hash * FNV_prime64;
hash = hash ^ ch;
}
return hash;
}
}
}
}

View File

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

View File

@@ -0,0 +1,91 @@

using UnityEngine;
namespace GameKit.Utilities
{
/// <summary>
/// Various utility classes relating to floats.
/// </summary>
public static class Ints
{
private static System.Random _random = new System.Random();
/// <summary>
/// Pads an index a specified value. Preferred over typical padding so that pad values used with skins can be easily found in the code.
/// </summary>
/// <param name="value"></param>
/// <param name="padding"></param>
/// <returns></returns>
public static string PadInt(int value, int padding)
{
return value.ToString().PadLeft(padding, '0');
}
/// <summary>
/// Provides a random inclusive int within a given range. Preferred over Unity's Random to eliminate confusion as Unity uses inclusive for floats max, and exclusive for int max.
/// </summary>
/// <param name="minimum">Inclusive minimum value.</param>
/// <param name="maximum">Inclusive maximum value.</param>
/// <returns></returns>
public static int RandomInclusiveRange(int minimum, int maximum)
{
return _random.Next(minimum, maximum + 1);
}
/// <summary>
/// Provides a random exclusive int within a given range. Preferred over Unity's Random to eliminate confusion as Unity uses inclusive for floats max, and exclusive for int max.
/// </summary>
/// <param name="minimum">Inclusive minimum value.</param>
/// <param name="maximum">Exclusive maximum value.</param>
/// <returns></returns>
public static int RandomExclusiveRange(int minimum, int maximum)
{
return _random.Next(minimum, maximum);
}
/// <summary>
/// Returns a clamped int within a specified range.
/// </summary>
/// <param name="value">Value to clamp.</param>
/// <param name="minimum">Minimum value.</param>
/// <param name="maximum">Maximum value.</param>
/// <returns></returns>
public static int Clamp(int value, int minimum, int maximum)
{
if (value < minimum)
value = minimum;
else if (value > maximum)
value = maximum;
return value;
}
/// <summary>
/// Determins if all values passed in are the same.
/// </summary>
/// <param name="values">Values to check.</param>
/// <returns>True if all values are the same.</returns>
public static bool ValuesMatch(params int[] values)
{
if (values.Length == 0)
{
Debug.Log("Ints -> ValuesMatch -> values array is empty.");
return false;
}
//Assign first value as element in first array.
int firstValue = values[0];
//Check all values.
for (int i = 1; i < values.Length; i++)
{
//If any value doesn't match first value return false.
if (firstValue != values[i])
return false;
}
//If this far all values match.
return true;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c673118198f5c4b41986d52762828363
timeCreated: 1527268448
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using UnityEngine;
namespace GameKit.Utilities
{
public static class Layers
{
/// <summary>
/// Converts a layer mask to a layer number.
/// </summary>
/// <param name="mask"></param>
/// <returns></returns>
public static int LayerMaskToLayerNumber(LayerMask mask)
{
return LayerValueToLayerNumber(mask.value);
}
/// <summary>
/// Converts a layer value int to a layer int.
/// </summary>
/// <param name="bitmask"></param>
/// <returns></returns>
public static int LayerValueToLayerNumber(int bitmask)
{
int result = bitmask > 0 ? 0 : 31;
while (bitmask > 1)
{
bitmask = bitmask >> 1;
result++;
}
return result;
}
/// <summary>
/// Returns if a LayerMask contains a specified layer.
/// </summary>
/// <param name="layerMask">LayerMask to check for layer in.</param>
/// <param name="layer">Layer to check within LayerMask.</param>
/// <returns></returns>
public static bool ContainsLayer(LayerMask layerMask, int layer)
{
return (layerMask == (layerMask | (1 << layer)));
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1c18e15e44d21a94d8919f4b6b125a1f
timeCreated: 1522349045
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
using UnityEngine;
using UnityEngine.UI;
namespace GameKit.Utilities
{
public static class LayoutGroups
{
/// <summary>
/// Returns how many entries can fit into a GridLayoutGroup
/// </summary>
public static int EntriesPerWidth(this GridLayoutGroup lg)
{
RectTransform rectTransform = lg.GetComponent<RectTransform>();
return Mathf.CeilToInt(rectTransform.rect.width / lg.cellSize.x);
}
}
}

View File

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

View File

@@ -0,0 +1,36 @@
using UnityEngine;
namespace GameKit.Utilities
{
public static class Materials
{
/// <summary>
/// Returns the color or tint color property for a material.
/// </summary>
/// <param name="material"></param>
/// <returns></returns>
public static Color GetColor(this Material material)
{
if (material.HasProperty("_Color"))
return material.color;
else if (material.HasProperty("_TintColor"))
return material.GetColor("_TintColor");
return Color.white;
}
/// <summary>
/// Sets the color or tint color property for a material.
/// </summary>
/// <param name="material"></param>
public static void SetColor(this Material material, Color color)
{
if (material.HasProperty("_Color"))
material.color = color;
else if (material.HasProperty("_TintColor"))
material.SetColor("_TintColor", color);
}
}
}

View File

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

View File

@@ -0,0 +1,34 @@
namespace GameKit.Utilities
{
public static class Maths
{
/// <summary>
/// Returns a clamped SBytte.
/// </summary>
public static sbyte ClampSByte(long value, sbyte min, sbyte max)
{
if (value < min)
return min;
else if (value > max)
return max;
else
return (sbyte)value;
}
/// <summary>
/// Returns a clamped double.
/// </summary>
public static double ClampDouble(double value, double min, double max)
{
if (value < min)
return min;
else if (value > max)
return max;
else
return value;
}
}
}

View File

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

View File

@@ -0,0 +1,548 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace GameKit.Utilities
{
/// <summary>
/// Implement to use type with Caches.
/// </summary>
public interface IResettable
{
/// <summary>
/// Resets values when being placed in a cache.
/// </summary>
void ResetState();
/// <summary>
/// Initializes values after being retrieved from a cache.
/// </summary>
void InitializeState();
}
#region Resettable caches.
/// <summary>
/// Caches collections of multiple generics.
/// </summary>
public static class ResettableCollectionCaches<T1, T2> where T1 : IResettable where T2 : IResettable
{
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref Dictionary<T1, T2> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(Dictionary<T1, T2> value)
{
foreach (KeyValuePair<T1, T2> kvp in value)
{
kvp.Key.ResetState();
ObjectCaches<T1>.Store(kvp.Key);
kvp.Value.ResetState();
ObjectCaches<T2>.Store(kvp.Value);
}
value.Clear();
CollectionCaches<T1, T2>.Store(value);
}
}
/// <summary>
/// Caches collections of multiple generics.
/// </summary>
public static class ResettableT1CollectionCaches<T1, T2> where T1 : IResettable
{
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref Dictionary<T1, T2> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(Dictionary<T1, T2> value)
{
foreach (T1 item in value.Keys)
{
item.ResetState();
ObjectCaches<T1>.Store(item);
}
value.Clear();
CollectionCaches<T1, T2>.Store(value);
}
}
/// <summary>
/// Caches collections of multiple generics.
/// </summary>
public static class ResettableT2CollectionCaches<T1, T2> where T2 : IResettable
{
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref Dictionary<T1, T2> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(Dictionary<T1, T2> value)
{
foreach (T2 item in value.Values)
{
item.ResetState();
ObjectCaches<T2>.Store(item);
}
value.Clear();
CollectionCaches<T1, T2>.Store(value);
}
}
/// <summary>
/// Caches collections of a single generic.
/// </summary>
public static class ResettableCollectionCaches<T> where T : IResettable
{
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static T[] RetrieveArray() => CollectionCaches<T>.RetrieveArray();
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static List<T> RetrieveList() => CollectionCaches<T>.RetrieveList();
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static HashSet<T> RetrieveHashSet() => CollectionCaches<T>.RetrieveHashSet();
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
/// <param name="count">Number of entries in the array from the beginning.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref T[] value, int count)
{
if (value == null)
return;
Store(value, count);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
/// <param name="count">Number of entries in the array from the beginning.</param>
public static void Store(T[] value, int count)
{
for (int i = 0; i < count; i++)
{
value[i].ResetState();
ObjectCaches<T>.Store(value[i]);
}
CollectionCaches<T>.Store(value, count);
}
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref List<T> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(List<T> value)
{
for (int i = 0; i < value.Count; i++)
{
value[i].ResetState();
ObjectCaches<T>.Store(value[i]);
}
value.Clear();
CollectionCaches<T>.Store(value);
}
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref HashSet<T> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(HashSet<T> value)
{
foreach (T item in value)
{
item.ResetState();
ObjectCaches<T>.Store(item);
}
value.Clear();
CollectionCaches<T>.Store(value);
}
}
/// <summary>
/// Caches objects of a single generic.
/// </summary>
public static class ResettableObjectCaches<T> where T : IResettable
{
/// <summary>
/// Retrieves an instance of T.
/// </summary>
public static T Retrieve()
{
T result = ObjectCaches<T>.Retrieve();
result.InitializeState();
return result;
}
/// <summary>
/// Stores an instance of T and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref T value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores an instance of T.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(T value)
{
value.ResetState();
ObjectCaches<T>.Store(value);
}
}
#endregion
#region NonResettable caches.
/// <summary>
/// Caches collections of multiple generics.
/// </summary>
public static class CollectionCaches<T1, T2>
{
/// <summary>
/// Cache for dictionaries.
/// </summary>
private readonly static Stack<Dictionary<T1, T2>> _dictionaryCache = new Stack<Dictionary<T1, T2>>();
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static Dictionary<T1, T2> RetrieveDictionary()
{
if (_dictionaryCache.Count == 0)
return new Dictionary<T1, T2>();
else
return _dictionaryCache.Pop();
}
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref Dictionary<T1, T2> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(Dictionary<T1, T2> value)
{
value.Clear();
_dictionaryCache.Push(value);
}
}
/// <summary>
/// Caches collections of a single generic.
/// </summary>
public static class CollectionCaches<T>
{
/// <summary>
/// Cache for arrays.
/// </summary>
private readonly static Stack<T[]> _arrayCache = new Stack<T[]>();
/// <summary>
/// Cache for lists.
/// </summary>
private readonly static Stack<List<T>> _listCache = new Stack<List<T>>();
/// <summary>
/// Cache for hashset.
/// </summary>
private readonly static Stack<HashSet<T>> _hashsetCache = new Stack<HashSet<T>>();
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static T[] RetrieveArray()
{
if (_arrayCache.Count == 0)
return new T[0];
else
return _arrayCache.Pop();
}
/// <summary>
/// Retrieves a collection.
/// </summary>
/// <returns></returns>
public static List<T> RetrieveList()
{
if (_listCache.Count == 0)
return new List<T>();
else
return _listCache.Pop();
}
/// <summary>
/// Retrieves a collection adding one entry.
/// </summary>
/// <returns></returns>
public static List<T> RetrieveList(T entry)
{
List<T> result;
if (_listCache.Count == 0)
result = new List<T>();
else
result = _listCache.Pop();
result.Add(entry);
return result;
}
/// <summary>
/// Retrieves a HashSet<T>.
/// </summary>
/// <returns></returns>
public static HashSet<T> RetrieveHashSet()
{
if (_hashsetCache.Count == 0)
return new HashSet<T>();
else
return _hashsetCache.Pop();
}
/// <summary>
/// Retrieves a collection adding one entry.
/// </summary>
/// <returns></returns>
public static HashSet<T> RetrieveHashSet(T entry)
{
HashSet<T> result;
if (_hashsetCache.Count == 0)
result = new HashSet<T>();
else
result = _hashsetCache.Pop();
result.Add(entry);
return result;
}
/// <summary>
/// Stores a collection and sets the original reference to default.\
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
/// <param name="count">Number of entries in the array from the beginning.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref T[] value, int count)
{
if (value == null)
return;
Store(value, count);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
/// <param name="count">Number of entries in the array from the beginning.</param>
public static void Store(T[] value, int count)
{
for (int i = 0; i < count; i++)
value[i] = default;
_arrayCache.Push(value);
}
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref List<T> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(List<T> value)
{
value.Clear();
_listCache.Push(value);
}
/// <summary>
/// Stores a collection and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref HashSet<T> value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a collection.
/// </summary>
/// <param name="value">Value to store.</param>
public static void Store(HashSet<T> value)
{
value.Clear();
_hashsetCache.Push(value);
}
}
/// <summary>
/// Caches objects of a single generic.
/// </summary>
public static class ObjectCaches<T>
{
/// <summary>
/// Stack to use.
/// </summary>
private readonly static Stack<T> _stack = new Stack<T>();
/// <summary>
/// Returns a value from the stack or creates an instance when the stack is empty.
/// </summary>
/// <returns></returns>
public static T Retrieve()
{
if (_stack.Count == 0)
return Activator.CreateInstance<T>();
else
return _stack.Pop();
}
/// <summary>
/// Stores an instance of T and sets the original reference to default.
/// Method will not execute if value is null.
/// </summary>
/// <param name="value">Value to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StoreAndDefault(ref T value)
{
if (value == null)
return;
Store(value);
value = default;
}
/// <summary>
/// Stores a value to the stack.
/// </summary>
/// <param name="value"></param>
public static void Store(T value)
{
_stack.Push(value);
}
}
#endregion
}

View File

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

View File

@@ -0,0 +1,67 @@

using GameKit.Utilities.Types;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace GameKit.Utilities
{
public static class Objects
{
/// <summary>
/// Returns if an object has been destroyed from memory.
/// </summary>
/// <param name="gameObject"></param>
/// <returns></returns>
public static bool IsDestroyed(this GameObject gameObject)
{
// UnityEngine overloads the == operator for the GameObject type
// and returns null when the object has been destroyed, but
// actually the object is still there but has not been cleaned up yet
// if we test both we can determine if the object has been destroyed.
return (gameObject == null && !ReferenceEquals(gameObject, null));
}
/// <summary>
/// Finds all objects in the scene of type. This method is very expensive.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="requireSceneLoaded">True if the scene must be fully loaded before trying to seek objects.</param>
/// <returns></returns>
public static List<T> FindAllObjectsOfType<T>(bool activeSceneOnly = true, bool requireSceneLoaded = false, bool includeDDOL = true, bool includeInactive = true)
{
List<T> results = new List<T>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
//If to include only current scene.
if (activeSceneOnly)
{
if (SceneManager.GetActiveScene() != scene)
continue;
}
//If the scene must be fully loaded to seek objects within.
if (!scene.isLoaded && requireSceneLoaded)
continue;
GameObject[] allGameObjects = scene.GetRootGameObjects();
for (int j = 0; j < allGameObjects.Length; j++)
{
results.AddRange(allGameObjects[j].GetComponentsInChildren<T>(includeInactive));
}
}
//If to also include DDOL.
if (includeDDOL)
{
GameObject ddolGo = DDOL.GetDDOL().gameObject;
results.AddRange(ddolGo.GetComponentsInChildren<T>(includeInactive));
}
return results;
}
}
}

View File

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

View File

@@ -0,0 +1,109 @@
using UnityEngine;
namespace GameKit.Utilities
{
public static class Particles
{
/// <summary>
/// Issues stop on the specified particle systems.
/// </summary>
/// <param name="systems"></param>
public static float StopParticleSystem(ParticleSystem[] systems, bool stopLoopingOnly)
{
return StopParticleSystem(systems, stopLoopingOnly, ParticleSystemStopBehavior.StopEmitting);
}
/// <summary>
/// Issues stop on the specified particle systems while returning the time required to play out.
/// </summary>
/// <param name="systems"></param>
public static float StopParticleSystem(ParticleSystem[] systems, ParticleSystemStopBehavior stopBehavior = ParticleSystemStopBehavior.StopEmitting)
{
return StopParticleSystem(systems, false, stopBehavior);
}
/// <summary>
/// Issues stop on the specified particle systems while returning the time required to play out.
/// </summary>
/// <param name="systems"></param>
public static float StopParticleSystem(ParticleSystem[] systems, bool stopLoopingOnly, ParticleSystemStopBehavior stopBehavior = ParticleSystemStopBehavior.StopEmitting)
{
if (systems == null)
return 0f;
float playOutDuration = 0f;
for (int i = 0; i < systems.Length; i++)
playOutDuration = Mathf.Max(playOutDuration, StopParticleSystem(systems[i], stopLoopingOnly, stopBehavior));
return playOutDuration;
}
/// <summary>
/// Issues stop on the specified particle systems.
/// </summary>
/// <param name="systems"></param>
public static float StopParticleSystem(ParticleSystem system, bool stopLoopingOnly, bool stopChildren = false)
{
return StopParticleSystem(system, stopLoopingOnly, ParticleSystemStopBehavior.StopEmitting, stopChildren);
}
/// <summary>
/// Issues stop on the specified particle systems while returning the time required to play out.
/// </summary>
/// <param name="systems"></param>
public static float StopParticleSystem(ParticleSystem system, ParticleSystemStopBehavior stopBehavior = ParticleSystemStopBehavior.StopEmitting, bool stopChildren = false)
{
return StopParticleSystem(system, false, stopBehavior, stopChildren);
}
/// <summary>
/// Issues stop on the specified particle system while returning the time required to play out.
/// </summary>
public static float StopParticleSystem(ParticleSystem system, bool stopLoopingOnly, ParticleSystemStopBehavior stopBehavior = ParticleSystemStopBehavior.StopEmitting, bool stopChildren = false)
{
if (system == null)
return 0f;
if (stopChildren)
{
ParticleSystem[] all = system.GetComponentsInChildren<ParticleSystem>();
StopParticleSystem(all, stopLoopingOnly, stopBehavior);
}
float playOutDuration = 0f;
float timeLeft = system.main.duration - system.time;
playOutDuration = Mathf.Max(playOutDuration, timeLeft);
if (stopLoopingOnly)
{
if (system.main.loop)
system.Stop(false, stopBehavior);
}
else
{
system.Stop(false, stopBehavior);
}
return playOutDuration;
}
/// <summary>
/// Returns the longest time required for all systems to stop.
/// </summary>
/// <param name="systems"></param>
/// <returns></returns>
public static float ReturnLongestCycle(ParticleSystem[] systems)
{
float longestPlayTime = 0f;
for (int i = 0; i < systems.Length; i++)
{
float timeLeft = systems[i].main.duration - systems[i].time;
longestPlayTime = Mathf.Max(longestPlayTime, timeLeft);
}
return longestPlayTime;
}
}
}

View File

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

View File

@@ -0,0 +1,55 @@
using UnityEngine;
namespace GameKit.Utilities
{
public static class Quaternions
{
/// <summary>
/// Returns how fast an object must rotate over duration to reach goal.
/// </summary>
/// <param name="goal">Quaternion to measure distance against.</param>
/// <param name="duration">How long it should take to move to goal.</param>
/// <param name="interval">A multiplier applied towards interval. Typically this is used for ticks passed.</param>
/// <returns></returns>
public static float GetRate(this Quaternion a, Quaternion goal, float duration, out float angle, uint interval = 1, float tolerance = 0f)
{
angle = a.Angle(goal, true);
return angle / (duration * interval);
}
/// <summary>
/// Returns if two quaternions match.
/// </summary>
/// <param name="precise">True to use a custom implementation with no error tolerance. False to use Unity's implementation which may return a match even when not true due to error tolerance.</param>
/// <returns></returns>
public static bool Matches(this Quaternion a, Quaternion b, bool precise = false)
{
if (precise)
return (a.w == b.w && a.x == b.x && a.y == b.y && a.z == b.z);
else
return (a == b);
}
/// <summary>
/// Returns the angle between two quaterions.
/// </summary>
/// <param name="precise">True to use a custom implementation with no error tolerance. False to use Unity's implementation which may return 0f due to error tolerance, even while there is a difference.</param>
/// <returns></returns>
public static float Angle(this Quaternion a, Quaternion b, bool precise = false)
{
if (precise)
{
//This is run Unitys implementation without the error tolerance.
float dot = (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
return (Mathf.Acos(Mathf.Min(Mathf.Abs(dot), 1f)) * 2f * 57.29578f);
}
else
{
return Quaternion.Angle(a, b);
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 02a9084f4f788cd4293cdff56a49b5dd
timeCreated: 1522043602
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@

using System;
namespace GameKit.Utilities
{
public static class Strings
{
/// <summary>
/// Attachs or detaches an suffix to a string.
/// </summary>
/// <param name="text"></param>
/// <param name="suffix"></param>
/// <param name="addExtension"></param>
public static string ReturnModifySuffix(string text, string suffix, bool addExtension)
{
/* Since saving to a json, add the .json extension if not present.
* Length must be greater than 6 to contain a character and .json. */
if (text.Length > (suffix.Length + 1))
{
//If to add the extension.
if (addExtension)
{
//If doesn't contain the extension then add it on.
if (!text.Substring(text.Length - suffix.Length).Contains(suffix, StringComparison.CurrentCultureIgnoreCase))
return (text + suffix);
//Already contains extension.
else
return text;
}
//Remove extension.
else
{
//If contains extension.
if (text.Substring(text.Length - suffix.Length).Contains(suffix, StringComparison.CurrentCultureIgnoreCase))
return text.Substring(0, text.Length - (suffix.Length));
//Doesn't contain extension.
return text;
}
}
//Text isn't long enough to manipulate.
else
{
return text;
}
}
/// <summary>
/// Returns if a string contains another string using StringComparison.
/// </summary>
/// <param name="s"></param>
/// <param name="contains"></param>
/// <param name="comp"></param>
/// <returns></returns>
public static bool Contains(this string s, string contains, StringComparison comp)
{
int index = s.IndexOf(contains, comp);
return (index >= 0);
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b396f2be4de550a4e92b552650311600
timeCreated: 1525378031
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using UnityEngine;
namespace GameKit.Utilities
{
public static class Transforms
{
/// <summary>
/// Returns a position for the rectTransform ensuring it's fully on the screen.
/// </summary>
/// <param name="desiredPosition">Preferred position for the rectTransform.</param>
/// <param name="padding">How much padding the transform must be from the screen edges.</param>
public static Vector3 GetOnScreenPosition(this RectTransform rectTransform, Vector3 desiredPosition, Vector2 padding)
{
Vector2 scale = new Vector2(rectTransform.localScale.x, rectTransform.localScale.y);
//Value of which the tooltip would exceed screen bounds.
//If there would be overshoot then adjust to be just on the edge of the overshooting side.
float overshoot;
float halfWidthRequired = ((rectTransform.sizeDelta.x * scale.x) / 2f) + padding.x;
overshoot = (Screen.width - (desiredPosition.x + halfWidthRequired));
//If overshooting on the right.
if (overshoot < 0f)
desiredPosition.x += overshoot;
overshoot = (desiredPosition.x - halfWidthRequired);
//If overshooting on the left.
if (overshoot < 0f)
desiredPosition.x = halfWidthRequired;
float halfHeightRequired = ((rectTransform.sizeDelta.y * scale.y) / 2f) + padding.y;
overshoot = (Screen.height - (desiredPosition.y + halfHeightRequired));
//If overshooting on the right.
if (overshoot < 0f)
desiredPosition.y += overshoot;
overshoot = (desiredPosition.y - halfHeightRequired);
//If overshooting on the left.
if (overshoot < 0f)
desiredPosition.y = halfHeightRequired;
return desiredPosition;
}
/// <summary>
/// Sets a parent for src while maintaining position, rotation, and scale of src.
/// </summary>
/// <param name="parent">Transform to become a child of.</param>
public static void SetParentAndKeepTransform(this Transform src, Transform parent)
{
Vector3 pos = src.position;
Quaternion rot = src.rotation;
Vector3 scale = src.localScale;
src.SetParent(parent);
src.position = pos;
src.rotation = rot;
src.localScale = scale;
}
/// <summary>
/// Destroys all children under the specified transform.
/// </summary>
/// <param name="t"></param>
public static void DestroyChildren(this Transform t, bool destroyImmediately = false)
{
foreach (Transform child in t)
{
if (destroyImmediately)
MonoBehaviour.DestroyImmediate(child.gameObject);
else
MonoBehaviour.Destroy(child.gameObject);
}
}
/// <summary>
/// Destroys all children of a type under the specified transform.
/// </summary>
/// <param name="t"></param>
public static void DestroyChildren<T>(this Transform t, bool destroyImmediately = false) where T : MonoBehaviour
{
T[] children = t.GetComponentsInChildren<T>();
foreach (T child in children)
{
if (destroyImmediately)
MonoBehaviour.DestroyImmediate(child.gameObject);
else
MonoBehaviour.Destroy(child.gameObject);
}
}
/// <summary>
/// Gets components in children and optionally parent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="results"></param>
/// <param name="parent"></param>
/// <param name="includeParent"></param>
/// <param name="includeInactive"></param>
public static void GetComponentsInChildren<T>(this Transform parent, List<T> results, bool includeParent = true, bool includeInactive = false) where T : Component
{
if (!includeParent)
{
List<T> current = GameKit.Utilities.CollectionCaches<T>.RetrieveList();
for (int i = 0; i < parent.childCount; i++)
{
parent.GetChild(i).GetComponentsInChildren(includeInactive, current);
results.AddRange(current);
}
GameKit.Utilities.CollectionCaches<T>.Store(current);
}
else
{
parent.GetComponentsInChildren(includeInactive, results);
}
}
/// <summary>
/// Returns the position of this transform.
/// </summary>
public static Vector3 GetPosition(this Transform t, bool localSpace)
{
return (localSpace) ? t.localPosition : t.position;
}
/// <summary>
/// Returns the rotation of this transform.
/// </summary>
public static Quaternion GetRotation(this Transform t, bool localSpace)
{
return (localSpace) ? t.localRotation : t.rotation;
}
/// <summary>
/// Returns the scale of this transform.
/// </summary>
public static Vector3 GetScale(this Transform t)
{
return t.localScale;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetPosition(this Transform t, bool localSpace, Vector3 pos)
{
if (localSpace)
t.localPosition = pos;
else
t.position = pos;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetRotation(this Transform t, bool localSpace, Quaternion rot)
{
if (localSpace)
t.localRotation = rot;
else
t.rotation = rot;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetScale(this Transform t, Vector3 scale)
{
t.localScale = scale;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,236 @@
using GameKit.Dependencies.Inspectors;
using UnityEngine;
namespace GameKit.Utilities.Types
{
public class CanvasGroupFader : MonoBehaviour
{
#region Types.
/// <summary>
/// Current fade state or goal for this class.
/// </summary>
public enum FadeGoalType
{
Unset = 0,
Hidden = 1,
Visible = 2,
}
#endregion
#region Public.
/// <summary>
/// Current goal for the fader.
/// </summary>
public FadeGoalType FadeGoal { get; private set; } = FadeGoalType.Unset;
/// <summary>
/// True if hidden or in the process of hiding.
/// </summary>
public bool IsHiding => (FadeGoal == FadeGoalType.Hidden);
/// <summary>
/// True if visible. Will be true long as the CanvasGroup has alpha. Also see IsHiding.
/// </summary>
public bool IsVisible => (CanvasGroup.alpha > 0f);
#endregion
#region Serialized.
/// <summary>
/// CanvasGroup to fade in and out.
/// </summary>
[Tooltip("CanvasGroup to fade in and out.")]
[SerializeField, Group("Components")]
protected CanvasGroup CanvasGroup;
/// <summary>
/// True to update the CanvasGroup blocking settings when showing and hiding.
/// </summary>
[Tooltip("True to update the CanvasGroup blocking settings when showing and hiding.")]
[SerializeField, Group("Effects")]
protected bool UpdateCanvasBlocking = true;
/// <summary>
/// How long it should take to fade in the CanvasGroup.
/// </summary>
[SerializeField, Group("Effects")]
protected float FadeInDuration = 0.1f;
/// <summary>
/// How long it should take to fade out the CanvasGroup.
/// </summary>
[SerializeField, Group("Effects")]
protected float FadeOutDuration = 0.3f;
#endregion
#region Private.
/// <summary>
/// True if a fade cycle has completed at least once.
/// </summary>
private bool _completedOnce;
#endregion
protected virtual void OnEnable()
{
FadeGoal = (CanvasGroup.alpha > 0f) ? FadeGoalType.Visible : FadeGoalType.Hidden;
}
protected virtual void OnDisable()
{
if (FadeGoal == FadeGoalType.Visible)
ShowImmediately();
else
HideImmediately();
}
protected virtual void Update()
{
Fade();
}
/// <summary>
/// Shows CanvasGroup immediately.
/// </summary>
public virtual void ShowImmediately()
{
SetFadeGoal(true);
CompleteFade(true);
OnShow();
}
/// <summary>
/// Hides CanvasGroup immediately.
/// </summary>
public virtual void HideImmediately()
{
SetFadeGoal(false);
CompleteFade(false);
OnHide();
}
/// <summary>
/// Shows CanvasGroup with a fade.
/// </summary>
public virtual void Show()
{
if (FadeInDuration <= 0f)
{
ShowImmediately();
}
else
{
SetFadeGoal(true);
OnShow();
}
}
/// <summary>
/// Called after Show or ShowImmediate.
/// </summary>
protected virtual void OnShow() { }
/// <summary>
/// Hides CanvasGroup with a fade.
/// </summary>
public virtual void Hide()
{
if (FadeOutDuration <= 0f)
{
HideImmediately();
}
else
{
//Immediately make unclickable so players cannot hit UI objects as it's fading out.
SetCanvasGroupBlockingType(CanvasGroupBlockingType.Block);
SetFadeGoal(false);
OnHide();
}
}
/// <summary>
/// Called after Hide or HideImmediate.
/// </summary>
protected virtual void OnHide() { }
/// <summary>
/// Sets showing and begins fading if required.
/// </summary>
/// <param name="fadeIn"></param>
private void SetFadeGoal(bool fadeIn)
{
FadeGoal = (fadeIn) ? FadeGoalType.Visible : FadeGoalType.Hidden;
}
/// <summary>
/// Fades in or out over time.
/// </summary>
/// <returns></returns>
private void Fade()
{
//Should not be possible.
if (FadeGoal == FadeGoalType.Unset)
{
Debug.LogError($"{gameObject.name} has an unset FadeGoal. This should not be possible.");
return;
}
bool fadingIn = (FadeGoal == FadeGoalType.Visible);
float duration;
float targetAlpha;
if (fadingIn)
{
targetAlpha = 1f;
duration = FadeInDuration;
}
else
{
targetAlpha = 0f;
duration = FadeOutDuration;
}
/* Already at goal and had completed an iteration at least once.
* This is checked because even if at alpha we want to
* complete the cycle if not done once so that all
* local states and canvasgroup settings are proper. */
if (_completedOnce && CanvasGroup.alpha == targetAlpha)
return;
float rate = (1f / duration);
CanvasGroup.alpha = Mathf.MoveTowards(CanvasGroup.alpha, targetAlpha, rate * Time.deltaTime);
//If complete.
if (CanvasGroup.alpha == targetAlpha)
CompleteFade(fadingIn);
}
/// <summary>
/// Called when the fade completes.
/// </summary>
protected virtual void CompleteFade(bool fadingIn)
{
CanvasGroupBlockingType blockingType;
float alpha;
if (fadingIn)
{
blockingType = CanvasGroupBlockingType.Block;
alpha = 1f;
}
else
{
blockingType = CanvasGroupBlockingType.DoNotBlock;
alpha = 0f;
}
SetCanvasGroupBlockingType(blockingType);
CanvasGroup.alpha = alpha;
_completedOnce = true;
}
/// <summary>
/// Changes the CanvasGroups interactable and bloacking state.
/// </summary>
protected virtual void SetCanvasGroupBlockingType(CanvasGroupBlockingType blockingType)
{
if (UpdateCanvasBlocking)
CanvasGroup.SetBlockingType(blockingType);
}
}
}

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