This commit is contained in:
SoulliesOfficial
2026-03-14 02:30:26 -04:00
parent cf86f0ee51
commit aee62cd637
2041 changed files with 246771 additions and 129128 deletions

View File

@@ -0,0 +1,85 @@
using UnityEngine;
namespace SLSUtilities.General
{
public static class ColliderExtensions
{
/// <summary>
/// 判断一个世界坐标点是否在 BoxCollider 内部(支持旋转和缩放)
/// </summary>
public static bool IsPointInside(this BoxCollider box, Vector3 worldPoint)
{
// 1. 将世界坐标转为该 Collider 的本地坐标(处理了旋转和位置)
Vector3 localPoint = box.transform.InverseTransformPoint(worldPoint);
// 2. 减去中心偏移
localPoint -= box.center;
// 3. 计算实际的半长宽高(考虑 LossyScale 缩放)
// 注意BoxCollider 的 size 已经包含了本地缩放InverseTransformPoint 已经处理了缩放的影响
Vector3 halfSize = box.size * 0.5f;
// 4. AABB 判定
return Mathf.Abs(localPoint.x) <= halfSize.x &&
Mathf.Abs(localPoint.y) <= halfSize.y &&
Mathf.Abs(localPoint.z) <= halfSize.z;
}
/// <summary>
/// 判断一个世界坐标点是否在 SphereCollider 内部
/// </summary>
public static bool IsPointInside(this SphereCollider sphere, Vector3 worldPoint)
{
// 考虑中心偏移的世界坐标中心点
Vector3 center = sphere.transform.TransformPoint(sphere.center);
// 计算缩放后的实际半径Unity 以三个轴中缩放最大的为准)
Vector3 lossyScale = sphere.transform.lossyScale;
float maxScale = Mathf.Max(lossyScale.x, Mathf.Max(lossyScale.y, lossyScale.z));
float scaledRadius = sphere.radius * maxScale;
// 距离平方判定,性能最高
float sqrDistance = (worldPoint - center).sqrMagnitude;
return sqrDistance <= (scaledRadius * scaledRadius);
}
/// <summary>
/// 判断一个世界坐标点是否在 CapsuleCollider 内部
/// </summary>
public static bool IsPointInside(this CapsuleCollider capsule, Vector3 worldPoint)
{
// 转为本地坐标
Vector3 localPoint = capsule.transform.InverseTransformPoint(worldPoint);
localPoint -= capsule.center;
// 计算胶囊体内部中心轴线的半高度(不含两头的半圆)
float radius = capsule.radius;
float halfHeight = Mathf.Max(0f, (capsule.height * 0.5f) - radius);
// 根据方向计算点到中心线段的最短距离
// direction: 0 = X, 1 = Y, 2 = Z
Vector3 closestPointOnAxis = Vector3.zero;
switch (capsule.direction)
{
case 0: closestPointOnAxis.x = Mathf.Clamp(localPoint.x, -halfHeight, halfHeight); break;
case 1: closestPointOnAxis.y = Mathf.Clamp(localPoint.y, -halfHeight, halfHeight); break;
case 2: closestPointOnAxis.z = Mathf.Clamp(localPoint.z, -halfHeight, halfHeight); break;
}
return (localPoint - closestPointOnAxis).sqrMagnitude <= (radius * radius);
}
/// <summary>
/// 通用入口:根据 Collider 类型自动选择检测方法
/// </summary>
public static bool IsPointInside(this Collider collider, Vector3 worldPoint)
{
if (collider is BoxCollider box) return box.IsPointInside(worldPoint);
if (collider is SphereCollider sphere) return sphere.IsPointInside(worldPoint);
if (collider is CapsuleCollider capsule) return capsule.IsPointInside(worldPoint);
// 如果是 MeshCollider 等复杂形状,回退到 Bounds 粗略检测
return collider.bounds.Contains(worldPoint);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bbfbb5726f87a7241b8f73b6201de57d

View File

@@ -0,0 +1,116 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace SLSUtilities.General
{
/// <summary>
/// 一个复合布尔值类,支持多来源修改和优先级系统。
/// 可以将多个来源对布尔值的修改综合起来,由高优先级的修改决定最终结果。
/// </summary>
[InlineProperty]
[HideReferenceObjectPicker]
public class CompoundBool
{
/// <summary>
/// 返回当前生效优先级层级的计数。
/// </summary>
[ShowInInspector]
[LabelText("True Count"), LabelWidth(70)]
[HorizontalGroup("Stats", Width = 100)]
public int TrueCount => GetEffectiveCount();
private SortedList<int, int> counts = new SortedList<int, int>();
private bool isDirty = true;
private bool cachedValue;
/// <summary>
/// 获取最终计算出的布尔值。
/// </summary>
[ShowInInspector]
[LabelText("Value"), LabelWidth(50)]
[HorizontalGroup("Stats", Width = 100)]
public bool Value
{
get
{
if (isDirty) RecalculateValue();
return cachedValue;
}
}
/// <summary>
/// 初始化 CompoundBool。
/// </summary>
/// <param name="initialValue">初始值。如果为 true将在优先级 0 初始化计数为 1。</param>
public CompoundBool(bool initialValue = false)
{
if (initialValue) Modify(true, 0);
}
/// <summary>
/// 在指定的优先级层级修改布尔值。
/// 高优先级的值会覆盖低优先级的值。
/// 如果某个非零优先级的计数变为零,将自动移除该优先级层级。
/// </summary>
/// <param name="marker">修改意图true 增加计数false 减少计数)。</param>
/// <param name="priority">优先级,默认为 0。数字越大优先级越高。</param>
public void Modify(bool marker, int priority = 0)
{
if (counts.TryGetValue(priority, out int currentCount))
{
counts[priority] = marker ? currentCount + 1 : currentCount - 1;
}
else
{
counts.Add(priority, marker ? 1 : -1);
}
if(priority != 0 && counts[priority] == 0)
{
counts.Remove(priority);
}
isDirty = true;
}
/// <summary>
/// 重置所有修改恢复到默认状态false
/// </summary>
public void Reset()
{
counts.Clear();
isDirty = true;
}
private void RecalculateValue()
{
// 从最高优先级(列表末尾)向最低优先级迭代
for (int i = counts.Count - 1; i >= 0; i--)
{
int count = counts.Values[i];
if (count != 0)
{
cachedValue = count > 0;
isDirty = false;
return;
}
}
// 如果所有计数都为零或列表为空,则默认为 false
cachedValue = false;
isDirty = false;
}
private int GetEffectiveCount()
{
// 从最高优先级向最低优先级迭代
for (int i = counts.Count - 1; i >= 0; i--)
{
int count = counts.Values[i];
if (count != 0) return count;
}
return 0;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e0ce182426546484b89de4813493f8f0

View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace SLSUtilities.General
{
public static class DictionaryExtension
{
/// <summary>
/// 删除所有满足 predicate 的键值对。
/// </summary>
public static void RemoveWhere<TKey, TValue>(this IDictionary<TKey, TValue> dict, Func<TKey, TValue, bool> predicate)
{
dict.Where(pair => predicate(pair.Key, pair.Value)).Select(pair => pair.Key).ToList().ForEach(key => dict.Remove(key));
}
/// <summary>
/// 修改字典中指定键的值,如果键不存在则添加该键值对。
/// </summary>
public static void ModifyOrAdd(this IDictionary<string, float> dictionary, string key, float delta, float defaultValue = 0)
{
if (!dictionary.TryAdd(key, defaultValue + delta))
{
dictionary[key] += delta;
}
}
/// <summary>
/// 从字典中获取指定键的原始值。
/// </summary>
public static float GetRawValue(this Dictionary<string, float> dictionary, string key, float defaultValue = 0)
{
return dictionary.GetValueOrDefault(key, defaultValue);
}
/// <summary>
/// 从字典中获取指定键的值,并将其四舍五入为整数返回。
/// </summary>
public static int GetRoundValue(this Dictionary<string, float> dictionary, string key, int defaultValue = 0)
{
return dictionary.TryGetValue(key, out float value) ? Mathf.RoundToInt(value) : defaultValue;
}
/// <summary>
/// 从字典中获取指定键的值,并将其向下取整为整数返回。
/// </summary>
public static int GetFloorValue(this Dictionary<string, float> dictionary, string key, int defaultValue = 0)
{
return dictionary.TryGetValue(key, out float value) ? Mathf.FloorToInt(value) : defaultValue;
}
/// <summary>
/// 将源字典中的所有键值对粘贴到目标字典中,避免重复键。
/// </summary>
public static void Paste<T1, T2>(this IDictionary<T1, T2> source, IDictionary<T1, T2> target)
{
foreach (var pair in source)
{
if (!target.ContainsKey(pair.Key))
{
target[pair.Key] = pair.Value;
}
else
{
Debug.LogWarning($"Attribute \"{pair.Key}\" already exists. Skipping duplicate.");
}
}
}
}
public static class DictionaryExtensionForEvents
{
#region
public static void Invoke(this IDictionary<string, PrioritizedAction> dictionary)
{
foreach (string key in dictionary.Keys.ToList())
{
var pAction = dictionary[key];
pAction.Invoke();
if (pAction.ShouldRemove)
{
dictionary.Remove(key);
}
}
}
public static void Invoke<T>(this IDictionary<string, PrioritizedAction<T>> dictionary, T arg)
{
foreach (string key in dictionary.Keys.ToList())
{
var pAction = dictionary[key];
pAction.Invoke(arg);
if (pAction.ShouldRemove)
{
dictionary.Remove(key);
}
}
}
public static void Invoke<T0, T1>(this IDictionary<string, PrioritizedAction<T0, T1>> dictionary, T0 arg0, T1 arg1)
{
foreach (string key in dictionary.Keys.ToList())
{
var pAction = dictionary[key];
pAction.Invoke(arg0, arg1);
if (pAction.ShouldRemove)
{
dictionary.Remove(key);
}
}
}
public static void Invoke<T0, T1, T2>(this IDictionary<string, PrioritizedAction<T0, T1, T2>> dictionary, T0 arg0, T1 arg1, T2 arg2)
{
foreach (string key in dictionary.Keys.ToList())
{
var pAction = dictionary[key];
pAction.Invoke(arg0, arg1, arg2);
if (pAction.ShouldRemove)
{
dictionary.Remove(key);
}
}
}
#endregion
#region
public static Dictionary<string, TR> Invoke<TR>(this IDictionary<string, PrioritizedFunc<TR>> dictionary)
{
Dictionary<string, TR> results = new Dictionary<string, TR>();
foreach (string key in dictionary.Keys.ToList())
{
var pFunc = dictionary[key];
results[key] = pFunc.Invoke();
if (pFunc.ShouldRemove)
{
dictionary.Remove(key);
}
}
return results;
}
public static Dictionary<string, TR> Invoke<T, TR>(this IDictionary<string, PrioritizedFunc<T, TR>> dictionary, T arg)
{
Dictionary<string, TR> results = new Dictionary<string, TR>();
foreach (string key in dictionary.Keys.ToList())
{
var pFunc = dictionary[key];
results[key] = pFunc.Invoke(arg);
if (pFunc.ShouldRemove)
{
dictionary.Remove(key);
}
}
return results;
}
public static Dictionary<string, TR> Invoke<T0, T1, TR>(this IDictionary<string, PrioritizedFunc<T0, T1, TR>> dictionary, T0 arg0, T1 arg1)
{
Dictionary<string, TR> results = new Dictionary<string, TR>();
foreach (string key in dictionary.Keys.ToList())
{
var pFunc = dictionary[key];
results[key] = pFunc.Invoke(arg0, arg1);
if (pFunc.ShouldRemove)
{
dictionary.Remove(key);
}
}
return results;
}
public static Dictionary<string, TR> Invoke<T0, T1, T2, TR>(this IDictionary<string, PrioritizedFunc<T0, T1, T2, TR>> dictionary, T0 arg0, T1 arg1, T2 arg2)
{
Dictionary<string, TR> results = new Dictionary<string, TR>();
foreach (string key in dictionary.Keys.ToList())
{
var pFunc = dictionary[key];
results[key] = pFunc.Invoke(arg0, arg1, arg2);
if (pFunc.ShouldRemove)
{
dictionary.Remove(key);
}
}
return results;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c2791d3b316c2614394010404ee2ccc4

View File

@@ -0,0 +1,157 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace SLSUtilities.General
{
public enum EaseType
{
Linear,
InSine, OutSine, InOutSine,
InQuad, OutQuad, InOutQuad,
InCubic, OutCubic, InOutCubic,
InQuart, OutQuart, InOutQuart,
InQuint, OutQuint, InOutQuint,
InExpo, OutExpo, InOutExpo,
InCirc, OutCirc, InOutCirc,
InElastic, OutElastic, InOutElastic,
InBack, OutBack, InOutBack,
InBounce, OutBounce, InOutBounce,
Flash, InFlash, OutFlash, InOutFlash
}
public static class Ease
{
// 缓存生成的曲线,避免重复计算
private static readonly Dictionary<EaseType, AnimationCurve> CurveCache = new Dictionary<EaseType, AnimationCurve>();
/// <summary>
/// 获取指定的缓动曲线
/// </summary>
public static AnimationCurve GetCurve(EaseType easeType)
{
if (CurveCache.TryGetValue(easeType, out AnimationCurve cachedCurve))
{
return cachedCurve;
}
AnimationCurve newCurve = CreateCurve(easeType);
CurveCache[easeType] = newCurve;
return newCurve;
}
private static AnimationCurve CreateCurve(EaseType easeType)
{
Keyframe[] keys;
// 采样精细度:对于复杂的曲线(如 Elastic, Bounce采样点需要多一些
int samples = IsComplex(easeType) ? 50 : 20;
keys = new Keyframe[samples + 1];
for (int i = 0; i <= samples; i++)
{
float t = (float)i / samples;
float v = Evaluate(easeType, t);
keys[i] = new Keyframe(t, v);
}
AnimationCurve curve = new AnimationCurve(keys);
// 平滑曲线切线(除线性外)
if (easeType != EaseType.Linear)
{
for (int i = 0; i < keys.Length; i++)
{
curve.SmoothTangents(i, 0f);
}
}
return curve;
}
private static bool IsComplex(EaseType type)
{
return type.ToString().Contains("Elastic") ||
type.ToString().Contains("Bounce") ||
type.ToString().Contains("Flash");
}
// 数学计算核心:标准的 Robert Penner 缓动公式
private static float Evaluate(EaseType type, float t)
{
switch (type)
{
case EaseType.Linear: return t;
case EaseType.InSine: return 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
case EaseType.OutSine: return Mathf.Sin(t * Mathf.PI * 0.5f);
case EaseType.InOutSine: return -(Mathf.Cos(Mathf.PI * t) - 1f) * 0.5f;
case EaseType.InQuad: return t * t;
case EaseType.OutQuad: return 1f - (1f - t) * (1f - t);
case EaseType.InOutQuad: return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) * 0.5f;
case EaseType.InCubic: return t * t * t;
case EaseType.OutCubic: return 1f - Mathf.Pow(1f - t, 3f);
case EaseType.InOutCubic: return t < 0.5f ? 4f * t * t * t : 1f - Mathf.Pow(-2f * t + 2f, 3f) * 0.5f;
case EaseType.InQuart: return t * t * t * t;
case EaseType.OutQuart: return 1f - Mathf.Pow(1f - t, 4f);
case EaseType.InOutQuart: return t < 0.5f ? 8f * t * t * t * t : 1f - Mathf.Pow(-2f * t + 2f, 4f) * 0.5f;
case EaseType.InQuint: return t * t * t * t * t;
case EaseType.OutQuint: return 1f - Mathf.Pow(1f - t, 5f);
case EaseType.InOutQuint: return t < 0.5f ? 16f * t * t * t * t * t : 1f - Mathf.Pow(-2f * t + 2f, 5f) * 0.5f;
case EaseType.InExpo: return t == 0f ? 0f : Mathf.Pow(2f, 10f * t - 10f);
case EaseType.OutExpo: return t == 1f ? 1f : 1f - Mathf.Pow(2f, -10f * t);
case EaseType.InOutExpo:
return t == 0f ? 0f : t == 1f ? 1f : t < 0.5f ? Mathf.Pow(2f, 20f * t - 10f) * 0.5f : (2f - Mathf.Pow(2f, -20f * t + 10f)) * 0.5f;
case EaseType.InCirc: return 1f - Mathf.Sqrt(1f - Mathf.Pow(t, 2f));
case EaseType.OutCirc: return Mathf.Sqrt(1f - Mathf.Pow(t - 1f, 2f));
case EaseType.InOutCirc:
return t < 0.5f
? (1f - Mathf.Sqrt(1f - Mathf.Pow(2f * t, 2f))) * 0.5f
: (Mathf.Sqrt(1f - Mathf.Pow(-2f * t + 2f, 2f)) + 1f) * 0.5f;
case EaseType.InBack:
{
const float s = 1.70158f;
return (s + 1f) * t * t * t - s * t * t;
}
case EaseType.OutBack:
{
const float s = 1.70158f;
return 1f + (s + 1f) * Mathf.Pow(t - 1f, 3f) + s * Mathf.Pow(t - 1f, 2f);
}
case EaseType.InOutBack:
{
const float s = 1.70158f * 1.525f;
return t < 0.5f
? (Mathf.Pow(2f * t, 2f) * ((s + 1f) * 2f * t - s)) * 0.5f
: (Mathf.Pow(2f * t - 2f, 2f) * ((s + 1f) * (t * 2f - 2f) + s) + 2f) * 0.5f;
}
case EaseType.InElastic:
return t == 0f ? 0f : t == 1f ? 1f : -Mathf.Pow(2f, 10f * t - 10f) * Mathf.Sin((t * 10f - 10.75f) * ((2f * Mathf.PI) / 3f));
case EaseType.OutElastic:
return t == 0f ? 0f : t == 1f ? 1f : Mathf.Pow(2f, -10f * t) * Mathf.Sin((t * 10f - 0.75f) * ((2f * Mathf.PI) / 3f)) + 1f;
case EaseType.InOutElastic:
const float c5 = (2f * Mathf.PI) / 4.5f;
return t == 0f ? 0f :
t == 1f ? 1f :
t < 0.5f ? -(Mathf.Pow(2f, 20f * t - 10f) * Mathf.Sin((20f * t - 11.125f) * c5)) * 0.5f :
(Mathf.Pow(2f, -20f * t + 10f) * Mathf.Sin((20f * t - 11.125f) * c5)) * 0.5f + 1f;
case EaseType.InBounce: return 1f - Evaluate(EaseType.OutBounce, 1f - t);
case EaseType.OutBounce:
if (t < 1f / 2.75f) return 7.5625f * t * t;
else if (t < 2f / 2.75f) return 7.5625f * (t -= 1.5f / 2.75f) * t + 0.75f;
else if (t < 2.5f / 2.75f) return 7.5625f * (t -= 2.25f / 2.75f) * t + 0.9375f;
else return 7.5625f * (t -= 2.625f / 2.75f) * t + 0.984375f;
case EaseType.InOutBounce:
return t < 0.5f
? (1f - Evaluate(EaseType.OutBounce, 1f - 2f * t)) * 0.5f
: (1f + Evaluate(EaseType.OutBounce, 2f * t - 1f)) * 0.5f;
// Flash 效果通常指类似锯齿波的闪烁,这里模仿 DOTween 的 Flash 逻辑(快速往复)
case EaseType.Flash: return Mathf.Abs(Mathf.Cos(t * Mathf.PI * 4f)); // 简单的往复采样
case EaseType.InFlash: return t * Mathf.Abs(Mathf.Cos(t * Mathf.PI * 4f));
case EaseType.OutFlash: return (1f - t) * Mathf.Abs(Mathf.Cos(t * Mathf.PI * 4f));
case EaseType.InOutFlash: return Mathf.SmoothStep(0, 1, t) * Mathf.Abs(Mathf.Cos(t * Mathf.PI * 4f));
default: return t;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 99e626ae0cb173a40b150f7428791857

View File

@@ -0,0 +1,178 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace SLSUtilities.General
{
public class PrioritizedFunctionBase : IPrioritized
{
public int Priority { get; set; }
/// <summary>
/// 执行计数。
/// -1 代表无限次执行。
/// >0 代表剩余执行次数,每次 Invoke 后递减,减至 0 时应被移除。
/// </summary>
public int ExecutionCount { get; set; } = -1;
public bool ShouldRemove => ExecutionCount == 0;
protected void UpdateCount()
{
if (ExecutionCount > 0) ExecutionCount--;
}
}
#region PrioritizedAction
public class PrioritizedAction : PrioritizedFunctionBase
{
private readonly Action action;
public PrioritizedAction(Action action, int priority = 0, int count = -1)
{
this.action = action;
Priority = priority;
ExecutionCount = count;
}
public void Invoke()
{
action.Invoke();
UpdateCount();
}
}
public class PrioritizedAction<T> : PrioritizedFunctionBase
{
private readonly Action<T> action;
public PrioritizedAction(Action<T> action, int priority = 0, int count = -1)
{
this.action = action;
Priority = priority;
ExecutionCount = count;
}
public void Invoke(T arg)
{
action.Invoke(arg);
UpdateCount();
}
}
public class PrioritizedAction<T0, T1> : PrioritizedFunctionBase
{
private readonly Action<T0, T1> action;
public PrioritizedAction(Action<T0, T1> action, int priority = 0, int count = -1)
{
this.action = action;
Priority = priority;
ExecutionCount = count;
}
public void Invoke(T0 arg0, T1 arg1)
{
action.Invoke(arg0, arg1);
UpdateCount();
}
}
public class PrioritizedAction<T0, T1, T2> : PrioritizedFunctionBase
{
private readonly Action<T0, T1, T2> action;
public PrioritizedAction(Action<T0, T1, T2> action, int priority = 0, int count = -1)
{
this.action = action;
Priority = priority;
ExecutionCount = count;
}
public void Invoke(T0 arg0, T1 arg1, T2 arg2)
{
action.Invoke(arg0, arg1, arg2);
UpdateCount();
}
}
#endregion
#region PrioritizedFunc
public class PrioritizedFunc<TR> : PrioritizedFunctionBase
{
private readonly Func<TR> func;
public PrioritizedFunc(Func<TR> func, int priority = 0, int count = -1)
{
this.func = func;
this.Priority = priority;
this.ExecutionCount = count;
}
public TR Invoke()
{
TR result = func.Invoke();
UpdateCount();
return result;
}
}
public class PrioritizedFunc<T, TR> : PrioritizedFunctionBase
{
private readonly Func<T, TR> func;
public PrioritizedFunc(Func<T, TR> func, int priority = 0, int count = -1)
{
this.func = func;
this.Priority = priority;
this.ExecutionCount = count;
}
public TR Invoke(T arg)
{
TR result = func.Invoke(arg);
UpdateCount();
return result;
}
}
public class PrioritizedFunc<T0, T1, TR> : PrioritizedFunctionBase
{
private readonly Func<T0, T1, TR> func;
public PrioritizedFunc(Func<T0, T1, TR> func, int priority = 0, int count = -1)
{
this.func = func;
this.Priority = priority;
this.ExecutionCount = count;
}
public TR Invoke(T0 arg0, T1 arg1)
{
TR result = func.Invoke(arg0, arg1);
UpdateCount();
return result;
}
}
public class PrioritizedFunc<T0, T1, T2, TR> : PrioritizedFunctionBase
{
private readonly Func<T0, T1, T2, TR> func;
public PrioritizedFunc(Func<T0, T1, T2, TR> func, int priority = 0, int count = -1)
{
this.func = func;
this.Priority = priority;
this.ExecutionCount = count;
}
public TR Invoke(T0 arg0, T1 arg1, T2 arg2)
{
TR result = func.Invoke(arg0, arg1, arg2);
UpdateCount();
return result;
}
}
#endregion
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d4a739f10fb7d8d49a75be3de4f95ac8

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace SLSUtilities.General
{
public interface ICloneable<out T>
{
T Clone();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7b42b93abdc7c084fa29770bf5e5f66b

View File

@@ -0,0 +1,61 @@
using System;
namespace SLSUtilities.General
{
/// <summary>
/// 实现该接口的类可以根据优先级进行比较和排序。
/// 数字越大优先级越高。
/// </summary>
public interface IPrioritized : IComparable<IPrioritized>
{
int Priority { get; }
int IComparable<IPrioritized>.CompareTo(IPrioritized other)
{
return other.Priority.CompareTo(Priority);
}
}
public static class IPrioritizedExtensions
{
public static PrioritizedAction ToPrioritized<T>(this Action action, int priority = 0)
{
return new PrioritizedAction(action, priority);
}
public static PrioritizedAction<T> ToPrioritized<T>(this Action<T> action, int priority = 0)
{
return new PrioritizedAction<T>(action, priority);
}
public static PrioritizedAction<T1, T2> ToPrioritized<T1, T2>(this Action<T1, T2> action, int priority = 0)
{
return new PrioritizedAction<T1, T2>(action, priority);
}
public static PrioritizedAction<T1, T2, T3> ToPrioritized<T1, T2, T3>(this Action<T1, T2, T3> action, int priority = 0)
{
return new PrioritizedAction<T1, T2, T3>(action, priority);
}
public static PrioritizedFunc<TR> ToPrioritized<TR>(this Func<TR> func, int priority = 0)
{
return new PrioritizedFunc<TR>(func, priority);
}
public static PrioritizedFunc<T, TR> ToPrioritized<T, TR>(this Func<T, TR> func, int priority = 0)
{
return new PrioritizedFunc<T, TR>(func, priority);
}
public static PrioritizedFunc<T1, T2, TR> ToPrioritized<T1, T2, TR>(this Func<T1, T2, TR> func, int priority = 0)
{
return new PrioritizedFunc<T1, T2, TR>(func, priority);
}
public static PrioritizedFunc<T1, T2, T3, TR> ToPrioritized<T1, T2, T3, TR>(this Func<T1, T2, T3, TR> func, int priority = 0)
{
return new PrioritizedFunc<T1, T2, T3, TR>(func, priority);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 84b1c40e60420ad47b31aef43bc98901

View File

@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
namespace SLSUtilities.General
{
public static partial class ListExtension
{
/// <summary>
/// 对列表中的每个元素执行指定的操作
/// </summary>
public static void For<T>(this List<T> list, Action<T> action)
{
for (var index = 0; index < list.Count; index++)
{
action(list[index]);
}
}
/// <summary>
/// 将列表中的元素从此列表的原始位置转移到指定位置
/// </summary>
public static void Transfer<T>(this List<T> list, T item, int index)
{
if (list.Contains(item))
{
list.Remove(item);
list.Insert(index, item);
}
else
{
Debug.LogError($"Item {item} not found in this List.");
}
}
/// <summary>
/// 将列表中的元素从此列表转移到另一个列表,可选择插入位置,默认添加到末尾
/// </summary>
public static void Transfer<T>(this List<T> fromList, List<T> toList, T item, int index = -1)
{
if (fromList.Contains(item))
{
fromList.Remove(item);
if (index < 0)
{
toList.Add(item);
}
else
{
toList.Insert(index, item);
}
}
else
{
Debug.LogError($"Item {item} not found in this List.");
}
}
/// <summary>
/// 从列表中移除另一个列表中的所有元素
/// </summary>
public static void Remove<T>(this List<T> list, List<T> listToRemove)
{
foreach (T item in listToRemove)
{
if (list.Contains(item))
{
list.Remove(item);
}
}
}
/// <summary>
/// 在列表中交换两个元素
/// </summary>
public static void Swap<T>(this List<T> list, int i, int j)
{
(list[i], list[j]) = (list[j], list[i]);
}
/// <summary>
/// 随机打乱列表中的元素
/// </summary>
public static void Shuffle<T>(this List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
list.Swap(i, Random.Range(i, list.Count));
}
}
public static bool TryGet<T>(this List<T> list, Func<T, bool> predicate, out T element)
{
foreach (T item in list)
{
if (predicate(item))
{
element = item;
return true;
}
}
element = default;
return false;
}
/// <summary>
/// 尝试从列表中随机获取一个元素
/// </summary>
public static bool TryGetRandom<T>(this List<T> list, out T element)
{
if (list.Count == 0)
{
element = default;
return false;
}
element = list[Random.Range(0, list.Count)];
return true;
}
/// <summary>
/// 返回一个新的列表,包含原列表中排除指定元素后的所有元素
/// </summary>
public static List<T> Exclude<T>(this List<T> list, T element)
{
List<T> newList = new List<T>(list);
newList.Remove(element);
return newList;
}
/// <summary>
/// 返回一个新的列表,包含原列表中排除指定元素后的所有元素
/// </summary>
public static List<T> Exclude<T>(this List<T> list, params T[] elements)
{
List<T> newList = new List<T>(list);
foreach (T element in elements)
{
if (newList.Contains(element))
{
newList.Remove(element);
}
}
return newList;
}
/// <summary>
/// 返回一个新的列表,包含原列表中排除指定元素后的所有元素
/// </summary>
public static List<T> Exclude<T>(this List<T> list, List<T> elements)
{
return list.Exclude(elements.ToArray());
}
/// <summary>
/// 根据指定的过滤器函数,返回一个新的列表,包含符合条件的元素
/// </summary>
public static List<T> Filtered<T>(this List<T> list, Func<T, bool> filter, bool include = true)
{
return list.Where(item => filter(item) == include).ToList();
}
/// <summary>
/// 检查所有过滤器函数是否都全都返回true
/// </summary>
public static bool All<T>(this List<Func<T, bool>> filterList, T item)
{
return filterList.All(filter => filter(item));
}
/// <summary>
/// 检查是否有任一过滤器函数返回true
/// </summary>
public static bool Any<T>(this List<Func<T, bool>> filterList, T item)
{
return filterList.Any(filter => filter(item));
}
/// <summary>
/// 检查从lastList到thisList是否有元素根据condition条件切换出列表
/// </summary>
public static bool SwitchOut<T>(this List<T> lastList, List<T> thisList, Func<T, bool> condition)
{
return lastList.Any(condition) && thisList.All(element => !condition(element));
}
/// <summary>
/// 检查从lastList到thisList是否有指定元素切换出列表
/// </summary>
public static bool SwitchOut<T>(this List<T> lastList, List<T> thisList, T element)
{
return lastList.Contains(element) && !thisList.Contains(element);
}
}
public static partial class ListExtension
{
/// <summary>
/// 根据优先级将新项插入到已排序的列表中,保持列表按优先级从高到低排序(二分查找)。
/// 数字越大优先级越高。
/// </summary>
public static void AddByPriority<T>(this List<T> list, T newItem) where T : IPrioritized
{
// 优化:检查是否可以直接添加到末尾
if (list.Count == 0 || newItem.Priority <= list[list.Count - 1].Priority)
{
list.Add(newItem);
return;
}
int low = 0;
int high = list.Count;
while (low < high)
{
int mid = low + (high - low) / 2;
// 规则:数字越大,优先级越高,越靠前
// 如果中间项的优先级 >= 新项的优先级,
// 说明新项应该在它后面
if (list[mid].Priority >= newItem.Priority)
{
low = mid + 1;
}
else
{
// 中间项的优先级 < 新项,说明新项应该在它前面(或就是这个位置)
high = mid;
}
}
list.Insert(low, newItem);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3651f5e91140cb944b2ee602da6e5c48

View File

@@ -0,0 +1,92 @@
using System;
using UnityEngine;
namespace SLSUtilities.General
{
public static class MathExtensions
{
public static float ClampAngle(float lfAngle, float lfMin, float lfMax)
{
if (lfAngle < -360f) lfAngle += 360f;
if (lfAngle > 360f) lfAngle -= 360f;
return Mathf.Clamp(lfAngle, lfMin, lfMax);
}
public static Vector3 Flatten(this Vector3 vector)
{
return new Vector3(vector.x, 0, vector.z);
}
}
public class LerpFloat
{
public float currentValue;
public float targetValue;
public float lerpSpeed;
public LerpFloat(float initialValue, float lerpSpeed)
{
this.currentValue = initialValue;
this.targetValue = initialValue;
this.lerpSpeed = lerpSpeed;
}
public void Update(float deltaTime)
{
currentValue = Mathf.Lerp(currentValue, targetValue, lerpSpeed * deltaTime);
}
public void Update(float customSpeed, float deltaTime)
{
currentValue = Mathf.Lerp(currentValue, targetValue, customSpeed * deltaTime);
}
}
public class LerpVector3
{
public Vector3 currentValue;
public Vector3 targetValue;
public float lerpSpeed;
public LerpVector3(Vector3 initialValue, float lerpSpeed)
{
this.currentValue = initialValue;
this.targetValue = initialValue;
this.lerpSpeed = lerpSpeed;
}
public void Update(float deltaTime)
{
currentValue = Vector3.Lerp(currentValue, targetValue, lerpSpeed * deltaTime);
}
public void Update(float customSpeed, float deltaTime)
{
currentValue = Vector3.Lerp(currentValue, targetValue, customSpeed * deltaTime);
}
}
public class LerpColor
{
public Color currentValue;
public Color targetValue;
public float lerpSpeed;
public LerpColor(Color initialValue, float lerpSpeed)
{
this.currentValue = initialValue;
this.targetValue = initialValue;
this.lerpSpeed = lerpSpeed;
}
public void Update(float deltaTime)
{
currentValue = Color.Lerp(currentValue, targetValue, lerpSpeed * deltaTime);
}
public void Update(float customSpeed, float deltaTime)
{
currentValue = Color.Lerp(currentValue, targetValue, customSpeed * deltaTime);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a153bf87166c48543b97d7e69d5f6c0e

View File

@@ -0,0 +1,36 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace SLSUtilities.General
{
public class Singleton<T> : SerializedMonoBehaviour where T : MonoBehaviour
{
protected static T instance;
public static T Instance => instance == null ? FindFirstObjectByType<T>() : instance;
protected virtual void Awake()
{
Initialize(false);
}
protected virtual void Initialize(bool dontDestroy)
{
if (dontDestroy)
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
else
{
instance = this as T;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69d5cc5c03690434ebfd1473226fe410

View File

@@ -0,0 +1,118 @@
using UnityEngine;
namespace SLSUtilities.General
{
public static class SpaceConverter
{
/// <summary>
/// 世界坐标转换为屏幕坐标
/// </summary>
/// <param name="worldPoint">屏幕坐标</param>
/// <param name="worldCamera">用于转换的摄像机</param>
/// <returns></returns>
public static Vector3 WorldPointToScreenPoint(Vector3 worldPoint, Camera worldCamera)
{
// Camera.main 世界摄像机
Vector3 screenPoint = worldCamera.WorldToScreenPoint(worldPoint);
return screenPoint;
}
/// <summary>
/// 世界坐标转换为归一化屏幕坐标 (0-1)
/// </summary>
/// <param name="worldPoint"></param>
/// <param name="worldCamera"></param>
/// <returns></returns>
public static Vector2 WorldPointToNormalizedScreenPoint(Vector3 worldPoint, Camera worldCamera)
{
Vector2 screenPoint = WorldPointToScreenPoint(worldPoint, worldCamera);
Vector2 normalizedScreenPoint = new Vector2(screenPoint.x / Screen.width, screenPoint.y / Screen.height);
return normalizedScreenPoint;
}
/// <summary>
/// 屏幕坐标转换为世界坐标
/// </summary>
/// <param name="screenPoint">屏幕坐标</param>
/// <param name="planeZ">距离摄像机 Z 平面的距离</param>
/// <param name="camera">用于转换的摄像机</param>
/// <returns></returns>
public static Vector3 ScreenPointToWorldPoint(Vector2 screenPoint, float planeZ, Camera worldCamera)
{
// Camera.main 世界摄像机
Vector3 position = new Vector3(screenPoint.x, screenPoint.y, planeZ);
Vector3 worldPoint = worldCamera.ScreenToWorldPoint(position);
return worldPoint;
}
public static Vector2 WorldPointToUILocalPoint(RectTransform rt, Vector3 worldPosition, Camera worldCamera, Camera uiCamera)
{
// 将世界坐标转换为屏幕坐标
var screenPosition = WorldPointToScreenPoint(worldPosition, worldCamera);
// 将屏幕坐标转换为 UGUI 坐标
Vector2 localPoint = ScreenPointToUILocalPoint(rt, screenPosition, uiCamera);
return localPoint;
}
public static Vector3 WorldPointToUIPoint(RectTransform rt, Vector3 worldPosition, Camera worldCamera, Camera uiCamera)
{
// 将世界坐标转换为屏幕坐标
var screenPosition = WorldPointToScreenPoint(worldPosition, worldCamera);
// 将屏幕坐标转换为 UGUI 坐标
Vector3 uiPoint = ScreenPointToUIPoint(rt, screenPosition, uiCamera);
return uiPoint;
}
// RectTransformUtility.WorldToScreenPoint
// RectTransformUtility.ScreenPointToWorldPointInRectangle
// RectTransformUtility.ScreenPointToLocalPointInRectangle
// 上面三个坐标转换的方法使用 Camera 的地方
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceCamera、RenderMode.WorldSpace 时 传递参数 canvas.worldCamera
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceOverlay 时 传递参数 null
// UI 坐标转换为屏幕坐标
public static Vector2 UIPointToScreenPoint(Vector3 worldPoint, Camera uiCamera)
{
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPoint);
return screenPoint;
}
// 屏幕坐标转换为 UGUI 坐标
public static Vector3 ScreenPointToUIPoint(RectTransform rt, Vector2 screenPoint, Camera uiCamera)
{
//UI屏幕坐标转换为世界坐标
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceCamera、RenderMode.WorldSpace 时 uiCamera 不能为空
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceOverlay 时 uiCamera 可以为空
RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, screenPoint, uiCamera, out Vector3 globalMousePos);
// 转换后的 globalMousePos 使用下面方法赋值
// target 为需要使用的 UI RectTransform
// rt 可以是 target.GetComponent<RectTransform>(), 也可以是 target.parent.GetComponent<RectTransform>()
// target.transform.position = globalMousePos;
return globalMousePos;
}
// 屏幕坐标转换为 UGUI RectTransform 的 anchoredPosition
public static Vector2 ScreenPointToUILocalPoint(RectTransform parentRT, Vector2 screenPoint, Camera uiCamera)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRT, screenPoint, uiCamera, out Vector2 localPos);
// 转换后的 localPos 使用下面方法赋值
// target 为需要使用的 UI RectTransform
// parentRT 是 target.parent.GetComponent<RectTransform>()
// 最后赋值 target.anchoredPosition = localPos;
return localPos;
}
/// <summary>
/// 获取目标 RectTransform 在目标空间 RectTransform 中的本地位置
/// </summary>
public static Vector2 GetLocalUIPosition(RectTransform targetRect, RectTransform targetSpace)
{
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, targetRect.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(targetSpace, screenPos, null, out Vector2 localPos);
return localPos;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4315a0d9d4589a243b33d79da55b47a9

View File

@@ -0,0 +1,12 @@
using UnityEngine;
namespace SLSUtilities.General
{
public static class SpriteExtension
{
public static Sprite Create(Texture2D texture)
{
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a3531403c3553ec4e9db865958d48ef4

View File

@@ -0,0 +1,18 @@
using I2.Loc;
using UnityEngine;
namespace SLSUtilities.General
{
public static class StringExtension
{
public static string Localize(this string original)
{
if (LocalizationManager.TryGetTranslation(original, out string translated))
{
return translated;
}
return original;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fd4a697d946242343a065437c29f0cf2

View File

@@ -0,0 +1,24 @@
using Lean.Pool;
using UnityEngine;
namespace SLSUtilities.General
{
public static class TransformExtension
{
public static void DestroyAllChildren(this Transform transform)
{
for (int i = transform.childCount - 1; i >= 0; i--)
{
Object.Destroy(transform.GetChild(i).gameObject);
}
}
public static void DespawnAllChildren(this Transform transform)
{
for (int i = transform.childCount - 1; i >= 0; i--)
{
LeanPool.Despawn(transform.GetChild(i).gameObject);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 81bebd0fa57793c4bbe5369f3205e71b