Add StickGame Assets
This commit is contained in:
8
phr/StickGame/Assets/FishNet/Plugins/CodeAnalysis.meta
Normal file
8
phr/StickGame/Assets/FishNet/Plugins/CodeAnalysis.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aea01893c4a887048868eaa5b37c656a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -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:
|
||||
Binary file not shown.
@@ -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:
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93ef12b9e040fa8429d9ef686212ed4e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
Git URL:
|
||||
https://github.com/Abdelfattah-Radwan/FishNet.CodeAnalysis
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 835336ee0aec7ef41a1cfda40886f443
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a1aa3332a3dba24cbfab110df5ec883
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34f427edf65a7b845917c09d7dd5a4d9
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d2f070096038814b95de884c97d7432
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03b938ae217476545845d400f70e90ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26f811c782a153c40bf204a24f016233
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414566e956be2e44c88936767a5075a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b4d1ee96e33fa9428f01142ed427231
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9af11b60772c62749919bedb1b39e754
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 764a7199db8c90246820be2df11ee41d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 400639878008206409ec440eb16c3007
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9c34549a246d47439b106bb23f71dff
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
2.1.2
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df96c994e41a8f9498b5c3313e5b4540
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
phr/StickGame/Assets/FishNet/Plugins/GameKit.meta
Normal file
8
phr/StickGame/Assets/FishNet/Plugins/GameKit.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a72c0fe8d07e9fd49911db527eddbc39
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd9a0ca39fab66c448fdc3e25da9d482
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "GameKit.Dependencies"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36bccdedfe0feeb4daf43bef9e43b65b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8ce00cde8ada214fb582a92539f14b9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b43a8f6c3cc189c40ae6b248e76e2788
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c197d238762d66b41927449d5c48b3f4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 428419a625f80f6438c5b74beb2ac763
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bce51828adfc9b540b10914a9ec82c31
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3c02d1d5545ae54687d1a313ebb2fd6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 102b5a2337dae434f989eee1a6a1c571
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8361a4f779768242840a9c994392e20
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1266c8c8d104aeb4faf3f1daaee87479
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 203e832301eed08499198358cfd13e7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53002e457d153bf49aad4b2b28d4353c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54eb82a57a65e8548b57f5ca2a62bb76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 602eeac4b016b174f90ae5e85254ac86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0e7937b287d3d24d807a115c1a3a464
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d31d19bc39eb6041bad18d8eb68ed68
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
102
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Disks.cs
Normal file
102
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Disks.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3a909760282d284591c20c873f20837
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -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:
|
||||
130
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Enums.cs
Normal file
130
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Enums.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
232
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Floats.cs
Normal file
232
phr/StickGame/Assets/FishNet/Plugins/GameKit/Utilities/Floats.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17013b2a21298c14ea4808251346a38a
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a54dd369b7206e4f9e7716a42e53454
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e330113395c59ca4dba5de001e010f08
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27a618c551d5fdb4ca70bf07e1905580
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18a583dc22a9a0f4cabec0c4a0219c6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cb13274f7491a941b6e89a767905f56
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa6d28a28dbf6b4295602abad3de328
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f3d973dcfa06554998575e8eef0938a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba23540de73f58b458e7d7a200f3bb30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a29cd7621a70a044bb205cc8cfd96b3c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
Reference in New Issue
Block a user