2024-11-03 16:42:23 +08:00
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.Events;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Newtonsoft.Json.Serialization;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using UnityEngine.UIElements;
|
2025-02-24 23:03:39 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
|
|
|
|
|
2024-11-03 16:42:23 +08:00
|
|
|
|
namespace BITKit
|
|
|
|
|
{
|
|
|
|
|
public static partial class MathQ
|
|
|
|
|
{
|
|
|
|
|
public static bool IsNaN(this Quaternion self)
|
|
|
|
|
{
|
|
|
|
|
return float.IsNaN(self.x) || float.IsNaN(self.y) || float.IsNaN(self.z) || float.IsNaN(self.w);
|
|
|
|
|
}
|
|
|
|
|
public static Quaternion AlignRotation(Quaternion rotation, float angleIncrement)
|
|
|
|
|
{
|
|
|
|
|
var eulerAngles = rotation.eulerAngles;
|
|
|
|
|
|
|
|
|
|
eulerAngles.x = Mathf.Round(eulerAngles.x / angleIncrement) * angleIncrement;
|
|
|
|
|
eulerAngles.y = Mathf.Round(eulerAngles.y / angleIncrement) * angleIncrement;
|
|
|
|
|
eulerAngles.z = Mathf.Round(eulerAngles.z / angleIncrement) * angleIncrement;
|
|
|
|
|
|
|
|
|
|
return Quaternion.Euler(eulerAngles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
public static partial class MathV
|
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
public static Matrix4x4 Rotate(Transform self, Vector3 center, Vector3 euler)
|
|
|
|
|
{
|
|
|
|
|
// 计算物体与中心点的相对位置
|
|
|
|
|
Vector3 relativePosition = self.position - center;
|
|
|
|
|
|
|
|
|
|
// 使用欧拉角来计算旋转矩阵
|
|
|
|
|
Quaternion rotation = Quaternion.Euler(euler);
|
|
|
|
|
|
|
|
|
|
// 计算旋转后的相对位置
|
|
|
|
|
Vector3 rotatedPosition = rotation * relativePosition;
|
|
|
|
|
|
|
|
|
|
var newPosition = center + rotatedPosition;
|
|
|
|
|
var newRotation = rotation * self.rotation;
|
|
|
|
|
|
|
|
|
|
return Matrix4x4.TRS(newPosition, newRotation, Vector3.one);
|
|
|
|
|
}
|
2024-11-03 16:42:23 +08:00
|
|
|
|
public static Vector3 CalculateTorque(Transform transform,Quaternion targetRotation)
|
|
|
|
|
{
|
|
|
|
|
var deltaRot = targetRotation * Quaternion.Inverse(transform.rotation);
|
|
|
|
|
deltaRot.ToAngleAxis(out var angle, out var axis);
|
|
|
|
|
var angleDelta = axis * (angle * Mathf.Deg2Rad);
|
|
|
|
|
|
|
|
|
|
angleDelta.x = MathV.TransientRotationAxis(angleDelta.x);
|
|
|
|
|
|
|
|
|
|
var torqueLocal = transform.InverseTransformDirection(angleDelta);
|
|
|
|
|
// torqueLocal = rigidbody.inertiaTensorRotation * torqueLocal;
|
|
|
|
|
// torqueLocal.Scale(rigidbody.inertiaTensor);
|
|
|
|
|
// torqueLocal = Quaternion.Inverse(rigidbody.inertiaTensorRotation) * torqueLocal;
|
|
|
|
|
var torque = transform.TransformDirection(torqueLocal);
|
|
|
|
|
|
|
|
|
|
var minTorque = MathV.TransientRotationAxis(torque);
|
|
|
|
|
|
|
|
|
|
return new Vector3()
|
|
|
|
|
{
|
|
|
|
|
x = Mathf.Min(torque.x, minTorque.x),
|
|
|
|
|
y = Mathf.Min(torque.y, minTorque.y),
|
|
|
|
|
z = Mathf.Min(torque.z, minTorque.z),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
public static Vector3 Multiply(params Vector3[] vectors)
|
|
|
|
|
{
|
|
|
|
|
var result = Vector3.one;
|
|
|
|
|
foreach (var x in vectors)
|
|
|
|
|
{
|
|
|
|
|
result.x *= x.x;
|
|
|
|
|
result.y *= x.y;
|
|
|
|
|
result.z *= x.z;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
public static bool InFovRange(Vector3 selfPosition,Vector3 selfForward, Vector3 target, float fov)
|
|
|
|
|
{
|
|
|
|
|
var direction = target - selfPosition;
|
|
|
|
|
var angle = Vector3.Angle(direction, selfForward);
|
|
|
|
|
return angle < fov;
|
|
|
|
|
}
|
|
|
|
|
public static bool IsForward(Vector3 selfPosition, Vector3 selfForward,Vector3 targetPos)
|
|
|
|
|
{
|
|
|
|
|
// Get the direction from referencePos to targetPos
|
|
|
|
|
Vector3 directionToTarget = (targetPos - selfPosition).normalized;
|
|
|
|
|
|
|
|
|
|
// Calculate the dot product
|
|
|
|
|
float dotProduct = Vector3.Dot(directionToTarget, selfForward);
|
|
|
|
|
|
|
|
|
|
// If dot product is greater than 0, targetPos is in front of referencePos
|
|
|
|
|
return dotProduct > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 对于 Vector3
|
|
|
|
|
public static Vector3 AlignRotation(Vector3 eulerAngles, float angleIncrement)
|
|
|
|
|
{
|
|
|
|
|
eulerAngles.x = Mathf.Round(eulerAngles.x / angleIncrement) * angleIncrement;
|
|
|
|
|
eulerAngles.y = Mathf.Round(eulerAngles.y / angleIncrement) * angleIncrement;
|
|
|
|
|
eulerAngles.z = Mathf.Round(eulerAngles.z / angleIncrement) * angleIncrement;
|
|
|
|
|
|
|
|
|
|
return eulerAngles;
|
|
|
|
|
}
|
|
|
|
|
public static Vector3 AlignToGrid(Vector3 position, float gridSize)
|
|
|
|
|
{
|
|
|
|
|
var x = Mathf.Round(position.x / gridSize) * gridSize;
|
|
|
|
|
var y = Mathf.Round(position.y / gridSize) * gridSize;
|
|
|
|
|
var z = Mathf.Round(position.z / gridSize) * gridSize;
|
|
|
|
|
|
|
|
|
|
return new Vector3(x, y, z);
|
|
|
|
|
}
|
|
|
|
|
private static float CalculateMaxSpeed(this Vector2 direction)
|
|
|
|
|
{
|
|
|
|
|
var angle = Vector2.Angle(Vector2.right, direction);
|
|
|
|
|
var normalizedAngle = angle / 45f; // 根据圆的角度计算速度倍数
|
|
|
|
|
var maxSpeed = Mathf.Lerp(1f, 0.77f, normalizedAngle);
|
|
|
|
|
return maxSpeed;
|
|
|
|
|
}
|
|
|
|
|
public static bool IsNaN(this Vector3 self)
|
|
|
|
|
{
|
|
|
|
|
return float.IsNaN(self.x) || float.IsNaN(self.y) || float.IsNaN(self.z);
|
|
|
|
|
}
|
|
|
|
|
public static Vector2 Fix(this Vector2 self, int length = 1024)
|
|
|
|
|
{
|
|
|
|
|
if (MathB.IsNormal(self.x, length) is false)
|
|
|
|
|
{
|
|
|
|
|
self.x = 0;
|
|
|
|
|
}
|
|
|
|
|
if (MathB.IsNormal(self.y, length) is false)
|
|
|
|
|
{
|
|
|
|
|
self.y = 0;
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
public static Vector3 Fix(this Vector3 self, int length = 1024)
|
|
|
|
|
{
|
|
|
|
|
if (MathB.IsNormal(self.x, length) is false)
|
|
|
|
|
{
|
|
|
|
|
self.x = 0;
|
|
|
|
|
}
|
|
|
|
|
if (MathB.IsNormal(self.y, length) is false)
|
|
|
|
|
{
|
|
|
|
|
self.y = 0;
|
|
|
|
|
}
|
|
|
|
|
if (MathB.IsNormal(self.z, length) is false)
|
|
|
|
|
{
|
|
|
|
|
self.z = 0;
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
public static bool Parallel(Vector2 vectorX, Vector2 vectorY)
|
|
|
|
|
{
|
|
|
|
|
return (vectorX, vectorY) switch
|
|
|
|
|
{
|
|
|
|
|
var (x, y) when (x.x > 0 && y.x > 0 && x.y > 0 && y.y > 0) => true,
|
|
|
|
|
var (x, y) when (x.x < 0 && y.x < 0 && x.y < 0 && y.y < 0) => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
public static bool Approximately(Vector2 vectorX, Vector2 vectorY, float factor = 8)
|
|
|
|
|
{
|
|
|
|
|
/* var direction = vectorX - vectorY;
|
|
|
|
|
return direction.sqrMagnitude < factor; */
|
|
|
|
|
return Vector3.Angle(vectorX, vectorY) < 8;
|
|
|
|
|
}
|
2025-02-24 23:03:39 +08:00
|
|
|
|
public static Vector2 TransientRotationAxis(Vector2 transientAxis)
|
|
|
|
|
{
|
|
|
|
|
transientAxis.x = TransientRotationAxis(transientAxis.x);
|
|
|
|
|
transientAxis.y = TransientRotationAxis(transientAxis.y);
|
|
|
|
|
return transientAxis;
|
|
|
|
|
}
|
2024-11-03 16:42:23 +08:00
|
|
|
|
public static Vector3 TransientRotationAxis(Vector3 transientAxis)
|
|
|
|
|
{
|
|
|
|
|
transientAxis.x = TransientRotationAxis(transientAxis.x);
|
|
|
|
|
transientAxis.y = TransientRotationAxis(transientAxis.y);
|
|
|
|
|
transientAxis.z = TransientRotationAxis(transientAxis.z);
|
|
|
|
|
return transientAxis;
|
|
|
|
|
}
|
|
|
|
|
public static Vector3Int Align(this Vector3 self, int snapSize = 64)
|
|
|
|
|
{
|
|
|
|
|
Vector3Int result = new()
|
|
|
|
|
{
|
|
|
|
|
x = GetAlign(self.x),
|
|
|
|
|
y = GetAlign(self.y),
|
|
|
|
|
z = GetAlign(self.z),
|
|
|
|
|
};
|
|
|
|
|
return result * snapSize;
|
|
|
|
|
int GetAlign(float _self)
|
|
|
|
|
{
|
|
|
|
|
var size = (int)_self / snapSize;
|
|
|
|
|
var remainder = _self % snapSize;
|
|
|
|
|
return remainder > snapSize / 2 ? size + 1 : size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static float GetLength(this Vector2 self)
|
|
|
|
|
{
|
|
|
|
|
return Mathf.Max(Mathf.Abs(self.x), Mathf.Abs(self.y));
|
|
|
|
|
}
|
|
|
|
|
public static float GetLength(this Vector3 self)
|
|
|
|
|
{
|
|
|
|
|
return Mathf.Max(Mathf.Abs(self.x), Mathf.Abs(self.y), Mathf.Abs(self.z));
|
|
|
|
|
}
|
|
|
|
|
public static float GetValue(this Vector3 self)
|
|
|
|
|
{
|
|
|
|
|
var addValue = self.x + self.y + self.z;
|
|
|
|
|
var subtractValue = self.x - self.y - self.z;
|
|
|
|
|
if (Math.Abs(MathF.Abs(addValue) - Mathf.Abs(subtractValue)) < 0.01f)
|
|
|
|
|
{
|
|
|
|
|
return addValue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("Not a valid vector");
|
|
|
|
|
sb.AppendLine($"{self.x}+{self.y}+{self.z} = {addValue}");
|
|
|
|
|
sb.AppendLine($"{self.x}-{self.y}-{self.z} = {subtractValue}");
|
|
|
|
|
sb.AppendLine($"{addValue}");
|
|
|
|
|
sb.AppendLine($"{subtractValue}");
|
|
|
|
|
throw new Exception(sb.ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static bool InRange(this Vector2Int self, Vector2Int other)
|
|
|
|
|
{
|
|
|
|
|
return self is { x: >= 0, y: >= 0 }
|
|
|
|
|
&& other is { x: >= 0, y: >= 0 }
|
|
|
|
|
&& other.x <= self.x
|
|
|
|
|
&& other.y <= self.y
|
|
|
|
|
&& (self + other).x >= 0
|
|
|
|
|
&& (self + other).y >= 0;
|
|
|
|
|
}
|
|
|
|
|
public static float TransientRotationAxis(this float transientAxis)
|
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
return (transientAxis %= 360f) switch
|
2024-11-03 16:42:23 +08:00
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
> 180f => transientAxis - 360f,
|
|
|
|
|
< -180f => transientAxis + 360f,
|
2024-11-03 16:42:23 +08:00
|
|
|
|
_ => transientAxis,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
public static float WrapAngle(float angle)
|
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
angle %= 360f;
|
|
|
|
|
if (angle > 180f)
|
|
|
|
|
return angle - 360f;
|
2024-11-03 16:42:23 +08:00
|
|
|
|
|
|
|
|
|
return angle;
|
|
|
|
|
}
|
|
|
|
|
public static Vector3 WrapAngle(Vector3 vector)
|
|
|
|
|
{
|
|
|
|
|
return new Vector3
|
|
|
|
|
{
|
|
|
|
|
x = WrapAngle(vector.x),
|
|
|
|
|
y = WrapAngle(vector.y),
|
|
|
|
|
z = WrapAngle(vector.z),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float UnwrapAngle(float angle)
|
|
|
|
|
{
|
|
|
|
|
if (angle >= 0)
|
|
|
|
|
return angle;
|
|
|
|
|
|
|
|
|
|
angle = -angle % 360;
|
|
|
|
|
|
|
|
|
|
return 360 - angle;
|
|
|
|
|
}
|
|
|
|
|
public static Vector2Int ToVector2Int(this Vector2 self)
|
|
|
|
|
{
|
|
|
|
|
return new Vector2Int
|
|
|
|
|
{
|
|
|
|
|
x = (int)self.x,
|
|
|
|
|
y = (int)self.y,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static partial class Utility
|
|
|
|
|
{
|
|
|
|
|
public static bool Includes(this LayerMask mask, int layer)
|
|
|
|
|
{
|
|
|
|
|
return (mask.value & 1 << layer) > 0;
|
|
|
|
|
}
|
|
|
|
|
public static byte[] ToByte(this string self)
|
|
|
|
|
{
|
|
|
|
|
return System.Text.Encoding.UTF8.GetBytes(self);
|
|
|
|
|
}
|
|
|
|
|
public static string Padding(this string self, int length = 16, char c = ' ')
|
|
|
|
|
{
|
|
|
|
|
Encoding coding = Encoding.GetEncoding("gb2312");
|
|
|
|
|
int dcount = 0;
|
|
|
|
|
foreach (char ch in self.ToCharArray())
|
|
|
|
|
{
|
|
|
|
|
if (coding.GetByteCount(ch.ToString()) == 2)
|
|
|
|
|
dcount++;
|
|
|
|
|
}
|
|
|
|
|
string w = self.PadRight(length - dcount, c);
|
|
|
|
|
return w;
|
|
|
|
|
}
|
|
|
|
|
public static bool IsDefault<T>(this T self) where T : IEquatable<T>
|
|
|
|
|
{
|
|
|
|
|
return self.Equals(default);
|
|
|
|
|
}
|
|
|
|
|
public static float Random(this float self) => UnityEngine.Random.Range(-self, self);
|
|
|
|
|
public static int Random(this int self) => UnityEngine.Random.Range(-self, self);
|
|
|
|
|
public static T Random<T>(this IEnumerable<T> e)
|
|
|
|
|
{
|
|
|
|
|
return e.ElementAt(UnityEngine.Random.Range(0, e.Count()));
|
|
|
|
|
}
|
|
|
|
|
public static T Random<T>(this T[] self)
|
|
|
|
|
{
|
|
|
|
|
if (self is null || self.Length is 0) return default;
|
|
|
|
|
return self[UnityEngine.Random.Range(0, self.Length)];
|
|
|
|
|
}
|
|
|
|
|
public static bool IsNullOrEmpty(this string self)
|
|
|
|
|
{
|
|
|
|
|
return String.IsNullOrWhiteSpace(self);
|
|
|
|
|
}
|
|
|
|
|
public static bool IsValid(this string self)
|
|
|
|
|
{
|
|
|
|
|
return !self.IsNullOrEmpty();
|
|
|
|
|
}
|
|
|
|
|
public static UnityEvent AddListener(this UnityEvent unityEvent, UnityAction call)
|
|
|
|
|
{
|
|
|
|
|
unityEvent.AddListener(call);
|
|
|
|
|
return unityEvent;
|
|
|
|
|
}
|
|
|
|
|
public static string Combine(this string[] self)
|
|
|
|
|
{
|
|
|
|
|
if (self.IsNull())
|
|
|
|
|
{
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
StringBuilder stringBuilder = new();
|
|
|
|
|
self.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append(x);
|
|
|
|
|
});
|
|
|
|
|
return stringBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
public static string Combine(this IEnumerable<string> self, bool split = false)
|
|
|
|
|
{
|
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
|
if (self.IsNull())
|
|
|
|
|
{
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
self.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append(x);
|
|
|
|
|
stringBuilder.Append(" ");
|
|
|
|
|
if (split)
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append("\n");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return stringBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
public static float ToFloat(this string self)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(self)) return default;
|
|
|
|
|
return float.Parse(self);
|
|
|
|
|
}
|
|
|
|
|
public static Vector3 MatchTarget(this ref Vector3 self, Vector3 pos, float normalizedTime, float start, float end)
|
|
|
|
|
{
|
|
|
|
|
if (normalizedTime >= start && normalizedTime <= end)
|
|
|
|
|
{
|
|
|
|
|
float process = normalizedTime / (end - start);
|
|
|
|
|
Vector3 direction = pos - self;
|
|
|
|
|
return Vector3.Lerp(default, direction, process);
|
|
|
|
|
}
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
public static Dictionary<string, string> ToDictionary(this string jsonSrting, params string[] removes)
|
|
|
|
|
{
|
|
|
|
|
removes.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
jsonSrting = jsonSrting.Replace(x, string.Empty);
|
|
|
|
|
});
|
|
|
|
|
var jsonObject = JObject.Parse(jsonSrting);
|
|
|
|
|
var jTokens = jsonObject.Descendants().Where(p => !p.Any());
|
|
|
|
|
var tmpKeys = jTokens.Aggregate(new Dictionary<string, string>(),
|
|
|
|
|
(properties, jToken) =>
|
|
|
|
|
{
|
|
|
|
|
properties.Add(jToken.Path, jToken.ToString());
|
|
|
|
|
return properties;
|
|
|
|
|
});
|
|
|
|
|
return tmpKeys;
|
|
|
|
|
}
|
|
|
|
|
public static Dictionary<string, string> JsonToDictionary(this string jsonSrting, params string[] replace)
|
|
|
|
|
{
|
|
|
|
|
replace.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
jsonSrting = jsonSrting.Replace(x, string.Empty);
|
|
|
|
|
});
|
|
|
|
|
Dictionary<string, string> dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonSrting);
|
|
|
|
|
return dic;
|
|
|
|
|
}
|
|
|
|
|
public static string DeleteLines(string text, int lineCount)
|
|
|
|
|
{
|
|
|
|
|
while (text.Split('\n').Length > lineCount)
|
|
|
|
|
text = text.Remove(0, text.Split('\n')[0].Length + 1);
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
public static float GetDifference(this IEnumerable<float> self)
|
|
|
|
|
{
|
|
|
|
|
return self.Max() - self.Min();
|
|
|
|
|
}
|
|
|
|
|
public static void Toggle(this Behaviour self)
|
|
|
|
|
{
|
|
|
|
|
self.enabled = !self.enabled;
|
|
|
|
|
}
|
|
|
|
|
public static void SetEnabled(this UnityEngine.Behaviour self, bool value)
|
|
|
|
|
{
|
|
|
|
|
if (self)
|
|
|
|
|
{
|
|
|
|
|
self.enabled = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static T Or<T>(params T[] ts)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < ts.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var t = ts[i];
|
|
|
|
|
if (t != null && t.Equals(default))
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
public static bool TryGetComponentsInParent<T>(this GameObject self, out T[] components)
|
|
|
|
|
{
|
|
|
|
|
return TryGetComponentsInParent(self.transform, out components);
|
|
|
|
|
}
|
|
|
|
|
public static bool TryGetComponentsInParent<T>(this Component self, out T[] components)
|
|
|
|
|
{
|
|
|
|
|
components = self.GetComponentsInParent<T>();
|
|
|
|
|
return components.IsValid();
|
|
|
|
|
}
|
|
|
|
|
public static bool TryGetComponentAny<T>(this Component self, out T component)
|
|
|
|
|
{
|
|
|
|
|
component = self.GetComponentInChildren<T>(true);
|
|
|
|
|
if (component is not null)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
component = self.GetComponentInParent<T>(true);
|
2025-03-10 18:06:44 +08:00
|
|
|
|
if (component is not null)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
component = self.GetComponent<T>();
|
2024-11-03 16:42:23 +08:00
|
|
|
|
return component is not null;
|
|
|
|
|
}
|
|
|
|
|
public static bool TryGetFirstOrDefault<T>(this IEnumerable<T> self, out T value)
|
|
|
|
|
{
|
|
|
|
|
value = default;
|
|
|
|
|
if (self.IsValid())
|
|
|
|
|
{
|
|
|
|
|
value = self.ElementAt(0);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey key)
|
|
|
|
|
{
|
|
|
|
|
TValue value = default;
|
|
|
|
|
self.TryGetValue(key, out value);
|
|
|
|
|
return value; ;
|
|
|
|
|
}
|
2025-02-24 23:03:39 +08:00
|
|
|
|
public static void DestoryChilds(this GameObject self,bool immediately=false)
|
2024-11-03 16:42:23 +08:00
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
if (!self) return;
|
|
|
|
|
|
|
|
|
|
var list = self.GetComponentsInChildren<Transform>().ToList();
|
|
|
|
|
list.TryRemove(self.transform);
|
|
|
|
|
foreach (var x in list)
|
2024-11-03 16:42:23 +08:00
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
if (immediately)
|
2024-11-03 16:42:23 +08:00
|
|
|
|
{
|
2025-02-24 23:03:39 +08:00
|
|
|
|
Object.DestroyImmediate(x.gameObject);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Object.Destroy(x.gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 16:42:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static bool Contains(this string self, IEnumerable<string> strs, out string key)
|
|
|
|
|
{
|
|
|
|
|
bool result = false;
|
|
|
|
|
string newKey = default;
|
|
|
|
|
strs.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
if (self.Contains(x))
|
|
|
|
|
{
|
|
|
|
|
result = true;
|
|
|
|
|
newKey = x;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
key = newKey;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 带毫秒的字符转换成时间(DateTime)格式
|
|
|
|
|
/// 可处理格式:[2014-10-10 10:10:10,666 或 2014-10-10 10:10:10 666 或 2014-10-10 10:10:10.666]
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static DateTime GetDateTime(this string dateTime)
|
|
|
|
|
{
|
|
|
|
|
string[] strArr = dateTime.Split(new char[] { '-', ' ', ':', ',', '.' });
|
|
|
|
|
DateTime dt = new DateTime(int.Parse(strArr[0]),
|
|
|
|
|
int.Parse(strArr[1]),
|
|
|
|
|
int.Parse(strArr[2]),
|
|
|
|
|
int.Parse(strArr[3]),
|
|
|
|
|
int.Parse(strArr[4]),
|
|
|
|
|
int.Parse(strArr[5]),
|
|
|
|
|
int.Parse(strArr[6]));
|
|
|
|
|
return dt;
|
|
|
|
|
}
|
|
|
|
|
public static bool Equality<T>(this IEnumerable<T> self, IEnumerable<T> other)
|
|
|
|
|
{
|
|
|
|
|
bool result = true;
|
|
|
|
|
if (self.Count() != other.Count())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
if (other.Contains(x) == false)
|
|
|
|
|
{
|
|
|
|
|
result = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
public static string ReservedRows(this string self, int length, bool reverse = false)
|
|
|
|
|
{
|
|
|
|
|
StringBuilder stringBuilder = new(self);
|
|
|
|
|
var lines = self.Split("\n");
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
lines.Reverse().ToList();
|
|
|
|
|
}
|
|
|
|
|
if (lines.Length > length)
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Clear();
|
|
|
|
|
for (int i = lines.Length - length; i < lines.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var words = lines[i];
|
|
|
|
|
if (words is not "\n")
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append(words);
|
|
|
|
|
}
|
|
|
|
|
stringBuilder.Append("\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return stringBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static partial class ReflectionExtensions
|
|
|
|
|
{
|
|
|
|
|
public static void ForEachProties<T>(this object self, UnityAction<T> action = null)
|
|
|
|
|
{
|
|
|
|
|
self.GetType().GetProperties().ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
Debug.Log(x.Name);
|
|
|
|
|
if (x.GetCustomAttribute<ObsoleteAttribute>() == null && x.GetValue(self) is T t)
|
|
|
|
|
{
|
|
|
|
|
if (action is not null) action.Invoke(t);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
public static void ForEachFields<T>(this object self, UnityAction<T> action = null, UnityAction<FieldInfo> infoAction = null, UnityAction<FieldInfo, T> mixAction = null)
|
|
|
|
|
{
|
|
|
|
|
self.GetType().GetFields().ForEach(x =>
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (x.GetCustomAttribute<ObsoleteAttribute>() == null && x.GetValue(self) is T t)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (infoAction is not null) infoAction.Invoke(x);
|
|
|
|
|
if (action is not null) action.Invoke(t);
|
|
|
|
|
if (mixAction is not null) mixAction.Invoke(x, t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static partial class UIToolkitExtensions
|
|
|
|
|
{
|
|
|
|
|
public static VisualElement Create(this VisualElement self, VisualTreeAsset asset)
|
|
|
|
|
{
|
|
|
|
|
var clone = asset.CloneTree();
|
|
|
|
|
self.Add(clone);
|
|
|
|
|
return clone;
|
|
|
|
|
}
|
|
|
|
|
public static bool Entry(this VisualElement self, string name,bool visibleOnEmpty=false)
|
|
|
|
|
{
|
|
|
|
|
var result=false;
|
|
|
|
|
foreach (var x in self.Children())
|
|
|
|
|
{
|
|
|
|
|
if (string.Equals(x.name, name))
|
|
|
|
|
{
|
|
|
|
|
x.SetActive(true);
|
|
|
|
|
result = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
x.SetActive(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.SetActive(visibleOnEmpty || result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
public static T Get<T>(this VisualElement self ,int index = 0) where T : VisualElement => self.Q<T>($"{typeof(T).Name}--{index}");
|
|
|
|
|
public static T Create<T>(this VisualElement self, string name = Constant.EmetyString) where T : VisualElement, new()
|
|
|
|
|
{
|
|
|
|
|
var element = new T();
|
|
|
|
|
if (string.IsNullOrEmpty(name) is false)
|
|
|
|
|
{
|
|
|
|
|
element.name = name;
|
|
|
|
|
}
|
|
|
|
|
self.Add(element);
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
public static T Create<T>(this VisualElement self,Func<VisualElement> createFactory, string name = Constant.EmetyString) where T : VisualElement, new()
|
|
|
|
|
{
|
|
|
|
|
var element = createFactory.Invoke();
|
|
|
|
|
if (string.IsNullOrEmpty(name) is false)
|
|
|
|
|
{
|
|
|
|
|
element.name = name;
|
|
|
|
|
}
|
|
|
|
|
self.Add(element);
|
|
|
|
|
return element as T;
|
|
|
|
|
}
|
|
|
|
|
public static bool GetActive(this VisualElement self) => self.style.display.value == DisplayStyle.Flex;
|
|
|
|
|
public static void SetActive(this VisualElement self, bool active)
|
|
|
|
|
{
|
|
|
|
|
self.style.display = new(active ? DisplayStyle.Flex : DisplayStyle.None);
|
|
|
|
|
}
|
|
|
|
|
public static void SetVisible(this VisualElement self, bool visible)
|
|
|
|
|
{
|
|
|
|
|
self.style.visibility = new(visible ? Visibility.Visible : Visibility.Hidden);
|
|
|
|
|
}
|
|
|
|
|
public static float GetOpacity(this VisualElement self) => self.style.opacity.value;
|
|
|
|
|
public static void SetOpacity(this VisualElement self, float value) => self.style.opacity = new(value);
|
|
|
|
|
public static void ScrollToBottom(this ScrollView self)
|
|
|
|
|
{
|
|
|
|
|
self.verticalScroller.value =
|
|
|
|
|
self.verticalScroller.highValue > 0 ? self.verticalScroller.highValue : 0;
|
|
|
|
|
}
|
|
|
|
|
public static async void ScrollToBottomAutomatic(this ScrollView self, float delay = 0.02f)
|
|
|
|
|
{
|
|
|
|
|
switch (true)
|
|
|
|
|
{
|
|
|
|
|
case true when Math.Abs(self.verticalScroller.value - self.verticalScroller.highValue) < 0.01:
|
|
|
|
|
case true when self.verticalScroller.highValue < 0.01f:
|
|
|
|
|
case true when Math.Abs(self.verticalScroller.value - self.verticalScroller.highValue) < 0.01f:
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(delay), BITApp.CancellationToken);
|
|
|
|
|
ScrollToBottom(self);
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public static bool IsVisible(this VisualElement self,Vector3 worldPosition)
|
|
|
|
|
{
|
|
|
|
|
var cameraTrans = Camera.main.transform;
|
|
|
|
|
return Vector3.Dot(cameraTrans.forward, worldPosition - cameraTrans.position) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Vector2 GetScreenPosition(this VisualElement self, Vector3 worldPosition)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var panel = self.panel;
|
|
|
|
|
if (panel is null)
|
|
|
|
|
{
|
|
|
|
|
panel = self.parent.panel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (panel is null)
|
|
|
|
|
{
|
|
|
|
|
panel = self.parent.parent.panel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pos = RuntimePanelUtils
|
|
|
|
|
.CameraTransformWorldToPanel(panel, worldPosition, Camera.main);
|
|
|
|
|
|
|
|
|
|
pos.x -= self.layout.width / 2;
|
|
|
|
|
pos.y -= self.layout.height / 2;
|
|
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogException(e);
|
|
|
|
|
}
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Vector2 GetPosition(this VisualElement self)
|
|
|
|
|
{
|
|
|
|
|
return new Vector2(self.style.left.value.value, self.style.top.value.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void SetPosition(this VisualElement self, Vector2 screenPosition)
|
|
|
|
|
{
|
|
|
|
|
self.style.right = new StyleLength(StyleKeyword.Auto);
|
|
|
|
|
self.style.bottom = new StyleLength(StyleKeyword.Auto);
|
|
|
|
|
self.style.position = Position.Absolute;
|
|
|
|
|
self.style.left = screenPosition.x;
|
|
|
|
|
self.style.top = screenPosition.y;
|
|
|
|
|
}
|
|
|
|
|
public static void SetPosition(this VisualElement self,Vector3 worldPosition)
|
|
|
|
|
{
|
|
|
|
|
var pos = self.GetScreenPosition(worldPosition);
|
|
|
|
|
var visible = self.IsVisible(worldPosition);
|
|
|
|
|
|
|
|
|
|
self.style.right = new StyleLength(StyleKeyword.Auto);
|
|
|
|
|
self.style.bottom = new StyleLength(StyleKeyword.Auto);
|
|
|
|
|
self.style.position = Position.Absolute;
|
|
|
|
|
// self.transform.position = pos;
|
|
|
|
|
self.style.left = pos.x;
|
|
|
|
|
self.style.top = pos.y;
|
|
|
|
|
|
|
|
|
|
self.SetOpacity(visible ? 1 : 0);
|
|
|
|
|
}
|
2025-02-24 23:03:39 +08:00
|
|
|
|
|
|
|
|
|
public static void ClearTooltipsOnPointerLeave(this VisualElement visualElement)
|
|
|
|
|
{
|
|
|
|
|
visualElement.RegisterCallback<PointerLeaveEvent>(OnLeave);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
void OnLeave(PointerLeaveEvent evt)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(visualElement.tooltip) is false)
|
|
|
|
|
visualElement.tooltip = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void BlinkingCursor(this TextField tf,int interval=1000)
|
|
|
|
|
{
|
|
|
|
|
tf.schedule.Execute(() =>
|
|
|
|
|
{
|
|
|
|
|
if(tf.ClassListContains("transparentCursor"))
|
|
|
|
|
tf.RemoveFromClassList("transparentCursor");
|
|
|
|
|
else
|
|
|
|
|
tf.AddToClassList("transparentCursor");
|
|
|
|
|
}).Every(interval);
|
|
|
|
|
}
|
|
|
|
|
public static Vector2 GetAbsolutePositionInUI(this VisualElement element)
|
|
|
|
|
{
|
|
|
|
|
var position = Vector2.zero;
|
|
|
|
|
var currentElement = element;
|
|
|
|
|
|
|
|
|
|
// 遍历每一个父元素,并累计位置
|
|
|
|
|
while (currentElement != null)
|
|
|
|
|
{
|
|
|
|
|
var style = currentElement.resolvedStyle;
|
|
|
|
|
|
|
|
|
|
// 累加该元素相对于父元素的位置
|
|
|
|
|
position.x += style.left;
|
|
|
|
|
position.y += style.top;
|
|
|
|
|
|
|
|
|
|
// 移动到父元素
|
|
|
|
|
currentElement = currentElement.parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return position;
|
|
|
|
|
}
|
2024-11-03 16:42:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|