Skip to content

Instantly share code, notes, and snippets.

@omgwtfgames
Last active March 2, 2024 17:44
Show Gist options
  • Star 74 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save omgwtfgames/f917ca28581761b8100f to your computer and use it in GitHub Desktop.
Save omgwtfgames/f917ca28581761b8100f to your computer and use it in GitHub Desktop.
Some useful extension method for Unity3D
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
Copy link

mollsju commented Oct 11, 2017

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.

@david-jares
Copy link

@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.

@david-jares
Copy link

david-jares commented Feb 5, 2019

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

@dracolytch
Copy link

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

@AbandonedCrypt
Copy link

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.

@omgwtfgames
Copy link
Author

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.

@omgwtfgames
Copy link
Author

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 ).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment