大修
This commit is contained in:
8
Assets/Scripts/SLSUtilities/General.meta
Normal file
8
Assets/Scripts/SLSUtilities/General.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cbe1888dddc87246aa5867a7622f319
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
Assets/Scripts/SLSUtilities/General/ColliderExtensions.cs
Normal file
85
Assets/Scripts/SLSUtilities/General/ColliderExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbfbb5726f87a7241b8f73b6201de57d
|
||||
116
Assets/Scripts/SLSUtilities/General/CompoundBool.cs
Normal file
116
Assets/Scripts/SLSUtilities/General/CompoundBool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SLSUtilities/General/CompoundBool.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/CompoundBool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0ce182426546484b89de4813493f8f0
|
||||
208
Assets/Scripts/SLSUtilities/General/DictionaryExtension.cs
Normal file
208
Assets/Scripts/SLSUtilities/General/DictionaryExtension.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2791d3b316c2614394010404ee2ccc4
|
||||
157
Assets/Scripts/SLSUtilities/General/Ease.cs
Normal file
157
Assets/Scripts/SLSUtilities/General/Ease.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SLSUtilities/General/Ease.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/Ease.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99e626ae0cb173a40b150f7428791857
|
||||
178
Assets/Scripts/SLSUtilities/General/GameEvent.cs
Normal file
178
Assets/Scripts/SLSUtilities/General/GameEvent.cs
Normal 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
|
||||
}
|
||||
|
||||
2
Assets/Scripts/SLSUtilities/General/GameEvent.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/GameEvent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4a739f10fb7d8d49a75be3de4f95ac8
|
||||
9
Assets/Scripts/SLSUtilities/General/ICloneable.cs
Normal file
9
Assets/Scripts/SLSUtilities/General/ICloneable.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLSUtilities.General
|
||||
{
|
||||
public interface ICloneable<out T>
|
||||
{
|
||||
T Clone();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SLSUtilities/General/ICloneable.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/ICloneable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b42b93abdc7c084fa29770bf5e5f66b
|
||||
61
Assets/Scripts/SLSUtilities/General/IPrioritized.cs
Normal file
61
Assets/Scripts/SLSUtilities/General/IPrioritized.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SLSUtilities/General/IPrioritized.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/IPrioritized.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b1c40e60420ad47b31aef43bc98901
|
||||
239
Assets/Scripts/SLSUtilities/General/ListExtension.cs
Normal file
239
Assets/Scripts/SLSUtilities/General/ListExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3651f5e91140cb944b2ee602da6e5c48
|
||||
92
Assets/Scripts/SLSUtilities/General/MathExtensions.cs
Normal file
92
Assets/Scripts/SLSUtilities/General/MathExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a153bf87166c48543b97d7e69d5f6c0e
|
||||
36
Assets/Scripts/SLSUtilities/General/Singleton.cs
Normal file
36
Assets/Scripts/SLSUtilities/General/Singleton.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/SLSUtilities/General/Singleton.cs.meta
Normal file
2
Assets/Scripts/SLSUtilities/General/Singleton.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69d5cc5c03690434ebfd1473226fe410
|
||||
118
Assets/Scripts/SLSUtilities/General/SpaceConverter.cs
Normal file
118
Assets/Scripts/SLSUtilities/General/SpaceConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4315a0d9d4589a243b33d79da55b47a9
|
||||
12
Assets/Scripts/SLSUtilities/General/SpriteExtension.cs
Normal file
12
Assets/Scripts/SLSUtilities/General/SpriteExtension.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3531403c3553ec4e9db865958d48ef4
|
||||
18
Assets/Scripts/SLSUtilities/General/StringExtension.cs
Normal file
18
Assets/Scripts/SLSUtilities/General/StringExtension.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd4a697d946242343a065437c29f0cf2
|
||||
24
Assets/Scripts/SLSUtilities/General/TransformExtension.cs
Normal file
24
Assets/Scripts/SLSUtilities/General/TransformExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81bebd0fa57793c4bbe5369f3205e71b
|
||||
8
Assets/Scripts/SLSUtilities/LeanPoolAssistance.meta
Normal file
8
Assets/Scripts/SLSUtilities/LeanPoolAssistance.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cc876a6374ae414a8f65261647ed756
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lean.Pool;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLSUtilities.LeanPoolAssistance
|
||||
{
|
||||
public class PooledObject : SerializedMonoBehaviour, IPoolable
|
||||
{
|
||||
[Tooltip("是否在生成后定时自动回收")]
|
||||
public bool isAutoDespawn = true;
|
||||
|
||||
[ShowIf("isAutoDespawn")][Tooltip("自动回收时间")]
|
||||
public float autoDespawnTime = 1;
|
||||
[ShowIf("isAutoDespawn")][Tooltip("自动回收计时器")][ReadOnly][HideInEditorMode]
|
||||
public float despawnTimer;
|
||||
|
||||
private List<IPoolable> children;
|
||||
|
||||
protected bool spawnExecuted = false;
|
||||
|
||||
[HideInInspector]
|
||||
public Action onSpawnAction;
|
||||
[HideInInspector]
|
||||
public Action onDespawnAction;
|
||||
|
||||
public virtual void OnSpawn()
|
||||
{
|
||||
if (spawnExecuted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
spawnExecuted = true;
|
||||
despawnTimer = 0;
|
||||
onSpawnAction?.Invoke();
|
||||
|
||||
children = GetComponentsInChildren<IPoolable>().ToList();
|
||||
children.Remove(this);
|
||||
children.ForEach(child => child.OnSpawn());
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
UpdateTimer(Time.deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateTimer(float deltaTime)
|
||||
{
|
||||
if (!isAutoDespawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
despawnTimer += deltaTime;
|
||||
|
||||
if (despawnTimer >= autoDespawnTime)
|
||||
{
|
||||
LeanPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnDespawn()
|
||||
{
|
||||
spawnExecuted = false;
|
||||
onDespawnAction?.Invoke();
|
||||
children.ForEach(child => child.OnDespawn());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfc79d04c0439624b848efbb0e52b465
|
||||
Reference in New Issue
Block a user