Notifications
Article
Convenient Extension Methods
Published a year ago
141
0
Some Extension Methods I put in all of my projects
One of the unsung tricks in C# that is seldom mentioned in Unity is the Extension Method. An Extension method allows you to extend a class without actually having the source code or full access to the class. It allows you to extend functionality to any C# class that you can use in Unity. I'm not going to go into the full details here of how to create an extension method, but if you look at the source code you should get the idea fairly quickly.
All Extension Methods must be static methods. This isn't a problem because the very first parameter of an Extension Method is a link to the specific instance of the class you're extending... here's an example:
public static int Floor(this int i, int floor) { return Mathf.Max(i floor); }
What this provides is a simple function that will return a number that is never less than floor.
i-=1; i=i.Floor(0); //Ensure I never drops below 0
I have a few simple rules I apply to all my extension methods, to avoid unwanted side effects. These rules are part of what some programming styles call "pure functions".
  • No variables should be accessed from outside sources. Everything must be passed as a parameter.
  • No caching of variables. Whenever possible, do not create temporary variables.
  • Reuse of class statics like Mathf's functions is allowed, as these are generally pure functions as well.
  • The function should do exactly what it says it will.
  • The function should not change anything at all. It's only job is to return a value. No unintended side effects.
  • The function's results should be repeatable. Given the same inputs, the same result should occur. For example: print(i.Clamp(0,100)); should print i every time unless it is outside the range, in which case it should return 0 or 100. Calling it 3 times in a row where i is -7 should return 0 every time.
  • Where possible, no loops. Recursion is allowed (although none of these methods employ recursion).
Without further ado, here is the extension class file. Just add the file into your project. Any class that wishes to use the methods should add using Tk.Extensions; to the top of your using statements.
using UnityEngine; namespace Tk.Extensions { /// <summary> /// Useful extensions for common mathmatical functions. Many of these functions simply invoke Mathf functions, /// but they can now be called by simply calling the extension method on the value... example: int i=4; float f=1/i.F instead of /// int i=4, float f=1/(float)i; /// </summary> public static class MathExtensions { // Whenever possible, this class tries to stick to simply pure functions. // Pure rules: // 1) No variables accessed from outside sources, only what's in the parameters. // 2) No caching of variables, i.e., whenever possible, no creating temporary variables. // 3) Reuse of class statics functions like Mathf is allowed (as most of these are actually pure functions). // 4) The function should do exactly what it says it will. Results should be predictable and repeatable. // 5) Where possible, no loops. Recursion is allowed. /// <summary> /// Returns the absolute value of this integer. /// </summary> /// <param name="i"></param> /// <returns></returns> public static int Abs(this int i) { return Mathf.Abs(i); } /// <summary> /// Returns the absolute value of this float. /// </summary> /// <param name="f"></param> /// <returns>The absolute value of f</returns> public static float Abs(this float f) { return Mathf.Abs(f); } /// <summary> /// Returns the integer part of this float. e.g. (int)f /// </summary> /// <param name="f"></param> /// <returns></returns> public static int I(this float f) { return (int)f; } /// <summary> /// Returns this int converted to float value. e.g. (float)i /// </summary> /// <param name="i"></param> /// <returns></returns> public static float F(this int i) { return (float)i; } /// <summary> /// If f is lower than min returns min, otherwise f /// </summary> /// <param name="f"></param> /// <param name="min"></param> /// <returns></returns> public static float Floor(this float f, float min) { return Mathf.Max(f, min); } /// <summary> /// If f is greater than max, returns max, otherwise f /// </summary> /// <param name="f"></param> /// <param name="max"></param> /// <returns></returns> public static float Ceil(this float f, float max) { return Mathf.Min(f, max); } /// <summary> /// Clamps value of f between min and max. /// </summary> /// <param name="f"></param> /// <param name="min"></param> /// <param name="max"></param> /// <returns></returns> public static float Clamp(this float f, float min, float max) { return Mathf.Clamp(f, min, max); } /// <summary> /// If i is lower than min return min, otherwise i /// </summary> /// <param name="i"></param> /// <param name="min"></param> /// <returns></returns> public static int Floor(this int i, int min) { return Mathf.Max(i, min); } /// <summary> /// if i is greater than max, return max, otherwise i /// </summary> /// <param name="i"></param> /// <param name="max"></param> /// <returns></returns> public static int Ceil(this int i, int max) { return Mathf.Min(i, max); } /// <summary> /// Clamps i's value between min and max /// </summary> /// <param name="i"></param> /// <param name="min"></param> /// <param name="max"></param> /// <returns></returns> public static int Clamp(this int i, int min, int max) { return Mathf.Clamp(i, min, max); } /// <summary> /// A simple way of adding two ints and moduloing the result. /// </summary> /// <param name="i"></param> /// <param name="adder"></param> /// <param name="modulo"></param> /// <returns></returns> public static int AddMod(this int i, int adder, int modulo) { return (i + adder) % modulo; } /// <summary> /// Subtracts subber from i and % modulo /// </summary> /// <param name="i"></param> /// <param name="subber"></param> /// <param name="modulo"></param> /// <returns></returns> public static int SubMod(this int i, int subber, int modulo) { return (i - subber) % modulo; } /// <summary> /// Increments i, and applies result % modulo. /// </summary> /// <param name="i"></param> /// <param name="modulo"></param> /// <returns></returns> public static int IncMod(this int i, int modulo) { return (i + 1) % modulo; } /// <summary> /// Decrements i and applies result % modulo /// </summary> /// <param name="i"></param> /// <param name="modulo"></param> /// <returns></returns> public static int DecMod(this int i, int modulo) { return (i - 1) % modulo; } public static float Sin(this float f) { return Mathf.Sin(f); } public static float Cos(this float f) { return Mathf.Cos(f); } public static bool Bool(this int i) { return (i != 0); } public static int Sign(this int i) { return (int)Mathf.Sign(i); } public static int Sign(this float f) { return (int)Mathf.Sign(f); } /// <summary> /// Returns the distance from this vector to target Vector. /// </summary> /// <param name="v"></param> /// <param name="target"></param> /// <returns></returns> public static float Distance(this Vector3 v, Vector3 target) { return Vector3.Distance(v, target); } /// <summary> /// Returns this Vector, replacing value of z /// </summary> /// <param name="v"></param> /// <param name="z"></param> /// <returns></returns> public static Vector3 subZ(this Vector3 v, float z) { return new Vector3(v.x, v.y, z); } /// <summary> /// Returns this vector, replacing value of y /// </summary> /// <param name="v"></param> /// <param name="y"></param> /// <returns></returns> public static Vector3 SubY(this Vector3 v, float y) { return new Vector3(v.x, y, v.z); } /// <summary> /// Returns this vector, replacing value of x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static Vector3 SubX(this Vector3 v, float x) { return new Vector3(x, v.y, v.z); } /// <summary> /// Returns this vector with v.x adjusted by x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static Vector3 AddX(this Vector3 v, float x) { return new Vector3(v.x + x, v.y, v.z); } /// <summary> /// Returns this vector with v.x adjusted by x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static Vector3 AddY(this Vector3 v, float y) { return new Vector3(v.x, v.y + y, v.z); } /// <summary> /// Returns this vector with v.x adjusted by x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static Vector3 AddZ(this Vector3 v, float z) { return new Vector3(v.x, v.y, v.z + z); } /// <summary> /// Returns a vector facing target /// </summary> /// <param name="v"></param> /// <param name="target"></param> /// <returns></returns> public static Vector3 FaceTowards(this Vector3 v, Vector3 target) { return target - v; } /// <summary> /// Returns a vector facing from the given transform to the target. /// </summary> /// <param name="t"></param> /// <param name="target"></param> /// <returns></returns> public static Vector3 FaceTowards(this Transform t, Vector3 target) { return target - t.position; } /// <summary> /// Returns a vector facing the target from this object. /// </summary> /// <param name="b"></param> /// <param name="target"></param> /// <returns></returns> public static Vector3 FaceTowards(this MonoBehaviour b, MonoBehaviour target) { return target.transform.position - b.transform.position; } /// <summary> /// returns a vector facing away from target /// </summary> /// <param name="v"></param> /// <param name="target"></param> /// <returns></returns> public static Vector3 FaceAway(this Vector3 v, Vector3 target) { return v - target; } /// <summary> /// Returns a vector facing from the target to the calling transform. /// </summary> /// <param name="t"></param> /// <param name="target"></param> /// <returns></returns> public static Vector3 FaceAway(this Transform t, Vector3 target) { return t.position - target; } public static Vector3 FaceAway(this MonoBehaviour b, MonoBehaviour target) { return b.transform.position - target.transform.position; } /// <summary> /// Returns the signed angle between this transform and the target. (For turning calculations). /// </summary> /// <param name="t"></param> /// <param name="target"></param> /// <returns></returns> public static float SignedAngleTo(this Transform t, Vector3 target) { return Vector3.SignedAngle(t.forward, t.FaceTowards(target), t.up); } public static float SignedAngleTo(this Transform t, Transform target) { return Vector3.SignedAngle(t.forward, t.FaceTowards(target.position), t.up); } public static Quaternion ToQuaternion(this Vector3 v) { return Quaternion.Euler(v); } public static Quaternion LookRotation(this Vector3 v) { return Quaternion.LookRotation(v); } /// <summary> /// Equivilant to f*Time.deltaTime, for framerate independence. /// </summary> /// <param name="f"></param> /// <returns></returns> public static float Frame(this float f) { return f * Time.deltaTime; } } }
A practical example, an update that makes a character pursue a target, assuming root motion on the animator.
public Transform target; public Animator anim; //for this example, presume we assigned this in Awake() void Update() { //Vector3.SignedAngle(transform.forward, target.position-transform.position, transform.up); float AimAngle = transform.SignedAngleTo(target); anim.SetFloat("Direction", AimAngle); //Vector3.Distance(transform.position, target.position); float distance = transform.position.Distance(target); anim.SetFloat("Speed", distance<5? 1:2); }
Another simpler example:
public List<Transform> waypoints; NavmeshAgent agent; //again assume this is assigned int CurrentWaypoint=0; void Update() { if(transform.Distance(waypoints[CurrentWaypoint])<2) { CurrentWaypoint=CurrentWaypoint.IncMod(waypoints.Count); //The above line replaces the following: //CurrentWaypoint++; //if(CurrentWaypoint>waypoints.Count) CurrentWaypoint =0; agent.SetDestination(waypoints[CurrentWaypoint]); } }
These may or may not be useful to folks out there. If you can make use of these, please enjoy. I'll try to update this from time to time as I add more functions that I find useful in my own work.
Brian
Brian Trotter
TkrainDesigns - Programmer
2
Comments