version 1.3

This commit is contained in:
2023-01-02 00:08:18 +05:30
parent 8faf4476ed
commit af487ad62d
581 changed files with 145027 additions and 809 deletions

View File

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

View File

@@ -0,0 +1,72 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace FirstGearGames.Utilities.Editors
{
/// <summary>
/// SOURCE: https://answers.unity.com/questions/1477896/assign-enum-value-from-editorguienumflagsfield.html
/// </summary>
public static class EditorExtension
{
public static int DrawBitMaskField(Rect aPosition, int aMask, System.Type aType, GUIContent aLabel)
{
var itemNames = System.Enum.GetNames(aType);
var itemValues = System.Enum.GetValues(aType) as int[];
int val = aMask;
int maskVal = 0;
for (int i = 0; i < itemValues.Length; i++)
{
if (itemValues[i] != 0)
{
if ((val & itemValues[i]) == itemValues[i])
maskVal |= 1 << i;
}
else if (val == 0)
maskVal |= 1 << i;
}
int newMaskVal = EditorGUI.MaskField(aPosition, aLabel, maskVal, itemNames);
int changes = maskVal ^ newMaskVal;
for (int i = 0; i < itemValues.Length; i++)
{
if ((changes & (1 << i)) != 0)
{
if ((newMaskVal & (1 << i)) != 0)
{
if (itemValues[i] == 0)
{
val = 0;
break;
}
else
val |= itemValues[i];
}
else
{
val &= ~itemValues[i];
}
}
}
return val;
}
}
[CustomPropertyDrawer(typeof(BitMaskAttribute))]
public class EnumBitMaskPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label)
{
var typeAttr = attribute as BitMaskAttribute;
// Add the actual int value behind the field name
label.text = label.text + " (" + prop.intValue + ")";
prop.intValue = EditorExtension.DrawBitMaskField(position, prop.intValue, typeAttr.propType, label);
}
}
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
using UnityEngine;
namespace FirstGearGames.Utilities.Editors
{
/// <summary>
/// SOURCE: https://answers.unity.com/questions/1477896/assign-enum-value-from-editorguienumflagsfield.html
/// </summary>
public class BitMaskAttribute : PropertyAttribute
{
public System.Type propType;
public BitMaskAttribute(System.Type aType)
{
propType = aType;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
namespace FirstGearGames.Utilities.Objects
{
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>
/// <typeparam name="T"></typeparam>
/// <param name=""></param>
/// <param name="value"></param>
public static void AddUnique<T>(this List<T> list, object value)
{
if (!list.Contains((T)value))
list.Add((T)value);
}
/// <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;
}
}
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace FirstGearGames.Utilities.Objects
{
public static class Canvases
{
/// <returns>Returns true part is within whole.</returns>
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;
}
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using FirstGearGames.Utilities.Maths;
using UnityEngine;
using UnityEngine.UI;
namespace FirstGearGames.Utilities.Maths
{
public class DisplayFPS : MonoBehaviour
{
[SerializeField]
private Text _fpsText = null;
private FrameRateCalculator _frameRate = new FrameRateCalculator();
private void Update()
{
UpdateFrameRate();
}
private void UpdateFrameRate()
{
if (_frameRate.Update(Time.unscaledDeltaTime))
_fpsText.text = _frameRate.GetIntFrameRate().ToString();
}
}
}

View File

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

View File

@@ -0,0 +1,109 @@
using System;
namespace FirstGearGames.Utilities.Maths
{
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());
}
}
}

View File

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

View File

@@ -0,0 +1,132 @@
using System;
using UnityEngine;
namespace FirstGearGames.Utilities.Maths
{
public static class Floats
{
private static System.Random _random = new System.Random();
/// <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(float value)
{
if (Ints.RandomInclusiveRange(0, 1) == 0)
return value;
else
return (value *= -1f);
}
}
}

View File

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

View File

@@ -0,0 +1,69 @@
using UnityEngine;
namespace FirstGearGames.Utilities.Maths
{
public class FrameRateCalculator
{
#region Private.
/// <summary>
/// Time used to generate Frames.
/// </summary>
private float _timePassed = 0f;
/// <summary>
/// Frames performed in TimePassed. Float is used to reduce casting.
/// </summary>
private float _frames = 0;
#endregion
#region Const.
/// <summary>
/// How many frames to pass before slicing calculation values.
/// </summary>
private const int RESET_FRAME_COUNT = 60;
/// <summary>
/// Percentage to slice calculation values by. Higher percentages result in smoother frame rate adjustments.
/// </summary>
private const float CALCULATION_SLICE_PERCENT = 0.7f;
#endregion
/// <summary>
/// Gets the current frame rate.
/// </summary>
/// <returns></returns>
public int GetIntFrameRate()
{
return Mathf.CeilToInt((_frames / _timePassed));
}
/// <summary>
/// Gets the current frame rate.
/// </summary>
/// <returns></returns>
public float GetFloatFrameRate()
{
return (_frames / _timePassed);
}
/// <summary>
/// Updates frame count and time passed.
/// </summary>
public bool Update(float unscaledDeltaTime)
{
_timePassed += unscaledDeltaTime;
_frames++;
if (_frames > RESET_FRAME_COUNT)
{
_frames *= CALCULATION_SLICE_PERCENT;
_timePassed *= CALCULATION_SLICE_PERCENT;
return true;
}
else
{
return false;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
using Mirror;
using System.Runtime.CompilerServices;
namespace FirstGearGames.Utilities.Networks
{
public static class Lookups
{
/// <summary>
/// Returns the NetworkBehaviour for the specified NetworkIdentity and component index.
/// </summary>
/// <param name="componentIndex"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NetworkBehaviour ReturnNetworkBehaviour(NetworkIdentity netIdentity, byte componentIndex)
{
if (netIdentity == null)
return null;
/* Networkbehaviours within the collection are the same order as compenent indexes.
* I can save several iterations by simply grabbing the index from the networkbehaviours collection rather than iterating
* it. */
//A network behaviour was removed or added at runtime, component counts don't match up.
if (componentIndex >= netIdentity.NetworkBehaviours.Length)
return null;
return netIdentity.NetworkBehaviours[componentIndex];
}
}
}

View File

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

View File

@@ -0,0 +1,36 @@
#if MIRROR_53_0_OR_NEWER
using Mirror;
namespace FirstGearGames.Utilities.Networks
{
public static class MirrorBreaksProjectsEveryRelease_Serializers
{
public static void WriteBoolean(this NetworkWriter writer, bool value) => writer.WriteBool(value);
public static bool ReadBoolean(this NetworkReader reader) => reader.ReadBool();
public static void WriteInt64(this NetworkWriter writer, long value) => writer.WriteLong(value);
public static long ReadInt64(this NetworkReader reader) => reader.ReadLong();
public static void WriteUInt64(this NetworkWriter writer, ulong value) => writer.WriteULong(value);
public static ulong ReadUInt64(this NetworkReader reader) => reader.ReadULong();
public static void WriteInt32(this NetworkWriter writer, int value) => writer.WriteInt(value);
public static int ReadInt32(this NetworkReader reader) => reader.ReadInt();
public static void WriteUInt32(this NetworkWriter writer, uint value) => writer.WriteUInt(value);
public static uint ReadUInt32(this NetworkReader reader) => reader.ReadUInt();
public static void WriteInt16(this NetworkWriter writer, short value) => writer.WriteShort(value);
public static short ReadInt16(this NetworkReader reader) => reader.ReadShort();
public static void WriteUInt16(this NetworkWriter writer, ushort value) => writer.WriteUShort(value);
public static ushort ReadUInt16(this NetworkReader reader) => reader.ReadUShort();
public static void WriteSingle(this NetworkWriter writer, float value) => writer.WriteFloat(value);
public static float ReadSingle(this NetworkReader reader) => reader.ReadFloat();
}
}
#endif

View File

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

View File

@@ -0,0 +1,174 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using Mirror;
namespace FirstGearGames.Utilities.Networks
{
public static class Platforms
{
/// <summary>
/// Returns the NetworkId for a NetworkIdentity.
/// </summary>
/// <param name="ni"></param>
/// <returns></returns>
public static uint ReturnNetworkId(this NetworkIdentity ni)
{
return ni.netId;
}
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="nm"></param>
/// <param name="msg"></param>
/// <param name="channel"></param>
public static void ClientSend<T>(NetworkManager nm, T msg, int channel) where T : struct, NetworkMessage
{
NetworkClient.Send(msg, channel);
}
/// <summary>
/// Sends a message to all clients.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="nm"></param>
/// <param name="msg"></param>
/// <param name="channel"></param>
public static void ServerSendToAll<T>(NetworkManager nm, T msg, int channel) where T : struct, NetworkMessage
{
NetworkServer.SendToAll(msg, channel, true);
}
/// <summary>
/// Returns true if object has an owner.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnHasOwner(this NetworkBehaviour nb)
{
return (nb.connectionToClient != null);
}
/// <summary>
/// Returns the networkId for the networkIdentity.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReturnNetId(this NetworkBehaviour nb)
{
return nb.netIdentity.netId;
}
/// <summary>
/// Returns current owner of this object.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NetworkConnection ReturnOwner(this NetworkBehaviour nb)
{
return nb.connectionToClient;
}
/// <summary>
/// Returns if current client has authority.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnHasAuthority(this NetworkBehaviour nb)
{
return nb.hasAuthority;
}
/// <summary>
/// Returns if is server.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnIsServer(this NetworkBehaviour nb)
{
return nb.isServer;
}
/// <summary>
/// Returns if is server only.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnIsServerOnly(this NetworkBehaviour nb)
{
return nb.isServerOnly;
}
/// <summary>
/// Returns if is client.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnIsClient(this NetworkBehaviour nb)
{
return nb.isClient;
}
/// <summary>
/// Returns if client is active.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnServerActive(NetworkManager nm)
{
return NetworkServer.active;
}
/// <summary>
/// Returns if client is active.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReturnClientActive(NetworkManager nm)
{
return NetworkClient.active;
}
/// <summary>
/// Returns if a connection is ready.
/// </summary>
/// <param name="nc"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsReady(this NetworkConnection nc)
{
return nc.isReady;
}
/// <summary>
/// Returns currently spawned NetworkIdentities.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<uint, NetworkIdentity> ReturnSpawned(NetworkManager nm)
{
return NetworkIdentity.spawned;
}
/// <summary>
/// Sets MTU values.
/// </summary>
/// <param name="reliable"></param>
/// <param name="unreliable"></param>
/// <returns></returns>
public static void SetMTU(ref int reliable, ref int unreliable, int maxPacketSize)
{
if (Transport.activeTransport != null)
{
reliable = Mathf.Min(maxPacketSize, Transport.activeTransport.GetMaxPacketSize(0));
unreliable = Mathf.Min(maxPacketSize, Transport.activeTransport.GetMaxPacketSize(1));
}
//If packet sizes are not calculated then use max.
if (reliable == -1)
reliable = maxPacketSize;
if (unreliable == -1)
unreliable = maxPacketSize;
}
}
}

View File

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

View File

@@ -0,0 +1,176 @@
using System;
using UnityEngine;
namespace FirstGearGames.Utilities.Networks
{
public static class Quaternions
{
/// <summary>
/// Credit to this man for converting gaffer games c code to c#
/// https://gist.github.com/fversnel/0497ad7ab3b81e0dc1dd
/// </summary>
private enum ComponentType : uint
{
X = 0,
Y = 1,
Z = 2,
W = 3
}
// note: 1.0f / sqrt(2)
private const float Maximum = +1.0f / 1.414214f;
private const int BitsPerAxis = 10;
private const int LargestComponentShift = BitsPerAxis * 3;
private const int AShift = BitsPerAxis * 2;
private const int BShift = BitsPerAxis * 1;
private const int IntScale = (1 << (BitsPerAxis - 1)) - 1;
private const int IntMask = (1 << BitsPerAxis) - 1;
internal static uint Compress(Quaternion quaternion)
{
float absX = Mathf.Abs(quaternion.x);
float absY = Mathf.Abs(quaternion.y);
float absZ = Mathf.Abs(quaternion.z);
float absW = Mathf.Abs(quaternion.w);
ComponentType largestComponent = ComponentType.X;
float largestAbs = absX;
float largest = quaternion.x;
if (absY > largestAbs)
{
largestAbs = absY;
largestComponent = ComponentType.Y;
largest = quaternion.y;
}
if (absZ > largestAbs)
{
largestAbs = absZ;
largestComponent = ComponentType.Z;
largest = quaternion.z;
}
if (absW > largestAbs)
{
largestComponent = ComponentType.W;
largest = quaternion.w;
}
float a = 0;
float b = 0;
float c = 0;
switch (largestComponent)
{
case ComponentType.X:
a = quaternion.y;
b = quaternion.z;
c = quaternion.w;
break;
case ComponentType.Y:
a = quaternion.x;
b = quaternion.z;
c = quaternion.w;
break;
case ComponentType.Z:
a = quaternion.x;
b = quaternion.y;
c = quaternion.w;
break;
case ComponentType.W:
a = quaternion.x;
b = quaternion.y;
c = quaternion.z;
break;
}
if (largest < 0)
{
a = -a;
b = -b;
c = -c;
}
uint integerA = ScaleToUint(a);
uint integerB = ScaleToUint(b);
uint integerC = ScaleToUint(c);
return (((uint)largestComponent) << LargestComponentShift) | (integerA << AShift) | (integerB << BShift) | integerC;
}
private static uint ScaleToUint(float v)
{
float normalized = v / Maximum;
return (uint)Mathf.RoundToInt(normalized * IntScale) & IntMask;
}
private static float ScaleToFloat(uint v)
{
float unscaled = v * Maximum / IntScale;
if (unscaled > Maximum)
unscaled -= Maximum * 2;
return unscaled;
}
internal static Quaternion Decompress(uint compressed)
{
var largestComponentType = (ComponentType)(compressed >> LargestComponentShift);
uint integerA = (compressed >> AShift) & IntMask;
uint integerB = (compressed >> BShift) & IntMask;
uint integerC = compressed & IntMask;
float a = ScaleToFloat(integerA);
float b = ScaleToFloat(integerB);
float c = ScaleToFloat(integerC);
Quaternion rotation;
switch (largestComponentType)
{
case ComponentType.X:
// (?) y z w
rotation.y = a;
rotation.z = b;
rotation.w = c;
rotation.x = Mathf.Sqrt(1 - rotation.y * rotation.y
- rotation.z * rotation.z
- rotation.w * rotation.w);
break;
case ComponentType.Y:
// x (?) z w
rotation.x = a;
rotation.z = b;
rotation.w = c;
rotation.y = Mathf.Sqrt(1 - rotation.x * rotation.x
- rotation.z * rotation.z
- rotation.w * rotation.w);
break;
case ComponentType.Z:
// x y (?) w
rotation.x = a;
rotation.y = b;
rotation.w = c;
rotation.z = Mathf.Sqrt(1 - rotation.x * rotation.x
- rotation.y * rotation.y
- rotation.w * rotation.w);
break;
case ComponentType.W:
// x y z (?)
rotation.x = a;
rotation.y = b;
rotation.z = c;
rotation.w = Mathf.Sqrt(1 - rotation.x * rotation.x
- rotation.y * rotation.y
- rotation.z * rotation.z);
break;
default:
// Should never happen!
throw new ArgumentOutOfRangeException("Unknown rotation component type: " +
largestComponentType);
}
return rotation;
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace FirstGearGames.Utilities.Maths
{
public static class Quaternions
{
/// <summary>
/// Returns if a rotational value matches another. This method is preferred over Equals or == since those variations allow larger differences before returning false.
/// </summary>
/// <param name="r"></param>
/// <param name="target"></param>
/// <param name="distance"></param>
/// <returns></returns>
public static bool Matches(this Quaternion r, Quaternion target, float? distance = null)
{
if (distance == null)
{
return (r.w == target.w && r.x == target.x && r.y == target.y && r.z == target.z);
}
else
{
float a = Vector3.SqrMagnitude(r.eulerAngles - target.eulerAngles);
return (a <= (distance * distance));
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
using FirstGearGames.Utilities.Maths;
namespace FirstGearGames.Utilities.Structures
{
[System.Serializable]
public struct FloatRange
{
public FloatRange(float minimum, float maximum)
{
Minimum = minimum;
Maximum = maximum;
}
/// <summary>
/// Minimum range.
/// </summary>
public float Minimum;
/// <summary>
/// Maximum range.
/// </summary>
public float Maximum;
/// <summary>
/// Returns a random value between Minimum and Maximum.
/// </summary>
/// <returns></returns>
public float RandomInclusive()
{
return Floats.RandomInclusiveRange(Minimum, Maximum);
}
}
}

View File

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

View File

@@ -0,0 +1,43 @@
using FirstGearGames.Utilities.Maths;
namespace FirstGearGames.Utilities.Structures
{
[System.Serializable]
public struct IntRange
{
public IntRange(int minimum, int maximum)
{
Minimum = minimum;
Maximum = maximum;
}
/// <summary>
/// Minimum range.
/// </summary>
public int Minimum;
/// <summary>
/// Maximum range.
/// </summary>
public int Maximum;
/// <summary>
/// Returns an exclusive random value between Minimum and Maximum.
/// </summary>
/// <returns></returns>
public float RandomExclusive()
{
return Ints.RandomExclusiveRange(Minimum, Maximum);
}
/// <summary>
/// Returns an inclusive random value between Minimum and Maximum.
/// </summary>
/// <returns></returns>
public float RandomInclusive()
{
return Ints.RandomInclusiveRange(Minimum, Maximum);
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using FirstGearGames.Utilities.Maths;
using UnityEngine;
namespace FirstGearGames.Utilities.Structures
{
[System.Serializable]
public struct Vector2Range
{
public Vector2Range(Vector2 minimum, Vector2 maximum)
{
X = new FloatRange(minimum.x, maximum.x);
Y = new FloatRange(minimum.y, maximum.y);
}
public Vector2Range(FloatRange minimum, FloatRange maximum)
{
X = minimum;
Y = maximum;
}
/// <summary>
/// Minimum range.
/// </summary>
public FloatRange X;
/// <summary>
/// Maximum range.
/// </summary>
public FloatRange Y;
/// <summary>
/// Returns a random value between Minimum and Maximum.
/// </summary>
/// <returns></returns>
public Vector2 RandomInclusive()
{
return new Vector2(
Floats.RandomInclusiveRange(X.Minimum, X.Maximum),
Floats.RandomInclusiveRange(Y.Minimum, Y.Maximum)
);
}
}
}

View File

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

View File

@@ -0,0 +1,112 @@
using System.Collections.Generic;
using UnityEngine;
namespace FirstGearGames.Utilities.Objects
{
public static class Transforms
{
/// <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>
/// 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>(Transform parent, List<T> results, bool includeParent = true, bool includeInactive = false) where T : Component
{
if (!includeParent)
{
List<T> current = new List<T>();
for (int i = 0; i < parent.childCount; i++)
{
parent.GetChild(i).GetComponentsInChildren(includeInactive, current);
results.AddRange(current);
}
}
else
{
parent.GetComponentsInChildren(includeInactive, results);
}
}
/// <summary>
/// Returns the position of this transform.
/// </summary>
public static Vector3 GetPosition(this Transform t, bool localSpace)
{
return (localSpace) ? t.localPosition : t.position;
}
/// <summary>
/// Returns the rotation of this transform.
/// </summary>
public static Quaternion GetRotation(this Transform t, bool localSpace)
{
return (localSpace) ? t.localRotation : t.rotation;
}
/// <summary>
/// Returns the scale of this transform.
/// </summary>
public static Vector3 GetScale(this Transform t)
{
return t.localScale;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetPosition(this Transform t, bool localSpace, Vector3 pos)
{
if (localSpace)
t.localPosition = pos;
else
t.position = pos;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetRotation(this Transform t, bool localSpace, Quaternion rot)
{
if (localSpace)
t.localRotation = rot;
else
t.rotation = rot;
}
/// <summary>
/// Sets the position of this transform.
/// </summary>
/// <param name="t"></param>
/// <param name="localSpace"></param>
public static void SetScale(this Transform t, Vector3 scale)
{
t.localScale = scale;
}
}
}

View File

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

View File

@@ -0,0 +1,221 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FirstGearGames.Utilities.Maths
{
public static class Vectors
{
/// <summary>
/// Vector3.zero.
/// </summary>
private static readonly Vector3 VECTOR3_ZERO = new Vector3(0.0f, 0.0f, 0.0f);
/// <summary>
/// Float epislon.
/// </summary>
private const float FLOAT_EPSILON = 0.00001f;
/// <summary>
/// Float square epislon.
/// </summary>
private const float FLOAT_SQR_EPSILON = 1e-15f;
#region Vector3.
/// <summary>
/// Calculates the linear parameter t that produces the interpolant value within the range [a, b].
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="value"></param>
/// <returns></returns>
public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value)
{
Vector3 ab = b - a;
Vector3 av = value - a;
return Mathf.Clamp01(Vector3.Dot(av, ab) / Vector3.Dot(ab, ab));
}
/// <summary>
/// Returns if the target Vector3 is within variance of the source Vector3.
/// </summary>
/// <param name="a">Source vector.</param>
/// <param name="b">Target vector.</param>
/// <param name="tolerance">How close the target vector must be to be considered close.</param>
/// <returns></returns>
public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f)
{
return (Vector3.Distance(a, b) <= tolerance);
}
/// <summary>
/// Returns if any values within a Vector3 are NaN.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static bool IsNan(this Vector3 source)
{
return (float.IsNaN(source.x) || float.IsNaN(source.y) || float.IsNaN(source.z));
}
/// <summary>
/// Lerp between three Vector3 values.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="percent"></param>
/// <returns></returns>
public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent)
{
Vector3 r0 = Vector3.Lerp(a, b, percent);
Vector3 r1 = Vector3.Lerp(b, c, percent);
return Vector3.Lerp(r0, r1, percent);
}
/// <summary>
/// Lerp between three Vector3 values.
/// </summary>
/// <param name="vectors"></param>
/// <param name="percent"></param>
/// <returns></returns>
public static Vector3 Lerp3(Vector3[] vectors, float percent)
{
if (vectors.Length < 3)
{
Debug.LogWarning("Vectors -> Lerp3 -> Vectors length must be 3.");
return Vector3.zero;
}
return Lerp3(vectors[0], vectors[1], vectors[2], percent);
}
/// <summary>
/// Multiplies a Vector3 by another.
/// </summary>
/// <param name="src"></param>
/// <param name="multiplier"></param>
/// <returns></returns>
public static Vector3 Multiply(this Vector3 src, Vector3 multiplier)
{
return new Vector3(src.x * multiplier.x, src.y * multiplier.y, src.z * multiplier.z);
}
#region Fast.
/* Fast checks are property of:
* Copyright (c) 2020 Maxim Munnig Schmidt
*
* 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.
*/
/// <summary>
/// Fast Distance.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float FastDistance(Vector3 a, Vector3 b)
{
var distx = a.x - b.x;
var disty = a.y - b.y;
var distz = a.z - b.z;
return (float)Math.Sqrt(distx * distx + disty * disty + distz * distz);
}
/// <summary>
/// Fast SqrMagnitude.
/// </summary>
/// <param name="vector"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float FastSqrMagnitude(Vector3 vector)
{
return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
}
/// <summary>
/// Fast Normalize.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 FastNormalize(Vector3 value)
{
float mag = (float)Math.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z); //Magnitude(value);
if (mag > FLOAT_EPSILON)
{
Vector3 result;
result.x = value.x / mag;
result.y = value.y / mag;
result.z = value.z / mag;
return result;// value / mag;
}
else
return VECTOR3_ZERO;
}
#endregion
#endregion
#region Vector2.
/// <summary>
/// Lerp between three Vector2 values.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="percent"></param>
/// <returns></returns>
public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent)
{
Vector2 r0 = Vector2.Lerp(a, b, percent);
Vector2 r1 = Vector2.Lerp(b, c, percent);
return Vector2.Lerp(r0, r1, percent);
}
/// <summary>
/// Lerp between three Vector2 values.
/// </summary>
/// <param name="vectors"></param>
/// <param name="percent"></param>
/// <returns></returns>
public static Vector2 Lerp2(Vector2[] vectors, float percent)
{
if (vectors.Length < 3)
{
Debug.LogWarning("Vectors -> Lerp3 -> Vectors length must be 3.");
return Vector2.zero;
}
return Lerp3(vectors[0], vectors[1], vectors[2], percent);
}
/// <summary>
/// Multiplies a Vector2 by another.
/// </summary>
/// <param name="src"></param>
/// <param name="multiplier"></param>
/// <returns></returns>
public static Vector2 Multiply(this Vector2 src, Vector2 multiplier)
{
return new Vector2(src.x * multiplier.x, src.y * multiplier.y);
}
#endregion
}
}

View File

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