(转载翻译):
GitHub - oxters168/UnityHelpers: Extensions and classes to ease development in Unity
1、MathHelpers
using UnityEngine;
namespace UnityHelpers
{
public static class MathHelpers
{
/// <summary>
/// 将以米/秒为单位的值转换为公里/小时 (mps * MPS_TO_KMH = kmh).
/// </summary>
public const float MPS_TO_KMH = 3.6f;
/// <summary>
/// 将以米/秒为单位的值转换为英里/小时 (mps * MPS_TO_MPH = mph).
/// </summary>
public const float MPS_TO_MPH = 2.237f;
/// <summary>
/// 将以米为单位的值转换为英里 (meters * METERS_TO_MILES = miles).
/// </summary>
public const float METERS_TO_MILES = 0.00062150404f;
/// <summary>
/// 将以米为单位的值转换为公里(meters * METERS_TO_KILOS = kilometers).
/// </summary>
public const float METERS_TO_KILOS = 0.001f;
/// <summary>
/// 返回给定索引的奇数(0 => 1, 1 => 3, 2 => 5...)
/// </summary>
/// <param name="index">奇数的索引</param>
/// <returns>一个奇数</returns>
public static int GetOddNumber(int index)
{
return index != 0 ? Mathf.RoundToInt((index - Mathf.Sign(index)) * 2 + Mathf.Sign(index)) : 0;
}
/// <summary>
/// 设置值的小数位。
/// </summary>
/// <param name="value">原始值</param>
/// <param name="places">要保留的小数位数</param>
/// <returns>仅指定小数位数的值</returns>
public static float SetDecimalPlaces(float value, uint places)
{
if (places > 0)
{
for (int i = 0; i < places; i++)
value *= 10;
value = (int)value;
for (int i = 0; i < places; i++)
value /= 10;
}
else
value = (int)value;
return value;
}
/// <summary>
/// 获取 a 和 b (a - b) 之间的数轴方向。 -1 表示“左”,1 表示“右”,0 表示相等。
/// </summary>
/// <param name="a">First value</param>
/// <param name="b">Second value</param>
/// <returns>数轴方向</returns>
public static int GetDirection(float a, float b)
{
float aRounded = SetDecimalPlaces(a, 5);
float bRounded = SetDecimalPlaces(b, 5);
return Mathf.Abs(aRounded - bRounded) > Mathf.Epsilon ? Mathf.RoundToInt(Mathf.Sign(aRounded - bRounded)) : 0;
}
/// <summary>
/// 返回角度的等效值,但在 0-360 范围内
/// </summary>
/// <param name="value">原值</param>
/// <returns>0 到 360 之间的值</returns>
public static float ThreeSixtyFi(float value)
{
while (value < 0)
value += 360;
return value % 360;
}
}
}
2、QuaternionHelpers
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityHelpers
{
public static class QuaternionHelpers
{
/// <summary>
/// 仅获取世界 X 轴的四元数旋转
/// Credit to Spikee_wave from https://forum.unity.com/threads/quaternion-to-remove-pitch.822768/
/// </summary>
/// <param name="quaternion">原来的方向</param>
/// <returns>世界 x 轴方向</returns>
public static Quaternion GetXAxisRotation(this Quaternion quaternion)
{
float a = Mathf.Sqrt((quaternion.w * quaternion.w) + (quaternion.x * quaternion.x));
return new Quaternion(x: quaternion.x, y: 0, z: 0, w: quaternion.w / a);
}
/// <summary>
/// 仅获取世界 Y 轴的四元数旋转
/// Credit to Spikee_wave from https://forum.unity.com/threads/quaternion-to-remove-pitch.822768/
/// </summary>
/// <param name="quaternion">The original orientation</param>
/// <returns>The world y-axis orientation</returns>
public static Quaternion GetYAxisRotation(this Quaternion quaternion)
{
float a = Mathf.Sqrt((quaternion.w * quaternion.w) + (quaternion.y * quaternion.y));
return new Quaternion(x: 0, y: quaternion.y, z: 0, w: quaternion.w / a);
}
/// <summary>
/// 仅获取世界 Z 轴的四元数旋转
/// Credit to Spikee_wave from https://forum.unity.com/threads/quaternion-to-remove-pitch.822768/
/// </summary>
/// <param name="quaternion">The original orientation</param>
/// <returns>The world z-axis orientation</returns>
public static Quaternion GetZAxisRotation(this Quaternion quaternion)
{
float a = Mathf.Sqrt((quaternion.w * quaternion.w) + (quaternion.z * quaternion.z));
return new Quaternion(x: 0, y: 0, z: quaternion.z, w: quaternion.w / a);
}
/// <summary>
/// 更正原始输入陀螺仪姿态值以与 Unity 兼容。
/// Source: https://gamedev.stackexchange.com/questions/174107/unity-gyroscope-orientation-attitude-wrong
/// </summary>
/// <param name="attitude">原始输入值</param>
/// <returns>“正确”的值</returns>
public static Quaternion AdjustAttitude(this Quaternion attitude)
{
return new Quaternion(attitude.x, attitude.y, -attitude.z, -attitude.w);
}
/// <summary>
/// 比较两个四元数的值
/// </summary>
/// <param name="first">The first quaternion</param>
/// <param name="second">The second quaternion</param>
/// <param name="tolerance">任何给定值的最大差异量(包括)</param>
/// <returns>如果所有值都在阈值内,则为真,否则为假</returns>
public static bool EqualTo(this Quaternion first, Quaternion second, float tolerance = float.Epsilon)
{
//float.Epsilon
//1、代表了一个无限接近于0的一个
//2、实际上代表1和一个比1大的最小值之间的差值
return Mathf.Abs(first.x - second.x) <= tolerance && Mathf.Abs(first.y - second.y) <= tolerance
&& Mathf.Abs(first.z - second.z) <= tolerance && Mathf.Abs(first.w - second.w) <= tolerance;
}
/// <summary>
/// 使用 Mathf.Approximately 比较四元数中的每个值
/// </summary>
/// <param name="first">The first orientation</param>
/// <param name="second">The second orientation</param>
/// <returns>如果所有值大致相同,则为真,否则为假</returns>
public static bool Approximately(this Quaternion first, Quaternion second)
{
//Mathf.Approximately比较两个浮点数,如果它们相互之间的差值处于较小值 (Epsilon) 范围内,则返回 true。
return Mathf.Approximately(first.x, second.x) &&
Mathf.Approximately(first.y, second.y) &&
Mathf.Approximately(first.z, second.z) &&
Mathf.Approximately(first.w, second.w);
}
/// <summary>
/// 比较两个四元数的旋转
/// </summary>
/// <param name="first">The first quaternion</param>
/// <param name="second">The second quaternion</param>
/// <param name="tolerance">旋转的最大差异量</param>
/// <returns>如果旋转在公差范围内,则为真</returns>
public static bool SameOrientationAs(this Quaternion first, Quaternion second, float tolerance = float.Epsilon)
{
return Mathf.Abs(Quaternion.Angle(first, second)) <= tolerance;
}
/// <summary>
/// Source: https://stackoverflow.com/questions/3684269/component-of-a-quaternion-rotation-around-an-axis
/// 仅沿指定轴获取四元数的弧度角
/// </summary>
/// <param name="rot">旋转四元数</param>
/// <param name="axis">要轮询的轴</param>
/// <returns>围绕给定轴的角度</returns>
public static float PollAxisSignedAngle(this Quaternion rot, Vector3 axis)
{
axis.Normalize();
Vector3 normal1, normal2;
axis.FindOrthoNormals(out normal1, out normal2);
Vector3 transformed = rot * normal1;
// 将变换后的矢量投影到平面上
Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis);
flattened.Normalize();
var sign = Mathf.Sign(Vector3.Dot(Vector3.Cross(normal1, flattened), axis));
// 获取原始向量和投影变换之间的角度以获取法线周围的角度
float a = sign * (float)Mathf.Acos(Vector3.Dot(normal1, flattened));
return a;
}
/// <summary>
/// Source: https://stackoverflow.com/questions/3684269/component-of-a-quaternion-rotation-around-an-axis
/// 仅沿指定轴获取四元数的弧度角
/// </summary>
/// <param name="rot">旋转四元数</param>
/// <param name="axis">要轮询的轴</param>
/// <returns>围绕给定轴的角度</returns>
public static float PollAxisAngle(this Quaternion rot, Vector3 axis)
{
axis.Normalize();
Vector3 normal1, normal2;
axis.FindOrthoNormals(out normal1, out normal2);
Vector3 transformed = rot * normal1;
// 将变换后的矢量投影到平面上
Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis);
flattened.Normalize();
// 获取原始向量和投影变换之间的角度以获取法线周围的角度
float a = (float)Mathf.Acos(Vector3.Dot(normal1, flattened));
return a;
}
/// <summary>
/// Source: https://stackoverflow.com/questions/3684269/component-of-a-quaternion-rotation-around-an-axis
/// 仅沿指定轴获取四元数的弧度角
/// </summary>
/// <param name="rot">旋转四元数</param>
/// <param name="axis">要轮询的轴</param>
/// <param name="axisNormal">轴的正交向量</param>
/// <returns>围绕给定轴的角度</returns>
public static float PollAxisSignedAngle(this Quaternion rot, Vector3 axis, Vector3 axisNormal)
{
axis.Normalize();
Vector3 transformed = rot * axisNormal;
// 将变换后的矢量投影到平面上
Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis);
flattened.Normalize();
var sign = Mathf.Sign(Vector3.Dot(Vector3.Cross(axisNormal, flattened), axis));
// 获取原始向量和投影变换之间的角度以获取法线周围的角度
float a = sign * (float)Mathf.Acos(Vector3.Dot(axisNormal, flattened));
return a;
}
/// <summary>
/// Source: https://stackoverflow.com/questions/3684269/component-of-a-quaternion-rotation-around-an-axis
/// 仅沿指定轴获取四元数的弧度角
/// </summary>
/// <param name="rot">旋转四元数</param>
/// <param name="axis">要轮询的轴</param>
/// <param name="axisNormal">轴的正交向量</param>
/// <returns>围绕给定轴的角度</returns>
public static float PollAxisAngle(this Quaternion rot, Vector3 axis, Vector3 axisNormal)
{
axis.Normalize();
Vector3 transformed = rot * axisNormal;
// 将变换后的矢量投影到平面上
Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis);
flattened.Normalize();
// 获取原始向量和投影变换之间的角度以获取法线周围的角度
float a = (float)Mathf.Acos(Vector3.Dot(axisNormal, flattened));
return a;
}
/// <summary>
/// 如果四元数绕轴走很长的路,那么这个函数将在轴上找到互补的短角
/// </summary>
/// <param name="value">原始四元数</param>
/// <returns>缩短的四元数值</returns>
public static Quaternion Shorten(this Quaternion value)
{
//Source: https://answers.unity.com/questions/147712/what-is-affected-by-the-w-in-quaternionxyzw.html
//“如果 w 为 - 1,则四元数定义围绕未定义轴的 +/ -2pi 旋转角度”
// 因此,通过这样做,我们检查这是否正确,如果是,则反过来
if (value.w < 0)
{
value.x = -value.x;
value.y = -value.y;
value.z = -value.z;
value.w = -value.w;
}
return value;
}
/// <summary>
/// 将旋转从局部空间转换为世界空间。
/// </summary>
/// <param name="transform">锚</param>
/// <param name="localRotation">锚点局部空间中的旋转</param>
/// <returns>世界空间中的旋转。</returns>
public static Quaternion TransformRotation(this Transform transform, Quaternion localRotation)
{
return transform.rotation * localRotation;
}
/// <summary>
/// 将旋转从世界空间转换为局部空间。
/// </summary>
/// <param name="transform">锚</param>
/// <param name="worldRotation">世界空间中的旋转</param>
/// <returns>锚点局部空间中的旋转</returns>
public static Quaternion InverseTransformRotation(this Transform transform, Quaternion worldRotation)
{
return Quaternion.Inverse(transform.rotation) * worldRotation;
}
/// <summary>
/// 确定四元数对于插值或与 transform.rotation 一起使用是否安全。
/// </summary>
/// <returns><c>false</c>如果在 Quaternion.Lerp() 中使用四元数将导致错误
/// 例如:(eg. NaN values or zero-length quaternion).
/// </returns>
/// <param name="quaternion">Quaternion.</param>
public static bool IsValid(this Quaternion quaternion)
{
//float.IsNaN用于描述非法的float,经过多次运算float值可能会出现非法情况,如除数为0.0
bool isNaN = float.IsNaN(quaternion.x + quaternion.y + quaternion.z + quaternion.w);
bool isZero = quaternion.x == 0 && quaternion.y == 0 && quaternion.z == 0 && quaternion.w == 0;
return !(isNaN || isZero);
}
/// <summary>
/// 将所有 NaN 值变为 0
/// </summary>
/// <param name="quaternion">有问题的四元数</param>
/// <returns>固定四元数</returns>
public static Quaternion FixNaN(this Quaternion quaternion)
{
if (float.IsNaN(quaternion.x))
quaternion.x = 0;
if (float.IsNaN(quaternion.y))
quaternion.y = 0;
if (float.IsNaN(quaternion.z))
quaternion.z = 0;
if (float.IsNaN(quaternion.w))
quaternion.w = 0;
return quaternion;
}
/// <summary>
/// 设置 x、y、z 和 w 值的小数位
/// </summary>
/// <param name="quaternion">原始四元数旋转</param>
/// <param name="places">要保留的小数位数</param>
/// <returns>调整了其值的小数位的四元数</returns>
public static Quaternion SetDecimalPlaces(this Quaternion quaternion, uint places)
{
return new Quaternion(MathHelpers.SetDecimalPlaces(quaternion.x, places),
MathHelpers.SetDecimalPlaces(quaternion.y, places),
MathHelpers.SetDecimalPlaces(quaternion.z, places),
MathHelpers.SetDecimalPlaces(quaternion.w, places));
}
/// <summary>
/// 获取所有四元数值之间的平均差。
/// ((x1-x2)+(y1-y2)+(z1-z2)+(w1-w2))/4
/// 如果您想比较两个旋转 Quaternion.Angle 可能对您有好处。
/// </summary>
/// <param name="rot1">The first rotation quaternion</param>
/// <param name="rot2">The second rotation quaternion</param>
/// <returns>平均差</returns>
public static float Difference(this Quaternion rot1, Quaternion rot2)
{
return ((rot1.x - rot2.x) + (rot1.y - rot2.y) + (rot1.z - rot2.z) + (rot1.w - rot2.w)) / 4;
}
/// <summary>
/// 将两个四元数相加
/// </summary>
/// <param name="rot1">The first rotation quaternion</param>
/// <param name="rot2">The second rotation quaternion</param>
/// <returns>一个四元数,其值是给定两个值的总和</returns>
public static Quaternion Add(this Quaternion rot1, Quaternion rot2)
{
return new Quaternion(rot1.x + rot2.x, rot1.y + rot2.y, rot1.z + rot2.z, rot1.w + rot2.w);
}
/// <summary>
/// 将所有四元数的值除以另一个值
/// </summary>
/// <param name="quaternion">The original quaternion</param>
/// <param name="value">要除以的值</param>
/// <returns>The result quaternion</returns>
public static Quaternion DivideBy(this Quaternion quaternion, float value)
{
return new Quaternion(quaternion.x / value,
quaternion.y / value,
quaternion.z / value,
quaternion.w / value);
}
/// <summary>
/// 添加所有四元数,然后将最终值除以给定的四元数
/// </summary>
/// <param name="quaternions">要平均的四元数列表</param>
/// <returns>平均四元数</returns>
public static Quaternion Average(this IEnumerable<Quaternion> quaternions)
{
return quaternions.Aggregate(Add).DivideBy(quaternions.Count());
}
/// <summary>
/// 添加所有四元数,然后将最终值除以给定的四元数
/// </summary>
/// <param name="quaternions">要平均的四元数列表</param>
/// <returns>平均四元数</returns>
public static Quaternion Average(params Quaternion[] quaternions)
{
return quaternions.Average();
}
}
}
3、VectorHelpers
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
namespace UnityHelpers
{
public static class VectorHelpers
{
private static Matrix4x4 OrthoX = Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0));
private static Matrix4x4 OrthoY = Matrix4x4.Rotate(Quaternion.Euler(0, 90, 0));
/// <summary>
/// 将输入 Vector2 转换为 Vector2Int,其中每个组件都应用了 Mathf.CeilToInt
/// </summary>
/// <param name="vec">输入向量</param>
/// <returns>转换后的向量</returns>
public static Vector2Int CeilToInt(this Vector2 vec)
{
//Mathf.CeilToInt:返回大于或等于参数的最小整数。
return new Vector2Int(Mathf.CeilToInt(vec.x), Mathf.CeilToInt(vec.y));
}
/// <summary>
/// 将输入 Vector2 转换为 Vector2Int,其中每个组件都应用了 Mathf.FloorToInt
/// </summary>
/// <param name="vec">输入向量</param>
/// <returns>转换后的向量</returns>
public static Vector2Int FloorToInt(this Vector2 vec)
{
//Mathf.FloorToInt:返回小于或等于 f 的最大整数。
return new Vector2Int(Mathf.FloorToInt(vec.x), Mathf.FloorToInt(vec.y));
}
/// <summary>
/// 将输入 Vector2 转换为 Vector2Int,其中每个组件都应用了 Mathf.RoundToInt
/// </summary>
/// <param name="vec">The input vector</param>
/// <returns>The converted vector</returns>
public static Vector2Int RoundToInt(this Vector2 vec)
{
//Mathf.RoundToInt
//返回舍入为最近整数的 /f/。
//如果数字结尾是 .5,从而使它处于两个整数正中间(其中一个是偶数,另一个是奇数),则返回偶数。
return new Vector2Int(Mathf.RoundToInt(vec.x), Mathf.RoundToInt(vec.y));
}
/// <summary>
/// 将输入 Vector3 转换为 Vector3Int,其中每个组件都应用了 Mathf.CeilToInt
/// </summary>
/// <param name="vec">The input vector</param>
/// <returns>The converted vector</returns>
public static Vector3Int CeilToInt(this Vector3 vec)
{
return new Vector3Int(Mathf.CeilToInt(vec.x), Mathf.CeilToInt(vec.y), Mathf.CeilToInt(vec.z));
}
/// <summary>
/// 将输入 Vector3 转换为 Vector3Int,其中每个组件都应用了 Mathf.FloorToInt
/// </summary>
/// <param name="vec">The input vector</param>
/// <returns>The converted vector</returns>
public static Vector3Int FloorToInt(this Vector3 vec)
{
return new Vector3Int(Mathf.FloorToInt(vec.x), Mathf.FloorToInt(vec.y), Mathf.FloorToInt(vec.z));
}
/// <summary>
/// 将输入 Vector3 转换为 Vector3Int,其中每个组件都应用了 Mathf.RoundToInt
/// </summary>
/// <param name="vec">The input vector</param>
/// <returns>The converted vector</returns>
public static Vector3Int RoundToInt(this Vector3 vec)
{
return new Vector3Int(Mathf.RoundToInt(vec.x), Mathf.RoundToInt(vec.y), Mathf.RoundToInt(vec.z));
}
/// <summary>
/// 检查所有值是否都为零
/// </summary>
/// <param name="vec">The vector in question</param>
/// <returns>True if all values are zero</returns>
public static bool IsZero(this Vector2 vec)
{
return Mathf.Abs(vec.x) <= float.Epsilon && Mathf.Abs(vec.y) <= float.Epsilon;
}
/// <summary>
/// 检查所有值是否都为零
/// </summary>
/// <param name="vec">The vector in question</param>
/// <returns>True if all values are zero</returns>
public static bool IsZero(this Vector3 vec)
{
return Mathf.Abs(vec.x) <= float.Epsilon && Mathf.Abs(vec.y) <= float.Epsilon && Mathf.Abs(vec.z) <= float.Epsilon;
}
/// <summary>
/// 获取最对齐的给定变换的轴
/// 在世界空间中提供方向
/// </summary>
/// <param name="orientedObject">有问题的对象</param>
/// <param name="direction">比较的方向</param>
/// <returns>属于对象的轴</returns>
public static Vector2 GetAxisAlignedTo(this Vector2 direction, Transform orientedObject)
{
Vector2[] objectAxes = new Vector2[] { orientedObject.right, orientedObject.up, -orientedObject.right, -orientedObject.up };
return direction.GetDirectionMostAlignedTo(objectAxes);
}
/// <summary>
/// 获取最对齐的方向
/// 与提供的方向
/// </summary>
/// <param name="direction">有问题的方向</param>
/// <param name="directions">要比较的方向</param>
/// <returns>给定数组的方向</returns>
public static Vector2 GetDirectionMostAlignedTo(this Vector2 direction, params Vector2[] directions)
{
Vector2 alignedAxis = Vector2.zero;
if (directions.Length > 0)
{
alignedAxis = directions[0];
float closestDot = Vector2.Dot(alignedAxis, direction);
for (int i = 1; i < directions.Length; i++)
{
var currentAxis = directions[i];
float otherDot = Vector2.Dot(currentAxis, direction);
if (otherDot > closestDot)
{
alignedAxis = currentAxis;
closestDot = otherDot;
}
}
}
return alignedAxis;
}
/// <summary>
/// 按照与给定方向最对齐的方式对给定数组重新排序
/// </summary>
/// <param name="direction">有问题的方向</param>
/// <param name="directions">要比较的方向</param>
public static void OrderDirectionsByAlignment(this Vector2 direction, Vector2[] directions)
{
if (directions.Length > 0)
{
//计算所有点积
float[] dots = new float[directions.Length];
for (int i = 0; i < directions.Length; i++)
dots[i] = Vector2.Dot(directions[i], direction);
//获取最终数组的顺序
int[] order = new int[dots.Length];
for (int i = 0; i < order.Length; i++)
order[i] = i;
for (int i = 0; i < order.Length - 1; i++)
{
for (int j = i; j < order.Length; j++)
{
float currentDot = dots[order[i]];
float nextDot = dots[order[j]];
if (nextDot > currentDot)
{
int temp = order[j];
order[j] = order[i];
order[i] = temp;
}
}
}
//重新排序数组
Vector2[] copy = (Vector2[])directions.Clone();
for (int i = 0; i < directions.Length; i++)
directions[i] = copy[order[i]];
}
}
/// <summary>
/// 获取最对齐的给定变换的轴
/// 在世界空间中提供方向
/// </summary>
/// <param name="orientedObject">有问题的对象</param>
/// <param name="direction">比较的方向</param>
/// <returns>属于对象的轴</returns>
public static Vector3 GetAxisAlignedTo(this Vector3 direction, Transform orientedObject)
{
Vector3[] objectAxes = new Vector3[] { orientedObject.forward, orientedObject.right, orientedObject.up, -orientedObject.forward, -orientedObject.right, -orientedObject.up };
return direction.GetDirectionMostAlignedTo(objectAxes);
}
/// <summary>
/// 获取最对齐的方向
/// 与提供的方向
/// </summary>
/// <param name="direction">有问题的方向</param>
/// <param name="directions">要比较的方向</param>
/// <returns>给定数组的方向</returns>
public static Vector3 GetDirectionMostAlignedTo(this Vector3 direction, params Vector3[] directions)
{
Vector3 alignedAxis = Vector3.zero;
if (directions.Length > 0)
{
alignedAxis = directions[0];
float closestDot = Vector3.Dot(alignedAxis, direction);
for (int i = 1; i < directions.Length; i++)
{
var currentAxis = directions[i];
float otherDot = Vector3.Dot(currentAxis, direction);
if (otherDot > closestDot)
{
alignedAxis = currentAxis;
closestDot = otherDot;
}
}
}
return alignedAxis;
}
/// <summary>
/// 按照与给定方向最对齐的方式对给定数组重新排序
/// </summary>
/// <param name="direction">有问题的方向</param>
/// <param name="directions">要比较的方向</param>
public static void OrderDirectionsByAlignment(this Vector3 direction, Vector3[] directions)
{
if (directions.Length > 0)
{
//计算所有点积
float[] dots = new float[directions.Length];
for (int i = 0; i < directions.Length; i++)
dots[i] = Vector3.Dot(directions[i], direction);
//获取最终数组的顺序
int[] order = new int[dots.Length];
for (int i = 0; i < order.Length; i++)
order[i] = i;
for (int i = 0; i < order.Length - 1; i++)
{
for (int j = i; j < order.Length; j++)
{
float currentDot = dots[order[i]];
float nextDot = dots[order[j]];
if (nextDot > currentDot)
{
int temp = order[j];
order[j] = order[i];
order[i] = temp;
}
}
}
//重新排序数组
Vector3[] copy = (Vector3[])directions.Clone();
for (int i = 0; i < directions.Length; i++)
directions[i] = copy[order[i]];
}
}
#region 注释
///// <summary>
/////检索向量中的哪些轴具有非零值
///// </summary>
///// <param name="vector">The vector in question</param>
///// <returns>An enum value</returns>
//public static Axis GetValuedAxes(this Vector3 vector)
//{
// return (Mathf.Abs(vector.x) > float.Epsilon ? Axis.x : Axis.none) |
// (Mathf.Abs(vector.y) > float.Epsilon ? Axis.y : Axis.none) |
// (Mathf.Abs(vector.z) > float.Epsilon ? Axis.z : Axis.none);
//}
///// <summary>
///// 检索向量中的哪些轴具有非零值
///// </summary>
///// <param name="vector">The vector in question</param>
///// <returns>An enum value</returns>
//public static Axis GetValuedAxes(this Vector2 vector)
//{
// return (Mathf.Abs(vector.x) > float.Epsilon ? Axis.x : Axis.none) |
// (Mathf.Abs(vector.y) > float.Epsilon ? Axis.y : Axis.none);
//}
#endregion
/// <summary>
/// 将 1 置于有值的位置,将 0 置于 0 的位置
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>A mask</returns>
public static Vector3 CreateMask(this Vector3 vector)
{
return new Vector3(Mathf.Abs(vector.x) > float.Epsilon ? 1 : 0,
Mathf.Abs(vector.y) > float.Epsilon ? 1 : 0, Mathf.Abs(vector.z) > float.Epsilon ? 1 : 0);
}
/// <summary>
/// 在值为 0 的位置放置 1,在非 0 的位置放置 0
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>An inverted mask</returns>
public static Vector3 CreateInvertedMask(this Vector3 vector)
{
return new Vector3(Mathf.Abs(vector.x) > float.Epsilon ? 0 : 1,
Mathf.Abs(vector.y) > float.Epsilon ? 0 : 1, Mathf.Abs(vector.z) > float.Epsilon ? 0 : 1);
}
/// <summary>
/// 将 1 置于有值的位置,将 0 置于 0 的位置
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>A mask</returns>
public static Vector2 CreateMask(this Vector2 vector)
{
return new Vector2(Mathf.Abs(vector.x) > float.Epsilon ? 1 : 0,
Mathf.Abs(vector.y) > float.Epsilon ? 1 : 0);
}
/// <summary>
/// 在值为 0 的位置放置 1,在非 0 的位置放置 0
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>An inverted mask</returns>
public static Vector2 CreateInvertedMask(this Vector2 vector)
{
return new Vector2(Mathf.Abs(vector.x) > float.Epsilon ? 0 : 1,
Mathf.Abs(vector.y) > float.Epsilon ? 0 : 1);
}
/// <summary>
/// 对于向量中的每一个值,它将1表示正,-1表示负,0表示零
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>The sign vector</returns>
public static Vector3 Sign(this Vector3 vector)
{
return new Vector3(Mathf.Abs(vector.x) > float.Epsilon ?
Mathf.Sign(vector.x) : 0, Mathf.Abs(vector.y) > float.Epsilon ?
Mathf.Sign(vector.y) : 0, Mathf.Abs(vector.z) > float.Epsilon ? Mathf.Sign(vector.z) : 0);
}
/// <summary>
/// 对于向量中的每一个值,它将1表示正,-1表示负,0表示零
/// </summary>
/// <param name="vector">The original vector</param>
/// <returns>The sign vector</returns>
public static Vector2 Sign(this Vector2 vector)
{
return new Vector2(Mathf.Abs(vector.x) > float.Epsilon ?
Mathf.Sign(vector.x) : 0, Mathf.Abs(vector.y) > float.Epsilon ? Mathf.Sign(vector.y) : 0);
}
/// <summary>
/// Source: https://stackoverflow.com/questions/3684269/component-of-a-quaternion-rotation-around-an-axis
/// 检索三维空间中与给定方向向量正交的向量
/// </summary>
/// <param name="vector">方向向量</param>
/// <param name="normal1">给定向量的第一法向量</param>
/// <param name="normal2">给定向量的第二个法向量</param>
public static void FindOrthoNormals(this Vector3 vector, out Vector3 normal1, out Vector3 normal2)
{
Vector3 w = OrthoX.MultiplyPoint(vector);
float dot = Vector3.Dot(vector, w);
if (Mathf.Abs(dot) > 0.6)
{
w = OrthoY.MultiplyPoint(vector);
}
w.Normalize();
normal1 = Vector3.Cross(vector, w);
normal1.Normalize();
normal2 = Vector3.Cross(vector, normal1);
normal2.Normalize();
}
/// <summary>
/// 如果输入向量的大小超过给定值,则返回与最大值方向相同的向量,否则返回相同的向量
/// </summary>
/// <param name="vec">The original vector</param>
/// <param name="maxMagnitude">The maximum value of the magnitude</param>
/// <returns>A vector with a magnitude that does not exceed the given max</returns>
public static Vector3 MaxMag(this Vector3 vec, float maxMagnitude)
{
if (vec.sqrMagnitude > maxMagnitude * maxMagnitude)
vec = vec.normalized * maxMagnitude;
return vec;
}
/// <summary>
/// 如果输入向量的大小低于给定值,则返回与最小大小相同方向的向量,或者返回相同的向量
/// </summary>
/// <param name="vec">The original vector</param>
/// <param name="minMagnitude">The minimum value of the magnitude</param>
/// <returns>A vector with a magnitude that does not go below the given min</returns>
public static Vector3 MinMag(this Vector3 vec, float minMagnitude)
{
if (vec.sqrMagnitude < minMagnitude * minMagnitude)
vec = vec.normalized * minMagnitude;
return vec;
}
/// <summary>
/// 如果输入向量的大小超过给定值,则返回一个具有最大大小的相同方向的向量,否则返回相同的向量
/// </summary>
/// <param name="vec">The original vector</param>
/// <param name="maxMagnitude">The maximum value of the magnitude</param>
/// <returns>大小不超过给定最大值的向量</returns>
public static Vector2 MaxMag(this Vector2 vec, float maxMagnitude)
{
if (vec.sqrMagnitude > maxMagnitude * maxMagnitude)
vec = vec.normalized * maxMagnitude;
return vec;
}
/// <summary>
/// 如果输入向量的大小低于给定值,则返回与最小值大小方向相同的向量,否则返回相同的向量
/// </summary>
/// <param name="vec">The original vector</param>
/// <param name="minMagnitude">The minimum value of the magnitude</param>
/// <returns>A vector with a magnitude that does not go below the given min</returns>
public static Vector2 MinMag(this Vector2 vec, float minMagnitude)
{
if (vec.sqrMagnitude < minMagnitude * minMagnitude)
vec = vec.normalized * minMagnitude;
return vec;
}
/// <summary>
/// 使用Mathf比较向量中的每个值。Mathf.Approximately
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <returns>如果所有值几乎相同,则为True,否则为false</returns>
public static bool Approximately(this Vector2 first, Vector2 second)
{
return Mathf.Approximately(first.x, second.x) && Mathf.Approximately(first.y, second.y);
}
/// <summary>
/// 使用Mathf比较向量中的每个值。Mathf.Approximately
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <returns>True if all values are approximately the same, false otherwise</returns>
public static bool Approximately(this Vector3 first, Vector3 second)
{
return Mathf.Approximately(first.x, second.x) && Mathf.Approximately(first.y, second.y) && Mathf.Approximately(first.z, second.z);
}
/// <summary>
/// 比较两个向量的值
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <returns>如果所有值都相等,则为真,否则为假</returns>
public static bool EqualTo(this Vector2Int first, Vector2Int second)
{
return first.x == second.x && first.y == second.y;
}
/// <summary>
/// 如果所有值都相等,则为真,否则为假
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <returns>True if all values are equal, false otherwise</returns>
public static bool EqualTo(this Vector3Int first, Vector3Int second)
{
return first.x == second.x && first.y == second.y && first.z == second.z;
}
/// <summary>
/// 比较两个向量的值
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <param name="tolerance">任何给定值的最大差值(包括)</param>
/// <returns>如果所有值都在阈值内,则为True,否则为false</returns>
public static bool EqualTo(this Vector2 first, Vector2 second, float tolerance = float.Epsilon)
{
return Mathf.Abs(first.x - second.x) <= tolerance && Mathf.Abs(first.y - second.y) <= tolerance;
}
/// <summary>
/// 比较两个向量的值
/// </summary>
/// <param name="first">The first vector</param>
/// <param name="second">The second vector</param>
/// <param name="tolerance">任何给定值的最大差值(包括)</param>
/// <returns>如果所有值都在阈值内,则为True,否则为false</returns>
public static bool EqualTo(this Vector3 first, Vector3 second, float tolerance = float.Epsilon)
{
return Mathf.Abs(first.x - second.x) <= tolerance && Mathf.Abs(first.y - second.y) <= tolerance && Mathf.Abs(first.z - second.z) <= tolerance;
}
/// <summary>
/// 生成一个vector3,其x和z值等于给定的vector2的x和y值。
/// </summary>
/// <param name="point">The original point</param>
/// <param name="yValue">The resulting vector3's y value</param>
/// <returns>A vector3 value</returns>
public static Vector3 ToXZVector3(this Vector2 point, float yValue = 0)
{
return new Vector3(point.x, yValue, point.y);
}
/// <summary>
/// 在正交轴上顺时针旋转给定向量。
///
/// By: XenoRo
/// Source: https://answers.unity.com/questions/661383/whats-the-most-efficient-way-to-rotate-a-vector2-o.html
/// </summary>
/// <param name="vector">原来的向量</param>
/// <param name="degrees">旋转的角度</param>
/// <param name="pivot">旋转的枢轴</param>
/// <returns>旋转矢量</returns>
public static Vector2 Rotate(this Vector2 vector, float degrees, Vector2 pivot = default(Vector2))
{
vector -= pivot;
vector = Quaternion.Euler(0, 0, -degrees) * vector;
vector += pivot;
return vector;
}
/// <summary>
/// 根据给定的控制点和百分比值计算贝塞尔曲线上的一个点
/// </summary>
/// <param name="controlPoints">构成贝塞尔曲线的点</param>
/// <param name="t">一个介于0到1之间的值,表示沿贝塞尔曲线移动的百分比</param>
/// <returns>贝塞尔曲线上的一个点</returns>
public static Vector3 Bezier(this IEnumerable<Vector3> controlPoints, float t)
{
IEnumerable<Vector3> decayingPoints = controlPoints;
while (decayingPoints.Count() > 1)
decayingPoints = decayingPoints.SelectEveryPair((first, second) =>
{
var difference = second - first;
var direction = difference.normalized;
var distance = difference.magnitude;
return first + direction * distance * t;
});
return decayingPoints.First();
}
/// <summary>
/// 根据给定的控制点和百分比值计算贝塞尔曲线上的一个点
/// </summary>
/// <param name="controlPoints">构成贝塞尔曲线的点</param>
/// <param name="t">一个介于0到1之间的值,表示沿贝塞尔曲线移动的百分比</param>
/// <returns>贝塞尔曲线上的一个点</returns>
public static Vector2 Bezier(this IEnumerable<Vector2> controlPoints, float t)
{
return controlPoints.Select(point => new Vector3(point.x, point.y)).Bezier(t).xy();
}
/// <summary>
/// 将一个点从一个变换的局部空间直接转换到另一个变换的局部空间
/// </summary>
/// <param name="transform">原始表示点所在空间的变换</param>
/// <param name="otherTransform">你想让点在其空间中表示的变换</param>
/// <param name="point">在原始变换空间中的点</param>
/// <returns>转换后的点</returns>
public static Vector3 TransformPointToAnotherSpace(this Transform transform, Transform otherTransform, Vector3 point)
{
var localToLocalMatrix = otherTransform.worldToLocalMatrix * transform.localToWorldMatrix;
return localToLocalMatrix.MultiplyPoint(point);
}
/// <summary>
/// 将一个点从一个变换的局部空间直接转换到另一个变换的局部空间
/// </summary>
/// <param name="transform">原始表示点所在空间的变换</param>
/// <param name="otherTransform">你想让点在其空间中表示的变换</param>
/// <param name="point">在原始变换空间中的点</param>
/// <returns>改变了方向</returns>
public static Vector3 TransformDirectionToAnotherSpace(this Transform transform, Transform otherTransform, Vector3 direction)
{
var localToLocalMatrix = otherTransform.worldToLocalMatrix * transform.localToWorldMatrix;
return localToLocalMatrix.MultiplyVector(direction);
}
/// <summary>
/// 将绝对值应用于给定vector3的所有值
/// </summary>
/// <param name="original">The original vector3</param>
/// <returns>绝对价值vector3</returns>
public static Vector3 Abs(this Vector3 original)
{
return new Vector3(Mathf.Abs(original.x), Mathf.Abs(original.y), Mathf.Abs(original.z));
}
/// <summary>
/// 将绝对值应用于给定vector2的所有值
/// </summary>
/// <param name="original">The original vector2</param>
/// <returns>Absolute valued vector2</returns>
public static Vector2 Abs(this Vector2 original)
{
return new Vector2(Mathf.Abs(original.x), Mathf.Abs(original.y));
}
/// <summary>
/// 计算一个向量的方向与另一个向量的方向接近的百分比(1表示相同,-1表示相反,0表示垂直)点,但中间更正确))。
/// </summary>
/// <param name="vector">The first vector</param>
/// <param name="otherVector">The second vector</param>
/// <returns>一个介于-1到1之间的值,表示两个向量方向之间的夹角。</returns>
public static float PercentDirection(this Vector3 vector, Vector3 otherVector)
{
return -(Vector3.Angle(vector.normalized, otherVector.normalized) / 90 - 1);
}
/// <summary>
/// 计算fromDirection和给定轴上两点之间的带符号的角度。
/// </summary>
/// <param name="point">The start point.</param>
/// <param name="otherPoint">The end point.</param>
/// <param name="fromDirection">The direction to compare to.</param>
/// <param name="axis">要测量的轴。</param>
/// <returns>由这些点产生的方向与给定方向之间的夹角。</returns>
public static float SignedAngle(this Vector3 point, Vector3 otherPoint, Vector3 fromDirection, Vector3 axis)
{
Vector3 obstacleOffset = otherPoint - point;
return Vector3.SignedAngle(fromDirection, obstacleOffset.normalized, axis);
}
/// <summary>
/// 计算从另一个方向到达一个方向的最短带符号角
/// </summary>
/// <param name="from">The start direction</param>
/// <param name="to">The end direction</param>
/// <returns>两个方向之间最短的带符号的角</returns>
public static float GetShortestSignedAngle(this Vector2 from, Vector2 to)
{
float requestedAngle = Vector2.SignedAngle(to, Vector2.up);
float currentAngle = Vector2.SignedAngle(from, Vector2.up);
Quaternion currentUpOrientation = Quaternion.AngleAxis(currentAngle, Vector3.up);
Quaternion requestedUpOrientation = Quaternion.AngleAxis(requestedAngle, Vector3.up);
Quaternion orientationDiff = requestedUpOrientation * Quaternion.Inverse(currentUpOrientation);
orientationDiff = orientationDiff.Shorten();
float angleDiff;
Vector3 axis;
orientationDiff.ToAngleAxis(out angleDiff, out axis);
return angleDiff * Mathf.Sign(Vector3.Dot(axis, Vector3.up));
}
/// <summary>
/// 计算两个方向在同一平面上的另一个方向到达一个方向的最短带符号角
/// </summary>
/// <param name="from">The start direction</param>
/// <param name="to">The end direction</param>
/// <param name="planeNormal">这两个方向所处平面的法线</param>
/// <returns>两个方向之间最短的带符号的角</returns>
public static float GetShortestSignedAngle(this Vector3 from, Vector3 to, Vector3 planeNormal)
{
Quaternion currentOrientation = Quaternion.LookRotation(from, planeNormal);
Quaternion requestedOrientation = Quaternion.LookRotation(to, planeNormal);
Quaternion orientationDiff = requestedOrientation * Quaternion.Inverse(currentOrientation);
orientationDiff = orientationDiff.Shorten();
float angleDiff;
Vector3 axis;
orientationDiff.ToAngleAxis(out angleDiff, out axis);
return angleDiff * Mathf.Sign(Vector3.Dot(axis, planeNormal));
}
/// <summary>
/// 获取顺时针方向from和to之间的角度(0/360=相同,180=相反,90/270=垂直)
/// </summary>
/// <param name="from">The start direction</param>
/// <param name="to">The end direction</param>
/// <returns>角度:从与到之间的角度</returns>
public static float GetClockwiseAngle(this Vector2 from, Vector2 to)
{
float angleOffset = from.GetShortestSignedAngle(to);
if (angleOffset < 0)
angleOffset += 360;
return angleOffset;
}
/// <summary>
/// 获取顺时针方向from和to之间的角度(0/360=相同,180=相反,90/270=垂直)
/// </summary>
/// <param name="from">The start direction</param>
/// <param name="to">The end direction</param>
/// <returns>角度:从与到之间的角度</returns>
public static float GetClockwiseAngle(this Vector3 from, Vector3 to, Vector3 planeNormal)
{
float angleOffset = from.GetShortestSignedAngle(to, planeNormal);
if (angleOffset < 0)
angleOffset += 360;
return angleOffset;
}
/// <summary>
/// 只获取向量的x和y值
/// </summary>
/// <param name="vector">Original vector</param>
/// <returns>带有请求值的Vector2</returns>
public static Vector2 xy(this Vector3 vector)
{
return new Vector2(vector.x, vector.y);
}
/// <summary>
/// 只获取向量的x和z值
/// </summary>
/// <param name="vector">Original vector</param>
/// <returns>Vector2 with requested values</returns>
public static Vector2 xz(this Vector3 vector)
{
return new Vector2(vector.x, vector.z);
}
/// <summary>
/// 只获取向量的y和z值
/// </summary>
/// <param name="vector">Original vector</param>
/// <returns>Vector2 with requested values</returns>
public static Vector2 yz(this Vector3 vector)
{
return new Vector2(vector.y, vector.z);
}
/// <summary>
/// 将给定的向量扁平化为一个平面(Vector3中的ProjectOnPlane做的基本相同)
/// </summary>
/// <param name="vector">Original vector</param>
/// <param name="originalUp">The normal of the original vector</param>
/// <param name="planeNormal">Normal of plane</param>
/// <returns>Flattened vector</returns>
public static Vector3 Planar(this Vector3 vector, Vector3 originalUp, Vector3 planeNormal)
{
return Quaternion.FromToRotation(originalUp, planeNormal) * vector;
}
/// <summary>
/// 将给定的向量扁平化为一个平面(Vector3中的ProjectOnPlane做的基本相同)
/// </summary>
/// <param name="vector">Original vector</param>
/// <param name="planeNormal">Normal of plane</param>
/// <returns>扁平的向量</returns>
public static Vector3 Planar(this Vector3 vector, Vector3 planeNormal)
{
return Quaternion.AngleAxis(90 - Vector3.Angle(vector, Vector3.up), Vector3.Cross(planeNormal, vector)) * vector;
}
/// <summary>
/// 将一个点移到一个曲面上
/// </summary>
/// <param name="point">The point to be shifted</param>
/// <param name="pointAlreadyOnSurface">A point already on the surface</param>
/// <param name="surfaceNormal">The surface's normal</param>
/// <returns>原来的点移到了水面上</returns>
public static Vector3 ProjectPointToSurface(this Vector3 point, Vector3 pointAlreadyOnSurface, Vector3 surfaceNormal)
{
Vector3 offset = Vector3.Project(point - pointAlreadyOnSurface, surfaceNormal);
return point - offset;
}
/// <summary>
/// 乘以两个Vector3s值的值(x*x, y*y, z*z)
/// </summary>
/// <param name="first">The first vector3</param>
/// <param name="second">The second vector3</param>
/// <returns>两个向量的乘积</returns>
public static Vector3 Multiply(this Vector3 first, Vector3 second)
{
return new Vector3(first.x * second.x, first.y * second.y, first.z * second.z);
}
/// <summary>
/// 将两个Vector2s值对值相乘(x*x, y*y)
/// </summary>
/// <param name="first">The first vector2</param>
/// <param name="second">The second vector2</param>
/// <returns>两个向量的乘积</returns>
public static Vector2 Multiply(this Vector2 first, Vector2 second)
{
return new Vector2(first.x * second.x, first.y * second.y);
}
/// <summary>
/// 增加了两个向量
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>The sum of the two vectors</returns>
public static Vector3 Add(this Vector3 v1, Vector3 v2)
{
return v1 + v2;
}
/// <summary>
/// 获取一个列表vector3s的平均值
/// </summary>
/// <param name="vectors">To be averaged</param>
/// <returns>The average vector</returns>
public static Vector3 Average(params Vector3[] vectors)
{
return vectors.Average();
}
/// <summary>
/// 获取一个列表vector3s的平均值
/// </summary>
/// <param name="vectors">To be averaged</param>
/// <returns>The average vector</returns>
public static Vector3 Average(this IEnumerable<Vector3> vectors)
{
return vectors.Aggregate(Add) / vectors.Count();
}
/// <summary>
/// 将所有NaN值转换为0
/// </summary>
/// <param name="vector3">The Vector3 in question</param>
/// <returns>The fixed Vector3</returns>
public static Vector3 FixNaN(this Vector3 vector3)
{
if (float.IsNaN(vector3.x))
vector3.x = 0;
if (float.IsNaN(vector3.y))
vector3.y = 0;
if (float.IsNaN(vector3.z))
vector3.z = 0;
return vector3;
}
/// <summary>
/// 增加了两个向量
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>The sum of the two vectors</returns>
public static Vector2 Add(this Vector2 v1, Vector2 v2)
{
return v1 + v2;
}
/// <summary>
/// 获取vector列表的平均值
/// </summary>
/// <param name="vectors">To be averaged</param>
/// <returns>The average vector</returns>
public static Vector2 Average(params Vector2[] vectors)
{
return vectors.Average();
}
/// <summary>
/// 获取vector列表的平均值
/// </summary>
/// <param name="vectors">To be averaged</param>
/// <returns>The average vector</returns>
public static Vector2 Average(this IEnumerable<Vector2> vectors)
{
return vectors.Aggregate(Add) / vectors.Count();
}
/// <summary>
/// 将向量的所有值固定为最小值和最大值。
/// </summary>
/// <param name="vector2">The original Vector2</param>
/// <param name="min">The clamp mininmum</param>
/// <param name="max">The clamp maximum</param>
/// <returns>A clamped Vector2</returns>
public static Vector2 Clamp(this Vector2 vector2, float min, float max)
{
return new Vector2(Mathf.Clamp(vector2.x, min, max), Mathf.Clamp(vector2.y, min, max));
}
/// <summary>
/// 将向量的所有值固定为最小值和最大值。
/// </summary>
/// <param name="vector3">The original Vector3</param>
/// <param name="min">The clamp mininmum</param>
/// <param name="max">The clamp maximum</param>
/// <returns></returns>
public static Vector3 Clamp(this Vector3 vector3, float min, float max)
{
return new Vector3(Mathf.Clamp(vector3.x, min, max), Mathf.Clamp(vector3.y, min, max), Mathf.Clamp(vector3.z, min, max));
}
/// <summary>
/// 将一个正方形的输入vector2转换为圆输入。
/// x和y的取值范围应该在-1到1之间。
/// Source: https://amorten.com/blog/2017/mapping-square-input-to-circle-in-unity/
/// </summary>
/// <param name="vector2">The original Vector2</param>
/// <returns>An adjusted Vector2</returns>
public static Vector2 ToCircle(this Vector2 vector2)
{
return new Vector2(vector2.x * Mathf.Sqrt(1 - vector2.y * vector2.y * 0.5f),
vector2.y * Mathf.Sqrt(1 - vector2.x * vector2.x * 0.5f));
}
/// <summary>
/// 将圆输入vector2转换为正方形输入。
/// x和y的取值范围应该在-1到1之间。
/// Source: https://forum.unity.com/threads/making-a-square-vector2-fit-a-circle-vector2.422352/
/// </summary>
/// <param name="vector2">The original Vector2</param>
/// <returns>An adjusted Vector2</returns>
public static Vector2 ToSquare(this Vector2 vector2)
{
const float COS_45 = 0.70710678f;
if (vector2.sqrMagnitude < float.Epsilon) // Or < EPSILON, Or < inner circle threshold. Your choice.
return Vector2.zero;
Vector2 normal = vector2.normalized;
float x, y;
if (normal.x != 0 && normal.y >= -COS_45 && normal.y <= COS_45)
x = normal.x >= 0 ? vector2.x / normal.x : -vector2.x / normal.x;
else
x = vector2.x / Mathf.Abs(normal.y);
if (normal.y != 0 && normal.x >= -COS_45 && normal.x <= COS_45)
y = normal.y >= 0 ? vector2.y / normal.y : -vector2.y / normal.y;
else
y = vector2.y / Mathf.Abs(normal.x);
return new Vector2(x, y);
}
/// <summary>
/// 设置x、y和z值的小数点后位
/// </summary>
/// <param name="vector3">The original Vector3</param>
/// <param name="places">Number of decimal places to keep</param>
/// <returns>调整了其值的小数点后的向量</returns>
public static Vector3 SetDecimalPlaces(this Vector3 vector3, uint places)
{
return new Vector3(MathHelpers.SetDecimalPlaces(vector3.x, places),
MathHelpers.SetDecimalPlaces(vector3.y, places),
MathHelpers.SetDecimalPlaces(vector3.z, places));
}
/// <summary>
/// 设置x和y值的小数点后位
/// </summary>
/// <param name="vector2">The original Vector2</param>
/// <param name="places"></param>
/// <returns>调整了其值的小数点后的向量</returns>
public static Vector2 SetDecimalPlaces(this Vector2 vector2, uint places)
{
return new Vector2(MathHelpers.SetDecimalPlaces(vector2.x, places),
MathHelpers.SetDecimalPlaces(vector2.y, places));
}
}
}
4、CameraHelpers
using UnityEngine;
using System.Linq;
namespace UnityHelpers
{
public static class CameraHelpers
{
private static RenderTexture screenshotRenderTexture;
/// <summary>
/// 获取当前帧的快照并将其存储在texture2D中
/// </summary>
/// <param name="camera">用来截屏的相机</param>
/// <param name="width">结果纹理的宽度</param>
/// <param name="height">结果纹理的高度</param>
/// <param name="depth">结果纹理的颜色深度</param>
/// <returns>A screenshot</returns>
public static Texture2D TakeScreenshot(this Camera camera, int width = 2048, int height = 2048, int depth = 32)
{
RenderTexture tempRenderTexture = camera.targetTexture; //如果用户有渲染纹理集,记住它
if (screenshotRenderTexture != null && (screenshotRenderTexture.width != width || screenshotRenderTexture.height != height || screenshotRenderTexture.depth != depth))
{
GameObject.Destroy(screenshotRenderTexture);
screenshotRenderTexture = null;
}
if (screenshotRenderTexture == null)
{
screenshotRenderTexture = new RenderTexture(width, height, depth);
}
camera.targetTexture = screenshotRenderTexture;
camera.Render();
Texture2D saveTexture = new Texture2D(screenshotRenderTexture.width, screenshotRenderTexture.height);
RenderTexture.active = screenshotRenderTexture;
Rect renderRect = new Rect(0, 0, screenshotRenderTexture.width, screenshotRenderTexture.height);
saveTexture.ReadPixels(renderRect, 0, 0); //函数从活动渲染纹理中读取
camera.targetTexture = tempRenderTexture; //如果有,返回用户的渲染纹理
return saveTexture;
}
//// <summary>
/// 返回前向距离,需要放置相机,以适应特定的世界宽度在屏幕上的关系相机的视角。
/// </summary>
/// <param name="camera">相机应用计算</param>
/// <param name="worldWidth">请求的世界宽度</param>
/// <param name="debug">在编辑器中显示截锥线</param>
/// <returns>相机前进的距离</returns>
public static float PerspectiveDistanceFromWidth(this Camera camera, float worldWidth, bool debug = false)
{
Vector2 nearDimensions = camera.PerspectiveFrustumAtNear(debug);
Vector2 farDimensions = camera.PerspectiveFrustumAtFar(debug);
float slope = ((camera.farClipPlane - camera.nearClipPlane) / (farDimensions.x - nearDimensions.x));
float intercept = camera.nearClipPlane - slope * nearDimensions.x;
return slope * worldWidth + intercept + camera.nearClipPlane;
}
/// <summary>
/// 返回前向距离,需要放置相机,以适应特定的世界高度在屏幕上相对于相机的视角。
/// </summary>
/// <param name="camera">The camera to apply the calculation to</param>
/// <param name="worldHeight">The requested world height</param>
/// <param name="debug">Show frustum lines in editor</param>
/// <returns>Camera forward distance</returns>
public static float PerspectiveDistanceFromHeight(this Camera camera, float worldHeight, bool debug = false)
{
Vector2 nearDimensions = camera.PerspectiveFrustumAtNear(debug);
Vector2 farDimensions = camera.PerspectiveFrustumAtFar(debug);
float slope = ((camera.farClipPlane - camera.nearClipPlane) / (farDimensions.y - nearDimensions.y));
float intercept = camera.nearClipPlane - slope * nearDimensions.y;
return slope * worldHeight + intercept + camera.nearClipPlane;
}
/// <summary>
/// 获取摄像机透视图的世界平面宽度和高度在指定的距离上。
/// </summary>
/// <param name="fieldOfView">The camera's field of view</param>
/// <param name="frustumDistance">Distance of the plane from the camera</param>
/// <param name="aspect">相机的纵横比</param>
/// <returns>角度平面尺寸</returns>
public static Vector2 PerspectiveFrustum(float fieldOfView, float frustumDistance, float aspect)
{
float frustumHeight = 2.0f * frustumDistance * Mathf.Tan(fieldOfView * 0.5f * Mathf.Deg2Rad);
float frustumWidth = frustumHeight * aspect;
return new Vector2(frustumWidth, frustumHeight);
}
/// <summary>
/// 获取相机透视图在指定距离上的世界平面宽度和高度。
/// </summary>
/// <param name="distance">Distance of the plane from the camera</param>
/// <returns>角度平面尺寸</returns>
public static Vector2 PerspectiveFrustum(this Camera camera, float distance)
{
return PerspectiveFrustum(camera.fieldOfView, distance, camera.aspect);
}
/// <summary>
///获取摄像机视角在远剪辑平面上的世界平面宽度和高度。
/// </summary>
/// <param name="camera">The camera to calculate the dimensions for</param>
/// <param name="debug">Show frustum lines in editor</param>
/// <returns>Perspective plane dimensions</returns>
public static Vector2 PerspectiveFrustumAtFar(this Camera camera, bool debug = false)
{
Vector2 frustumDimensions = camera.PerspectiveFrustum(camera.farClipPlane);
if (debug)
{
Vector3 farClipCenter = camera.transform.position + camera.farClipPlane * camera.transform.forward;
Vector3 halfHeight = camera.transform.up * frustumDimensions.y / 2f;
Vector3 halfWidth = camera.transform.right * frustumDimensions.x / 2f;
Debug.DrawLine(farClipCenter + halfHeight - halfWidth, farClipCenter + halfHeight + halfWidth, Color.red, 1);
Debug.DrawLine(farClipCenter + halfWidth - halfHeight, farClipCenter + halfWidth + halfHeight, Color.green, 1);
}
return frustumDimensions;
}
/// <summary>
/// 获取摄像机视角在近剪切平面上的世界平面宽度和高度。
/// </summary>
/// <param name="camera">用来计算尺寸的相机</param>
/// <param name="debug">在编辑器中显示截锥线</param>
/// <returns>角度平面尺寸</returns>
public static Vector2 PerspectiveFrustumAtNear(this Camera camera, bool debug = false)
{
Vector2 frustumDimensions = camera.PerspectiveFrustum(camera.nearClipPlane);
if (debug)
{
Vector3 nearClipCenter = camera.transform.position + camera.nearClipPlane * camera.transform.forward;
Vector3 halfHeight = camera.transform.up * frustumDimensions.y / 2f;
Vector3 halfWidth = camera.transform.right * frustumDimensions.x / 2f;
Debug.DrawLine(nearClipCenter + halfHeight - halfWidth, nearClipCenter + halfHeight + halfWidth, Color.red, 1);
Debug.DrawLine(nearClipCenter + halfWidth - halfHeight, nearClipCenter + halfWidth + halfHeight, Color.green, 1);
}
return frustumDimensions;
}
/// <summary>
/// 创建包含两个点的具有给定纵横比的矩形
/// </summary>
/// <param name="pointA">First point</param>
/// <param name="pointB">Second point</param>
/// <param name="aspect">必需的长宽比</param>
/// <returns>具有适当长宽比的矩形</returns>
public static Rect Aspectify(Vector2 pointA, Vector2 pointB, float aspect)
{
Vector2 min = new Vector2(Mathf.Min(pointA.x, pointB.x), Mathf.Min(pointA.y, pointB.y));
Vector2 max = new Vector2(Mathf.Max(pointA.x, pointB.x), Mathf.Max(pointA.y, pointB.y));
Rect toAspectify = Rect.MinMaxRect(min.x, min.y, max.x, max.y);
Vector2 maxedSize = new Vector2(Mathf.Max(toAspectify.height * aspect, toAspectify.width), Mathf.Max(toAspectify.width / aspect, toAspectify.height));
return toAspectify.ResizeFromCenter(maxedSize);
}
/// <summary>
/// 创建一个矩形,其中包含给定的所有点,而所有点都具有请求的填充
/// </summary>
/// <param name="padding">Amount to cushion by</param>
/// <param name="currentPoints">Points to include</param>
/// <returns>包含所有填充点的矩形</returns>
public static Rect PaddedMinMax(float padding, params Vector3[] currentPoints)
{
Rect paddedRect = new Rect();
if (currentPoints != null && currentPoints.Length > 1)
paddedRect = currentPoints.Select(point => new Rect(point.x, point.z, 0, 0).ResizeFromCenter(padding, padding)).Aggregate(RectHelpers.Grow);
else if (currentPoints != null && currentPoints.Length > 0)
paddedRect = new Rect(currentPoints[0].x, currentPoints[0].z, 0, 0).ResizeFromCenter(padding, padding);
return paddedRect;
}
}
}
5、Task
/****************************************************
文件:TaskWrapper.cs
作者:Edision
日期:#CreateTime#
功能:Task封装
*****************************************************/
using System;
using System.Threading;
using System.Threading.Tasks;
namespace UnityHelpers
{
//[Serializable]
public class TaskWrapper
{
/// <summary>
/// 任务名字
/// </summary>
public string name { get; private set; }
/// <summary>
/// 取消标记源
/// 1、CancellationTokenSource用于取消基于Task建立的线程(单个线程、多个线程都可以)。
/// 2、定时取消也是可以的。
/// 3、可以通过合并多个cts,达到其中一个任务取消,则任务全部取消的效果。
/// </summary>
private CancellationTokenSource cancellationTokenSource;
/// <summary>
/// 取消标记回调
/// </summary>
private Action<CancellationToken> cancellableAction;
/// <summary>
/// 取消标记回调有返回值
/// </summary>
private Func<CancellationToken, Task> funkyTask;
/// <summary>
/// 是否已经取消
/// </summary>
public bool cancelled { get; private set; }
/// <summary>
/// 只有当你没有使用TaskManagerController时才创建,
/// 否则调用TaskManagerController的RunActionAsync。
/// </summary>
/// <param name="_name">任务名字</param>
/// <param name="_cancellableAction">任务本身</param>
public TaskWrapper(string _name, Action<CancellationToken> _cancellableAction)
{
name = _name;
//task = _task;
//cancellationTokenSource = _cancellationTokenSource;
cancellableAction = _cancellableAction;
}
/// <summary>
/// 只有当你没有使用TaskManagerController时才创建,
/// 否则调用TaskManagerController的RunActionAsync。
/// </summary>
/// <param name="_name">任务名字</param>
/// <param name="_funkyTask">任务本身</param>
public TaskWrapper(string _name, Func<CancellationToken, Task> _funkyTask)
{
name = _name;
funkyTask = _funkyTask;
}
/// <summary>
/// 只有在不使用TaskManagerController的情况下才调用这个函数,
/// 或者可以使用TaskManagerController的CancelTask函数
/// </summary>
public void Cancel()
{
if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)
{
UnityEngine.Debug.Log("Sending cancel request through cancellation token source");
cancellationTokenSource.Cancel();
cancelled = true;
}
}
/// <summary>
/// 只有在不使用TaskManagerController时才调用此函数
/// </summary>
/// <param name="onBegin">任务开始时要执行的操作</param>
/// <param name="onEnd">任务结束时要执行的操作</param>
/// <returns></returns>
public async Task Start(Action<TaskWrapper> onBegin = null, Action<TaskWrapper> onEnd = null)
{
cancelled = false;
if (cancellableAction != null)
{
using (cancellationTokenSource = new CancellationTokenSource())
{
await Task.Run(() => { onBegin?.Invoke(this); cancellableAction(cancellationTokenSource.Token); onEnd?.Invoke(this); }, cancellationTokenSource.Token);
}
}
else if (funkyTask != null)
{
using (cancellationTokenSource = new CancellationTokenSource())
{
onBegin?.Invoke(this);
await Task.Run(() => { return funkyTask(cancellationTokenSource.Token); }, cancellationTokenSource.Token);
onEnd?.Invoke(this);
}
}
}
}
}
/****************************************************
文件:TaskManagerController.cs
作者:Edision
日期:#CreateTime#
功能:Task管理控制封装
*****************************************************/
using UnityEngine;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
namespace UnityHelpers
{
[DefaultExecutionOrder(-50)]
public class TaskManagerController : MonoBehaviour
{
/// <summary>
/// 最大并发任务
/// </summary>
public int maxConcurrentTasks = 5;
/// <summary>
/// 是否展示调试信息
/// </summary>
public bool showDebugMessages;
/// <summary>
/// 是否生成了管理器
/// </summary>
private static bool taskManagerCreated;
private static TaskManagerController taskManagerControllerInScene;
private static List<TaskWrapper> queuedTasks = new List<TaskWrapper>();
private static List<TaskWrapper> runningTasks = new List<TaskWrapper>();
private static List<Action> actions = new List<Action>();
async void Update()
{
if (queuedTasks.Count > 0 && runningTasks.Count < maxConcurrentTasks)
{
int i = queuedTasks.Count - 1;
TaskWrapper currentTask = queuedTasks[i];
queuedTasks.RemoveAt(i);
await currentTask.Start((task) =>
{
if (showDebugMessages)
Debug.Log("Running task " + task.name);
runningTasks.Add(task);
}, (task) =>
{
if (showDebugMessages)
Debug.Log("Completed task " + task.name);
runningTasks.Remove(task);
});
}
if (actions.Count > 0)
{
int i = actions.Count - 1;
Action action = actions[i];
actions.RemoveAt(i);
Debug.Assert(action != null, "TaskManagerController: Given action is null");
action?.Invoke();
}
}
/// <summary>
/// 检查是否存在管理器
/// </summary>
private static void CheckManagerExists()
{
if (!taskManagerCreated)
{
taskManagerCreated = true;
taskManagerControllerInScene = new GameObject("Task Manager").AddComponent<TaskManagerController>();
}
}
/// <summary>
/// 运行回调
/// </summary>
/// <param name="action"></param>
public static void RunAction(Action action)
{
if (action == null)
throw new ArgumentNullException("Action cannot be null");
actions.Insert(0, action);
CheckManagerExists();
}
/// <summary>
/// 创建Task
/// </summary>
/// <param name="name"></param>
/// <param name="action"></param>
/// <returns></returns>
public static TaskWrapper CreateTask(string name, Action<CancellationToken> action)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty or null");
if (action == null)
throw new ArgumentNullException("Action cannot be null");
return new TaskWrapper(name, action);
}
public static TaskWrapper CreateTask(string name, Func<CancellationToken, Task> action)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty or null");
if (action == null)
throw new ArgumentNullException("Action cannot be null");
return new TaskWrapper(name, action);
}
public static TaskWrapper CreateTask(Action<CancellationToken> action)
{
if (action == null)
throw new ArgumentNullException("Action cannot be null");
return new TaskWrapper("", action);
}
public static TaskWrapper CreateTask(Func<CancellationToken, Task> action)
{
if (action == null)
throw new ArgumentNullException("Action cannot be null");
return new TaskWrapper("", action);
}
public static TaskWrapper RunActionAsync(string name, Action<CancellationToken> action)
{
TaskWrapper tw = CreateTask(name, action);
QueueTask(tw);
return tw;
}
public static TaskWrapper RunActionAsync(string name, Func<CancellationToken, Task> action)
{
TaskWrapper tw = CreateTask(name, action);
QueueTask(tw);
return tw;
}
public static TaskWrapper RunActionAsync(Action<CancellationToken> action)
{
TaskWrapper tw = CreateTask(action);
QueueTask(tw);
return tw;
}
public static TaskWrapper RunActionAsync(Func<CancellationToken, Task> action)
{
TaskWrapper tw = CreateTask(action);
QueueTask(tw);
return tw;
}
public static void QueueTask(TaskWrapper task)
{
if (HasTask(task))
throw new InvalidOperationException("The given task is already queued or running");
else if (!string.IsNullOrEmpty(task.name) && HasTask(task.name))
throw new InvalidOperationException("A queued or running task already exists with the name " + task.name);
queuedTasks.Insert(0, task);
CheckManagerExists();
}
public static void CancelTask(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty or null");
if (taskManagerControllerInScene.showDebugMessages)
Debug.Log("Cancelling task " + name);
//var runningTasks = taskManagerControllerInScene.runningTasks;
TaskWrapper task = runningTasks.FirstOrDefault(checkedTask => checkedTask.name.Equals(name, StringComparison.Ordinal));
if (task == null || !task.name.Equals(name, StringComparison.Ordinal))
{
//var queuedTasks = taskManagerControllerInScene.queuedTasks;
int taskIndex = queuedTasks.FindIndex(checkedTask => checkedTask.name.Equals(name, StringComparison.Ordinal));
if (taskIndex >= 0)
queuedTasks.RemoveAt(taskIndex);
}
else
{
task.Cancel();
}
}
public static void CancelTask(TaskWrapper task)
{
if (task == null)
throw new ArgumentException("Task cannot be null");
if (taskManagerControllerInScene.showDebugMessages)
Debug.Log("Cancelling task " + task.name);
if (queuedTasks.Contains(task))
{
if (taskManagerControllerInScene.showDebugMessages)
Debug.Log("Removing " + task.name + " from queued tasks");
queuedTasks.Remove(task);
}
else if (runningTasks.Contains(task))
{
if (taskManagerControllerInScene.showDebugMessages)
Debug.Log(task.name + " is currently running, attempting to cancel then remove");
task.Cancel();
}
else
Debug.LogError("Cannot cancel " + task.name + " since it is not queued or running");
}
public static bool IsQueued(string taskName)
{
//if (string.IsNullOrEmpty(taskName))
// throw new ArgumentNullException("Task name cannot be null or empty");
return !string.IsNullOrEmpty(taskName) && queuedTasks.Exists(item => item.name.Equals(taskName, StringComparison.Ordinal));
}
public static bool IsQueued(TaskWrapper task)
{
//if (task == null)
// throw new ArgumentNullException("Task cannot be null");
return task != null && queuedTasks.Contains(task);
}
public static bool IsRunning(string taskName)
{
//if (string.IsNullOrEmpty(taskName))
// throw new ArgumentNullException("Task name cannot be null or empty");
return !string.IsNullOrEmpty(taskName) && runningTasks.Exists(item => item.name.Equals(taskName, StringComparison.Ordinal));
}
public static bool IsRunning(TaskWrapper task)
{
//if (task == null)
// throw new ArgumentNullException("Task cannot be null");
return task != null && runningTasks.Contains(task);
}
public static bool HasTask(string taskName)
{
//if (string.IsNullOrEmpty(taskName))
// throw new ArgumentNullException("Task name cannot be null or empty");
return !string.IsNullOrEmpty(taskName) && (IsQueued(taskName) || IsRunning(taskName));
}
public static bool HasTask(TaskWrapper task)
{
//if (task == null)
// throw new ArgumentNullException("Task cannot be null");
return task != null && (IsQueued(task) || IsRunning(task));
}
}
}
6、DataParser
using System;
using System.IO;
namespace UnityHelpers
{
public static class DataParser
{
#region 1 byte structures
public static bool ReadBool(Stream stream)
{
byte[] boolArray = new byte[1];
stream.Read(boolArray, 0, 1);
return BitConverter.ToBoolean(boolArray, 0);
}
public static sbyte ReadSByte(Stream stream)
{
byte[] buffer = new byte[1];
stream.Read(buffer, 0, 1);
return Convert.ToSByte(buffer[0]);
}
public static byte ReadByte(Stream stream)
{
byte[] buffer = new byte[1];
stream.Read(buffer, 0, 1);
return buffer[0];
}
public static char ReadChar(Stream stream)
{
byte[] buffer = new byte[2];
stream.Read(buffer, 0, 1);
return BitConverter.ToChar(buffer, 0);
}
#endregion
#region 2 byte structures
public static short ReadShort(Stream stream)
{
byte[] shortBytes = new byte[2];
stream.Read(shortBytes, 0, 2);
return BitConverter.ToInt16(shortBytes, 0);
}
public static ushort ReadUShort(Stream stream)
{
byte[] ushortBytes = new byte[2];
stream.Read(ushortBytes, 0, 2);
return BitConverter.ToUInt16(ushortBytes, 0);
}
#endregion
#region 4 byte structures
public static float ReadFloat(Stream stream)
{
byte[] floatBytes = new byte[4];
stream.Read(floatBytes, 0, 4);
return BitConverter.ToSingle(floatBytes, 0);
}
public static int ReadInt(Stream stream)
{
byte[] intBytes = new byte[4];
stream.Read(intBytes, 0, 4);
return BitConverter.ToInt32(intBytes, 0);
}
public static uint ReadUInt(Stream stream)
{
byte[] uintBytes = new byte[4];
stream.Read(uintBytes, 0, 4);
return BitConverter.ToUInt32(uintBytes, 0);
}
#endregion
#region 8 byte structures
public static double ReadDouble(Stream stream)
{
byte[] doubleBytes = new byte[8];
stream.Read(doubleBytes, 0, 8);
return BitConverter.ToDouble(doubleBytes, 0);
}
public static long ReadLong(Stream stream)
{
byte[] longBytes = new byte[8];
stream.Read(longBytes, 0, 8);
return BitConverter.ToInt64(longBytes, 0);
}
public static ulong ReadULong(Stream stream)
{
byte[] ulongBytes = new byte[8];
stream.Read(ulongBytes, 0, 8);
return BitConverter.ToUInt64(ulongBytes, 0);
}
#endregion
#region 16 byte structures
public static decimal ReadDecimal(Stream stream)
{
return new decimal(new int[] { ReadInt(stream), ReadInt(stream), ReadInt(stream), ReadInt(stream) }); //Big endian probably doesn't work, each individual int is flipped but their ordering is probably wrong
}
#endregion
#region Other
public static void CopyTo(this Stream from, Stream to, long amount, int bufferSize = 81920)
{
long totalCopied = 0;
byte[] buffer = new byte[bufferSize];
int actualAmountRead;
do
{
int readLength = (int)Math.Min(amount - totalCopied, bufferSize);
actualAmountRead = from.Read(buffer, 0, readLength);
if (actualAmountRead > 0)
to.Write(buffer, 0, actualAmountRead);
totalCopied += actualAmountRead;
}
while (actualAmountRead > 0);
}
#endregion
}
}
7、HealthController
/****************************************************
文件:HealthController.cs
作者:Edision
日期:#CreateTime#
功能:血条控制
*****************************************************/
using UnityEngine;
namespace UnityHelpers
{
public class HealthController : MonoBehaviour
{
[Range(0, 1)]
public float startPercent = 1;
public float fullValue = 100;
[SerializeField]
[Space(10), Tooltip("当前运行状况值,不要直接修改")]
private float value;
public HealthEvent onValueChanged, onHurt, onHealed, onDead;
private void Start()
{
SetPercent(startPercent);
}
public void HealPercent(float percent)
{
Add(percent);
}
/// <summary>
/// 恢复
/// </summary>
/// <param name="value"></param>
public void HealValue(float value)
{
value = Mathf.Clamp(Mathf.Abs(value), 0, float.MaxValue);
Add(value / fullValue);
}
public void HurtPercent(float percent)
{
Remove(percent);
}
public void HurtValue(float value)
{
value = Mathf.Clamp(Mathf.Abs(value), 0, float.MaxValue);
Remove(value / fullValue);
}
public void SetPercent(float percent)
{
float delta = percent - value;
if (delta < 0)
Remove(delta);
else if (delta > 0)
Add(delta);
}
public void SetValue(float value)
{
float percent = value / fullValue;
SetPercent(percent);
}
private void Add(float percent)
{
percent = Mathf.Clamp01(Mathf.Abs(percent));
value = Mathf.Clamp01(value + percent);
onValueChanged?.Invoke(value);
onHealed?.Invoke(percent);
}
private void Remove(float percent)
{
percent = Mathf.Clamp01(Mathf.Abs(percent));
value = Mathf.Clamp01(value - percent);
onValueChanged?.Invoke(value);
onHurt?.Invoke(-percent);
if (value <= 0)
onDead?.Invoke(-percent);
}
[System.Serializable]
public class HealthEvent : UnityEngine.Events.UnityEvent<float>
{ }
}
}
8、RailSystemController
using UnityEngine;
namespace UnityHelpers
{
/// <summary>
/// 路径点移动
/// </summary>
public class RailSystemController : MonoBehaviour
{
public Transform target;
public float targetLerp = 5;
public bool play;
[Range(0, 1)]
public float value;
[Range(-1, 1)]
public float speed = 0.01f;
public bool loop;
public bool showCheckpointsGizmos;
public PosRotWrapper[] checkpoints;
void Update()
{
if (target != null && checkpoints?.Length > 0)
{
if (loop && value >= 1)
value = 0;
float totalLength = checkpoints.Length - (loop ? 0 : 1);
int index = Mathf.FloorToInt(value * totalLength);
int nextIndex = (index + (speed > 0 ? 1 : -1) + checkpoints.Length) % checkpoints.Length;
float lerp = (value * totalLength) - index;
target.position = Vector3.Lerp(target.position, Vector3.Lerp(checkpoints[index].position, checkpoints[nextIndex].position, checkpoints[index].transition.Evaluate(lerp)), Time.deltaTime * targetLerp);
target.rotation = Quaternion.Lerp(target.rotation, Quaternion.Lerp(Quaternion.Euler(checkpoints[index].rotation), Quaternion.Euler(checkpoints[nextIndex].rotation), checkpoints[index].transition.Evaluate(lerp)), Time.deltaTime * targetLerp);
if (play)
value = Mathf.Clamp01(value + speed);
}
}
private void OnDrawGizmos()
{
if (showCheckpointsGizmos)
{
Gizmos.color = Color.green;
if (checkpoints != null)
for (int i = 0; i < checkpoints.Length; i++)
{
Gizmos.matrix = Matrix4x4.TRS(checkpoints[i].position, Quaternion.Euler(checkpoints[i].rotation), Vector3.one);
Gizmos.DrawFrustum(Vector3.zero, Camera.main.fieldOfView, 1, 0.01f, Camera.main.aspect);
}
}
}
}
[System.Serializable]
public struct PosRotWrapper
{
public Vector3 position;
public Vector3 rotation;
public AnimationCurve transition;
}
}
9、Rotator
using UnityEngine;
namespace UnityHelpers
{
public class Rotator : MonoBehaviour
{
[Tooltip("在指定轴上设置的角度")]
public float angle;
[Tooltip("角度的变化速度,单位为转/秒")]
public float rotSpeed;
[Tooltip("要旋转的局部轴")]
public Vector3 spinAxis = Vector3.up;
[Tooltip("将它的前向设置为spinAxis的转换")]
public Transform applyAxisOnTransform;
private void Update()
{
spinAxis = spinAxis.normalized;
if (applyAxisOnTransform != null)
applyAxisOnTransform.forward = spinAxis;
angle += rotSpeed * 360f * Time.deltaTime;
Quaternion quatRot = Quaternion.AngleAxis(angle, spinAxis);
transform.rotation = quatRot;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawLine(Vector3.zero, spinAxis.normalized);
}
}
}