1
This commit is contained in:
@@ -21,6 +21,10 @@ public class MonsterMoveLine
|
||||
// ----------------------------------------------------------------
|
||||
private static readonly Dictionary<string, Vector2[]> _curvePointsArrays
|
||||
= new Dictionary<string, Vector2[]>();
|
||||
|
||||
/// <summary>⭐ B样条节点向量缓存(曲线名 → 节点数组,与点数组同步构建/清理)</summary>
|
||||
private static readonly Dictionary<string, float[]> _curveKnotArrays
|
||||
= new Dictionary<string, float[]>();
|
||||
[Serializable]
|
||||
public class BezierCurveData
|
||||
{
|
||||
@@ -89,6 +93,32 @@ public class MonsterMoveLine
|
||||
arr[total - 1] = curve.end;
|
||||
|
||||
_curvePointsArrays[curve.name] = arr;
|
||||
|
||||
// ⭐ 同步构建 B样条节点向量缓存
|
||||
_curveKnotArrays[curve.name] = BuildKnotVector(total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⭐ 构建钳位(clamped)均匀 B样条节点向量。
|
||||
/// 首尾各重复 p+1 个 0/1 → 曲线严格过首末控制点(start/end),鱼的出入屏位置与路线数据一致;
|
||||
/// 内部节点在 (0,1) 均匀分布。度数 p = min(3, 控制点数-1),点不足时自动降阶。
|
||||
/// </summary>
|
||||
private static float[] BuildKnotVector(int controlPointCount)
|
||||
{
|
||||
int p = Mathf.Min(3, controlPointCount - 1);
|
||||
int knotCount = controlPointCount + p + 1;
|
||||
float[] knots = new float[knotCount];
|
||||
|
||||
// 首尾钳位:前 p+1 个 0,后 p+1 个 1
|
||||
for (int i = 0; i <= p; i++) knots[i] = 0f;
|
||||
for (int i = controlPointCount; i < knotCount; i++) knots[i] = 1f;
|
||||
|
||||
// 内部节点:i = p+1 .. N-1,均匀分布在 (0,1)
|
||||
float denom = controlPointCount - p;
|
||||
for (int i = p + 1; i < controlPointCount; i++)
|
||||
knots[i] = (i - p) / denom;
|
||||
|
||||
return knots;
|
||||
}
|
||||
|
||||
public static async Task LoadBezierTxtAsync(string path)
|
||||
@@ -200,6 +230,9 @@ public class MonsterMoveLine
|
||||
// ⭐ 同步清理点数组缓存
|
||||
_curvePointsArrays.Remove(curveName);
|
||||
|
||||
// ⭐ 同步清理 B样条节点向量缓存
|
||||
_curveKnotArrays.Remove(curveName);
|
||||
|
||||
// 列表移除所有同名曲线
|
||||
curves.RemoveAll(c => c.name == curveName);
|
||||
}
|
||||
@@ -234,13 +267,19 @@ public class MonsterMoveLine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主线程零分配计算贝塞尔曲线点(De Casteljau 算法)。
|
||||
/// 主线程零分配计算曲线点(⭐ B样条 de Boor 算法;原贝塞尔 De Casteljau 已注释保留备查,方法名保留兼容调用处)。
|
||||
/// ⭐ 钳位(clamped)均匀三次 B样条:
|
||||
/// - 节点向量首尾各重复 p+1 个 0/1 → 曲线严格过首控制点(start)和末控制点(end),
|
||||
/// 鱼的出入屏位置与路线数据一致
|
||||
/// - 中间控制点只被逼近不再穿过(与贝塞尔的本质区别)
|
||||
/// - 控制点总数 ≤ 4 时钳位B样条数学上退化为同阶贝塞尔,短路线行为天然兼容
|
||||
/// - 度数 p = min(3, N-1):仅 start+end 两点时退化为直线段
|
||||
/// ⭐ 优化说明(原 Job 方案的负优化问题):
|
||||
/// 1. 原 GetBezierPointJob 每次调用 new List + 2 个 NativeArray(TempJob) + ToArray,
|
||||
/// 每条鱼每帧产生 3 次堆分配,N 条鱼 = 3N 次 GC 分配/帧
|
||||
/// 2. 原 Job.Schedule() 后立即 Complete(),Job 实际单线程执行,
|
||||
/// 调度开销 > 计算收益(4-7 个控制点的计算量极小)
|
||||
/// 3. 现方案:曲线点数组静态缓存(共享),主线程直接 Lerp 计算,零 GC 分配
|
||||
/// 3. 现方案:曲线点数组+节点向量静态缓存(共享),主线程直接计算,零 GC 分配
|
||||
/// 4. Camera/RectTransform 由调用方缓存,避免每帧每鱼重复获取
|
||||
/// </summary>
|
||||
public Vector2 GetBezierPointJob(float t)
|
||||
@@ -261,21 +300,69 @@ public class MonsterMoveLine
|
||||
pts = _curvePointsArrays[bezierCurveData.name];
|
||||
}
|
||||
|
||||
// ⭐ 取静态缓存的 B样条节点向量(加载时与点数组同步构建)
|
||||
if (!_curveKnotArrays.TryGetValue(bezierCurveData.name, out var knots))
|
||||
{
|
||||
// 兜底:缓存未命中时实时构建
|
||||
knots = BuildKnotVector(pts.Length);
|
||||
_curveKnotArrays[bezierCurveData.name] = knots;
|
||||
}
|
||||
|
||||
// ⭐ 复用线程静态缓冲区(主线程单线程执行,安全复用)
|
||||
if (_tempPoints == null || _tempPoints.Length < pts.Length)
|
||||
_tempPoints = new Vector2[pts.Length];
|
||||
Array.Copy(pts, _tempPoints, pts.Length);
|
||||
|
||||
// De Casteljau 算法:逐层 Lerp 直到剩一个点
|
||||
int count = pts.Length;
|
||||
while (count > 1)
|
||||
{
|
||||
for (int i = 0; i < count - 1; i++)
|
||||
_tempPoints[i] = Vector2.Lerp(_tempPoints[i], _tempPoints[i + 1], t);
|
||||
count--;
|
||||
}
|
||||
int count = pts.Length; // 控制点总数 N = start + 控制点 + end
|
||||
int p = Mathf.Min(3, count - 1); // ⭐ B样条度数:默认三次,控制点不足时自动降阶
|
||||
|
||||
Vector2 canvasPos = _tempPoints[0];
|
||||
Vector2 canvasPos;
|
||||
|
||||
// ⭐ 钳位B样条端点:曲线严格过首末控制点;de Boor 在端点节点区间退化,直接取端点等价且更稳
|
||||
if (t <= 0f)
|
||||
{
|
||||
canvasPos = pts[0];
|
||||
}
|
||||
else if (t >= 1f)
|
||||
{
|
||||
canvasPos = pts[count - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// ================= 贝塞尔(De Casteljau)已停用,注释保留备查 =================
|
||||
// int c = pts.Length;
|
||||
// while (c > 1)
|
||||
// {
|
||||
// for (int i = 0; i < c - 1; i++)
|
||||
// _tempPoints[i] = Vector2.Lerp(_tempPoints[i], _tempPoints[i + 1], t);
|
||||
// c--;
|
||||
// }
|
||||
// canvasPos = _tempPoints[0];
|
||||
// ==========================================================================
|
||||
|
||||
// ================= ⭐ B样条求值(de Boor 递推) =================
|
||||
// 找节点区间 k: knots[k] <= u < knots[k+1],k ∈ [p, N-1]
|
||||
int k = p;
|
||||
while (k < count - 1 && t >= knots[k + 1])
|
||||
k++;
|
||||
|
||||
// d[j] = pts[j + k - p],j = 0..p(取当前区间涉及的 p+1 个控制点)
|
||||
for (int j = 0; j <= p; j++)
|
||||
_tempPoints[j] = pts[j + k - p];
|
||||
|
||||
// de Boor 逐层递推(结构同 De Casteljau,alpha 改由节点向量决定)
|
||||
for (int r = 1; r <= p; r++)
|
||||
{
|
||||
for (int j = p; j >= r; j--)
|
||||
{
|
||||
// 分母区间 [j+k-p, j+1+k-r] ⊇ 当前节点区间 [k, k+1],恒非退化,无除零风险
|
||||
float alpha = (t - knots[j + k - p]) / (knots[j + 1 + k - r] - knots[j + k - p]);
|
||||
_tempPoints[j] = Vector2.Lerp(_tempPoints[j - 1], _tempPoints[j], alpha);
|
||||
}
|
||||
}
|
||||
canvasPos = _tempPoints[p];
|
||||
// ====================================================================
|
||||
}
|
||||
|
||||
// ⭐ 用缓存的 Camera/RectTransform,避免每帧每鱼查询
|
||||
var cam = cachedCamera != null ? cachedCamera : Camera.main;
|
||||
|
||||
Reference in New Issue
Block a user