For my current project, I’ve been splitting my code base into “core” and “universal” scripts for logic related specifically to my application, and for other general-purpose logic that could be used across any genres of games respectively.
Most of the features I have so far are minor quality-of-life things such as extension methods and custom data-types.
Here’s some notable ones:
NumberExt - Extension methods for numeric types.
public static class NumberExt {
public static float Min(this float value, float min) => value < min ? min : value;
public static float Max(this float value, float max) => value > max ? max : value;
public static float Clamp(this float value, float min, float max) => Mathf.Clamp(value, min, max);
public static float ClampLoop(this float value, float min, float max) {
if(value < min) {
value = max;
}
else if(value > max) {
value = min;
}
return value;
}
public static float Clamp01(this float value) => Mathf.Clamp01(value);
public static float MoveTowards(this float value, float target, float maxDelta) => Mathf.MoveTowards(value, target, maxDelta);
public static float Rounded(this float value) => Mathf.Round(value);
public static int RoundedInt(this float value) => Mathf.RoundToInt(value);
public static float LerpTo(this float value, float target, float percent) => Mathf.Lerp(value, target, percent);
public static float Absolute(this float value) => Mathf.Abs(value);
public static bool IsBetween(this float value, float min, float max, bool inclusive = false) =>
inclusive ?
value >= min && value <= max :
value > min && value < max;
public static bool IsApproximately(this float value, float otherValue) => Mathf.Approximately(value, otherValue);
//Similar methods for other numeric-types.
}
VectorExt - Extension methods for Vector-types
public static class VectorExt {
public static Vector3 SetValues(this Vector3 value, float? x = null, float? y = null, float? z = null) => new Vector3(x ?? value.x, y ?? value.y, z ?? value.z);
public static Vector3 ClampXYZ(this Vector3 value, float min, float max) => new Vector3(Mathf.Clamp(value.x, min, max), Mathf.Clamp(value.y, min, max), Mathf.Clamp(value.z, min, max));
public static Vector3 ClampXYZ(this Vector3 value, Vector3 min, Vector3 max) => new Vector3(Mathf.Clamp(value.x, min.x, max.x), Mathf.Clamp(value.y, min.y, max.y), Mathf.Clamp(value.z, min.z, max.z));
public static Vector3 MinXYZ(this Vector3 value, float min) => ClampXYZ(value, min, float.MaxValue);
public static Vector3 MinXYZ(this Vector3 value, Vector3 min) => ClampXYZ(value, min, new Vector3(float.MaxValue, float.MaxValue, float.MaxValue));
public static Vector3 MaxXYZ(this Vector3 value, float max) => ClampXYZ(value, float.MinValue, max);
public static Vector3 MaxXYZ(this Vector3 value, Vector3 max) => ClampXYZ(value, new Vector3(float.MinValue, float.MinValue, float.MinValue), max);
public static Vector3 ClampMagnitude(this Vector3 value, float maxLength) => Vector3.ClampMagnitude(value, maxLength);
public static Vector3 Rounded(this Vector3 value) => new Vector3(Mathf.Round(value.x), Mathf.Round(value.y), Mathf.Round(value.z));
public static Vector3 LerpTo(this Vector3 value, Vector3 target, float percent) => Vector3.Lerp(value, target, percent);
public static Vector3 SlerpTo(this Vector3 value, Vector3 target, float percent) => Vector3.Slerp(value, target, percent);
public static Vector3 MoveTowards(this Vector3 value, Vector3 target, float maxDistanceDelta) => Vector3.MoveTowards(value, target, maxDistanceDelta);
public static float DistanceTo(this Vector3 value, Vector3 target) => (target - value).magnitude;
public static float SqrDistanceTo(this Vector3 value, Vector3 target) => (target - value).sqrMagnitude;
public static float AngleTo(this Vector3 value, Vector3 target) => Vector3.Angle(value, target);
public static float DotTo(this Vector3 value, Vector3 target) => Vector3.Dot(value, target);
public static Vector3 Absolute(this Vector3 value) => new Vector3(Mathf.Abs(value.x), Mathf.Abs(value.y), Mathf.Abs(value.z));
public static Quaternion ToQuaternion(this Vector3 value) => Quaternion.Euler(value);
//Similar methods for other Vector-types.
}
QuaternionExt - Extension methods for Quaternions
public static class QuaternionExt {
public static Quaternion LookAt2D(this Quaternion value, Vector3 currentPosition, Vector3 targetPosition, float angleOffset = 0f) {
Vector3 direction = targetPosition - currentPosition;
float zAngle = (Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg) - 90f;
Vector3 eulers = value.eulerAngles;
return Quaternion.Euler(eulers.x, eulers.y, zAngle + angleOffset);
}
public static Quaternion LerpTo(this Quaternion value, Quaternion target, float percent) => Quaternion.Lerp(value, target, percent);
public static Quaternion LerpTo(this Quaternion value, Vector3 targetEulers, float percent) => Quaternion.Lerp(value, targetEulers.ToQuaternion(), percent);
public static Quaternion SlerpTo(this Quaternion value, Quaternion target, float percent) => Quaternion.Slerp(value, target, percent);
public static Quaternion SlerpTo(this Quaternion value, Vector3 targetEulers, float percent) => Quaternion.Slerp(value, targetEulers.ToQuaternion(), percent);
public static float AngleTo(this Quaternion value, Quaternion target) => Quaternion.Angle(value, target);
public static float AngleTo(this Quaternion value, Vector3 targetEulers) => Quaternion.Angle(value, targetEulers.ToQuaternion());
public static float DotTo(this Quaternion value, Quaternion target) => Quaternion.Dot(value, target);
public static float DotTo(this Quaternion value, Vector3 targetEulers) => Quaternion.Dot(value, targetEulers.ToQuaternion());
public static Quaternion RotateTowards(this Quaternion value, Quaternion target, float maxDegreesDelta) => Quaternion.RotateTowards(value, target, maxDegreesDelta);
public static Quaternion RotateTowards(this Quaternion value, Vector3 targetEulers, float maxDegreesDelta) => Quaternion.RotateTowards(value, targetEulers.ToQuaternion(), maxDegreesDelta);
public static Quaternion Clamp(this Quaternion value, Vector3 minEulers, Vector3 maxEulers) {
Vector3 valueEulers = value.eulerAngles.AsNegativeEulers();
valueEulers = valueEulers.ClampXYZ(minEulers, maxEulers);
return Quaternion.Euler(valueEulers);
}
public static Quaternion Clamp(this Quaternion value, Quaternion minRotation, Quaternion maxRotation) => Clamp(value, minRotation.eulerAngles, maxRotation.eulerAngles);
}
TransformValues
A serializable struct that can be created from a Transform’s position/rotation/scale values.
[Serializable]
public struct TransformValues {
[SerializeField] private Vector3 _position;
[SerializeField] private Vector3 _rotation;
[SerializeField] private Vector3 _scale;
public TransformValues(Vector3 position, Vector3 rotation, Vector3 scale) {
_position = position;
_rotation = rotation;
_scale = scale;
}
public TransformValues(Transform transform, bool useLocalValues = false) {
if(useLocalValues) {
_position = transform.localPosition;
_rotation = transform.localEulerAngles;
_scale = transform.localScale;
}
else {
_position = transform.position;
_rotation = transform.eulerAngles;
_scale = transform.localScale;
}
}
public void Apply(Transform transform) {
transform.position = _position;
transform.rotation = Quaternion.Euler(_rotation);
transform.localScale = _scale;
}
public void ApplyLocal(Transform transform) {
transform.localPosition = _position;
transform.localRotation = Quaternion.Euler(_rotation);
transform.localScale = _scale;
}
public Vector3 Position => _position;
public Vector3 Rotation => _rotation;
public Vector3 Scale => _scale;
}
FloatRange, IntRange, and Vector3Range
Like their names suggest, these are three different structs that represent a min/max range between float, int, and Vector3 values respectively.
They each contain custom property drawers as well as equality & arithmetic operator overloads.
Here is FloatRange as an example:
[System.Serializable]
public struct FloatRange {
public static FloatRange Zero => new FloatRange(0f, 0f);
public static FloatRange One => new FloatRange(1f, 1f);
public static FloatRange ZeroToOne => new FloatRange(0f, 1f);
public static FloatRange MinusOneToZero => new FloatRange(-1f, 0f);
public static FloatRange MinusOneToOne => new FloatRange(-1f, 1f);
public static FloatRange Clamp(FloatRange floatRange, float min = float.MinValue, float max = float.MaxValue) => new FloatRange(floatRange.Min.Clamp(min, max), floatRange.Max.Clamp(min, max));
[SerializeField] private float _min;
[SerializeField] private float _max;
public FloatRange(float min, float max) {
_min = Mathf.Min(min, max);
_max = Mathf.Max(min, max);
}
public float GetLerpValue(float lerp) => Mathf.Lerp(_min, _max, lerp);
public float GetRandomRange() => Random.Range(_min, _max);
public float ClampValueInRange(float value) => value.Clamp(_min, _max);
public bool IsValueInRange(float value, bool inclusive = false) => value.IsBetween(_min, _max, inclusive);
public float Min => _min;
public float Max => _max;
#region OPERATORS
public override int GetHashCode() => base.GetHashCode();
public override bool Equals(object obj) => base.Equals(obj);
public static bool operator ==(FloatRange a, FloatRange b) => a.Equals(b);
public static bool operator !=(FloatRange a, FloatRange b) => !a.Equals(b);
public static FloatRange operator +(FloatRange a, FloatRange b) => new FloatRange(a._min + b._min, a._max + b._max);
public static FloatRange operator +(FloatRange a, float b) => new FloatRange(a._min + b, a._max + b);
public static FloatRange operator -(FloatRange a, FloatRange b) => new FloatRange(a._min - b._min, a._max - b._max);
public static FloatRange operator -(FloatRange a, float b) => new FloatRange(a._min - b, a._max - b);
public static FloatRange operator *(FloatRange a, FloatRange b) => new FloatRange(a._min * b._min, a._max * b._max);
public static FloatRange operator *(FloatRange a, float b) => new FloatRange(a._min * b, a._max * b);
public static FloatRange operator /(FloatRange a, FloatRange b) => new FloatRange(a._min / b._min, a._max / b._max);
public static FloatRange operator /(FloatRange a, float b) => new FloatRange(a._min / b, a._max / b);
#endregion
}
Other than that, there are some MonoBehaviour components as well for simple & common use-cases.
Much of these components are really just created as I start needing them for my project, and I’ll likely keep expanding the list as I go on.
Some notable components:
-
Movers (components that alter a GameObject’s position or rotation)
-
TranslatePositioner
-
Translates an object’s position on a given Vector3 direction, at a given speed, relative to its local space or world space.
-
Can specify to move the object via Transform, Rigidbody, or Rigidbody2D.
-
FollowPositioner
-
Moves an object’s position towards a given global Vector3 point, or another Transfrom in the scene.
-
Can specify to move the object via Transform, Rigidbody, or Rigidbody2D.
-
FollowCursorPositioner
-
Same as FollowPositioner, but it follows your mouse cursor.
-
SpinRotator
-
Simply rotates an object with a speed/direction relative to each axis.
-
WobbleRotator
-
Rotates an object back-and-forth between a specified angle at a given speed.
-
Wobbling can be eased with Sin, Cos, or PingPong (linear) timing functions.
-
Each axis can wobble with their own settings independently of each other.
-
LookRotator
-
Rotates an object to face a given global Vector3 point or other Transform in the scene.
-
LookCursorRotator
-
Same as LookRotator, but it looks at your mouse cursor.
-
VelocityRotator
-
Rotates an object to face the direction it or another object its traveling in.
-
Object Detectors (components that use ray-casting or overlapping to detect things in the scene)
-
RaycastSource2D, BoxCastSource2D, CircleCastSource2D
-
Casts 2D rays/boxes/circles from the component’s position towards their local upward direction up to a given distance.
-
Can specify LayerMasks to detect.
-
Can specify a max number of objects to detect.
I.E: A cast will not detect an object behind another object if the max limit is reached.
-
BoxOverlapper2D, CircleOverlapper2D
-
Overlaps a 2D box/circle area to detect objects within around the component’s position.
-
Can specify LayerMasks to detect.
-
Can specify a max number of objects to detect.
-
Can specify only detecting objects within line-of-sight of the component’s position.
-
A min area can be specified within the max area. Objects inside the min area will not be detected.
-
The min area’s origin position can be offset anywhere within the max area.
-
Misc.
-
RotationClamper
-
Constrains an object’s rotation so that it cannot rotate past a given angle.
-
Each axis can be constrained independently of each other.
-
PAEffect
-
Name stands for “Particle-Audio Effect”.
-
Contains a ParticleSystem and/or AudioSource reference & can play them both at a specified position.
-
ObjectSpawner
-
Spawns a given GameObject prefab with a wide variety of options.
-
Can spawn objects at the component’s position, inside a sphere/circle, or at set number of Vector3 points relative to the component’s position.
-
Objects can be spawned with a randomized rotation, an identity rotation, the same rotation as the component, or with an unset rotation.
-
Spawned objects can be re-used, effectively doubling as an object-pooling system.
-
A max number of active objects can be specified.
-
Spawned objects can be set to automatically disable or destroy after a constant or min/max lifetime.
-
TimeTracker, CountTracker
-
Tracks a current float & int value respectively up to a max value.
-
Invokes UnityEvents for when the value reaches maximum or zero.
-
WaveFormScroller
-
Evaluates a set AnimationCurve over time.
-
Contains common curve presets: Sine, Triangle, Sawtooth, Square.
-
A custom AnimationCurve can be defined instead of a preset.