-
-
Save omgwtfgames/f917ca28581761b8100f to your computer and use it in GitHub Desktop.
A collection of useful C# extension methods for the Unity engine. |
MIT License | |
Copyright (c) 2021 Andrew Perry | |
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. |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
public static class ShuffleListExtensions | |
{ | |
/// <summary> | |
/// Shuffle the list in place using the Fisher-Yates method. | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="list"></param> | |
public static void Shuffle<T>(this IList<T> list) | |
{ | |
Random rng = new Random(); | |
int n = list.Count; | |
while (n > 1) | |
{ | |
n--; | |
int k = rng.Next(n + 1); | |
T value = list[k]; | |
list[k] = list[n]; | |
list[n] = value; | |
} | |
} | |
/// <summary> | |
/// Return a random item from the list. | |
/// Sampling with replacement. | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="list"></param> | |
/// <returns></returns> | |
public static T RandomItem<T>(this IList<T> list) | |
{ | |
if (list.Count == 0) throw new System.IndexOutOfRangeException("Cannot select a random item from an empty list"); | |
return list[UnityEngine.Random.Range(0, list.Count)]; | |
} | |
/// <summary> | |
/// Removes a random item from the list, returning that item. | |
/// Sampling without replacement. | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="list"></param> | |
/// <returns></returns> | |
public static T RemoveRandom<T>(this IList<T> list) | |
{ | |
if (list.Count == 0) throw new System.IndexOutOfRangeException("Cannot remove a random item from an empty list"); | |
int index = UnityEngine.Random.Range(0, list.Count); | |
T item = list[index]; | |
list.RemoveAt(index); | |
return item; | |
} | |
} |
using System; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
using System.Text; | |
public static class StringExtensionMethods | |
{ | |
public static string Truncate(this string value, int maxLength) | |
{ | |
if (string.IsNullOrEmpty(value)) return value; | |
return value.Length <= maxLength ? value : value.Substring(0, maxLength); | |
} | |
// Named format strings from object attributes. Eg: | |
// string blaStr = aPerson.ToString("My name is {FirstName} {LastName}.") | |
// From: http://www.hanselman.com/blog/CommentView.aspx?guid=fde45b51-9d12-46fd-b877-da6172fe1791 | |
public static string ToString(this object anObject, string aFormat) | |
{ | |
return ToString(anObject, aFormat, null); | |
} | |
public static string ToString(this object anObject, string aFormat, IFormatProvider formatProvider) | |
{ | |
StringBuilder sb = new StringBuilder(); | |
Type type = anObject.GetType(); | |
Regex reg = new Regex(@"({)([^}]+)(})", RegexOptions.IgnoreCase); | |
MatchCollection mc = reg.Matches(aFormat); | |
int startIndex = 0; | |
foreach (Match m in mc) | |
{ | |
Group g = m.Groups[2]; //it's second in the match between { and } | |
int length = g.Index - startIndex - 1; | |
sb.Append(aFormat.Substring(startIndex, length)); | |
string toGet = string.Empty; | |
string toFormat = string.Empty; | |
int formatIndex = g.Value.IndexOf(":"); //formatting would be to the right of a : | |
if (formatIndex == -1) //no formatting, no worries | |
{ | |
toGet = g.Value; | |
} | |
else //pickup the formatting | |
{ | |
toGet = g.Value.Substring(0, formatIndex); | |
toFormat = g.Value.Substring(formatIndex + 1); | |
} | |
//first try properties | |
PropertyInfo retrievedProperty = type.GetProperty(toGet); | |
Type retrievedType = null; | |
object retrievedObject = null; | |
if (retrievedProperty != null) | |
{ | |
retrievedType = retrievedProperty.PropertyType; | |
retrievedObject = retrievedProperty.GetValue(anObject, null); | |
} | |
else //try fields | |
{ | |
FieldInfo retrievedField = type.GetField(toGet); | |
if (retrievedField != null) | |
{ | |
retrievedType = retrievedField.FieldType; | |
retrievedObject = retrievedField.GetValue(anObject); | |
} | |
} | |
if (retrievedType != null) //Cool, we found something | |
{ | |
string result = string.Empty; | |
if (toFormat == string.Empty) //no format info | |
{ | |
result = retrievedType.InvokeMember("ToString", | |
BindingFlags.Public | BindingFlags.NonPublic | | |
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase | |
, null, retrievedObject, null) as string; | |
} | |
else //format info | |
{ | |
result = retrievedType.InvokeMember("ToString", | |
BindingFlags.Public | BindingFlags.NonPublic | | |
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase | |
, null, retrievedObject, new object[] { toFormat, formatProvider }) as string; | |
} | |
sb.Append(result); | |
} | |
else //didn't find a property with that name, so be gracious and put it back | |
{ | |
sb.Append("{"); | |
sb.Append(g.Value); | |
sb.Append("}"); | |
} | |
startIndex = g.Index + g.Length + 1; | |
} | |
if (startIndex < aFormat.Length) //include the rest (end) of the string | |
{ | |
sb.Append(aFormat.Substring(startIndex)); | |
} | |
return sb.ToString(); | |
} | |
} |
using UnityEngine; | |
public static class ExtensionMethods | |
{ | |
public static float LinearRemap(this float value, | |
float valueRangeMin, float valueRangeMax, | |
float newRangeMin, float newRangeMax) | |
{ | |
return (value - valueRangeMin) / (valueRangeMax - valueRangeMin) * (newRangeMax - newRangeMin) + newRangeMin; | |
} | |
public static int WithRandomSign(this int value, float negativeProbability = 0.5f) | |
{ | |
return Random.value < negativeProbability ? -value : value; | |
} | |
} |
using UnityEngine; | |
public static class VectorExtensionMethods { | |
public static Vector2 xy(this Vector3 v) { | |
return new Vector2(v.x, v.y); | |
} | |
public static Vector3 WithX(this Vector3 v, float x) { | |
return new Vector3(x, v.y, v.z); | |
} | |
public static Vector3 WithY(this Vector3 v, float y) { | |
return new Vector3(v.x, y, v.z); | |
} | |
public static Vector3 WithZ(this Vector3 v, float z) { | |
return new Vector3(v.x, v.y, z); | |
} | |
public static Vector2 WithX(this Vector2 v, float x) { | |
return new Vector2(x, v.y); | |
} | |
public static Vector2 WithY(this Vector2 v, float y) { | |
return new Vector2(v.x, y); | |
} | |
public static Vector3 WithZ(this Vector2 v, float z) { | |
return new Vector3(v.x, v.y, z); | |
} | |
// axisDirection - unit vector in direction of an axis (eg, defines a line that passes through zero) | |
// point - the point to find nearest on line for | |
public static Vector3 NearestPointOnAxis(this Vector3 axisDirection, Vector3 point, bool isNormalized = false) | |
{ | |
if (!isNormalized) axisDirection.Normalize(); | |
var d = Vector3.Dot(point, axisDirection); | |
return axisDirection * d; | |
} | |
// lineDirection - unit vector in direction of line | |
// pointOnLine - a point on the line (allowing us to define an actual line in space) | |
// point - the point to find nearest on line for | |
public static Vector3 NearestPointOnLine( | |
this Vector3 lineDirection, Vector3 point, Vector3 pointOnLine, bool isNormalized = false) | |
{ | |
if (!isNormalized) lineDirection.Normalize(); | |
var d = Vector3.Dot(point - pointOnLine, lineDirection); | |
return pointOnLine + (lineDirection * d); | |
} | |
} |
@mollsju I Dont think its a typo, as that is the only function that makes sense. vector2 has no Z. So when you have a vector 2 and need to convert it to vector3, you can pass the vector2 and a zvalue as an argument.
I personally also added
public static Vector3 WithAddX(this Vector3 v, float x)
{
return new Vector3(v.x + x, v.y, v.z);
}
public static Vector3 WithAddY(this Vector3 v, float y)
{
return new Vector3(v.x, v.y + y, v.z);
}
public static Vector3 WithAddZ(this Vector3 v, float z)
{
return new Vector3(v.x, v.y, v.z + z);
}
public static Vector2 WithAddX(this Vector2 v, float x)
{
return new Vector2(v.x + x, v.y);
}
public static Vector2 WithAddY(this Vector2 v, float y)
{
return new Vector2(v.x, v.y + y);
}
so that I can just add to the existing value
Some of these are really helpful. I'm also working on a library of extension methods. Would it be OK for me to integrate some of these with my project? It's MIT license, so any of these you give me permission to integrate would become MIT license.
https://github.com/dracolytch/DracoSoftwareExtensionsForUnity
I don't know why nobody mentioned this yet, but both of your random list extensions are unsafe. Accessing a random index between 0 and List.Count will result in an IndexOutOfRangeException if List.Count is hit. The last item in a List is always at index List.Count-1.
Some of these are really helpful. I'm also working on a library of extension methods. Would it be OK for me to integrate some of these with my project? It's MIT license, so any of these you give me permission to integrate would become MIT license.
https://github.com/dracolytch/DracoSoftwareExtensionsForUnity
Apologies dracolytch - I didn't see your message for some reason. I've added a very belated MIT license.
I don't know why nobody mentioned this yet, but both of your random list extensions are unsafe. Accessing a random index between 0 and List.Count will result in an IndexOutOfRangeException if List.Count is hit. The last item in a List is always at index List.Count-1.
Have you actually hit this issue in a test case, or are you inferring this from reading the code ? Since UnityEngine.Random.Range(int, int)
is exclusive of the max value, I don't think it should be an issue (it's different to UnityEngine.Random.Range(float, float)
, which is inclusive of max, according to the docs https://docs.unity3d.com/ScriptReference/Random.Range.html ).
Theres a typo in vector extensions:
public static Vector3 WithZ(this Vector2 v, float z) { return new Vector3(v.x, v.y, z); }
Should be
public static Vector2 WithZ
and it should return a Vector2, as the Vector3.WithZ is already defined.