#region AI优化 using System.Collections; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using System; [Serializable] public class MonsterMoveLine { public static Dictionary dic_BezierCurveDatas = new Dictionary(); public static List curves = new List(); // ---------------------------------------------------------------- // ⭐ 性能优化:曲线点数组静态缓存 // 每条曲线的 [start, controlPoints..., end] 预组装成 Vector2[], // 所有鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC 分配。 // 加载时构建,卸载时清理。 // ---------------------------------------------------------------- private static readonly Dictionary _curvePointsArrays = new Dictionary(); /// ⭐ B样条节点向量缓存(曲线名 → 节点数组,与点数组同步构建/清理) private static readonly Dictionary _curveKnotArrays = new Dictionary(); [Serializable] public class BezierCurveData { public string name; public Vector2 start; public List controlPoints = new List(); public Vector2 end; } public static async Task LoadBezier() { await LoadBezierTxtAsync("MoveLine"); } public static async Task UnLoadBezier() { await UnLoadBezierTxtAsync("MoveLine"); } public static async Task LoadSingleBezierTxtAsync(string path) { AsyncOperationHandle handle = Addressables.LoadAssetAsync(path); await handle.Task; var lines = handle.Result.text.Split('\n'); foreach (var line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; var parts = line.Split(';'); BezierCurveData curve = new BezierCurveData(); curve.name = parts[0]; List points = new List(); for (int i = 1; i < parts.Length; i++) { points.Add(ParseVector(parts[i])); } curve.start = points[0]; if (points.Count > 1) curve.end = points[points.Count - 1]; for (int i = 1; i < points.Count - 1; i++) curve.controlPoints.Add(points[i]); curves.Add(curve); dic_BezierCurveDatas.Add(curve.name, curve); // ⭐ 同步构建点数组缓存(单条加载路径) BuildCurvePointsArray(curve); } } /// /// ⭐ 将曲线的 [start, controlPoints..., end] 组装成 Vector2[] 并缓存。 /// 所有引用此曲线的鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC。 /// private static void BuildCurvePointsArray(BezierCurveData curve) { if (_curvePointsArrays.ContainsKey(curve.name)) return; int total = 2 + curve.controlPoints.Count; // start + controls + end Vector2[] arr = new Vector2[total]; arr[0] = curve.start; for (int i = 0; i < curve.controlPoints.Count; i++) arr[1 + i] = curve.controlPoints[i]; arr[total - 1] = curve.end; _curvePointsArrays[curve.name] = arr; // ⭐ 同步构建 B样条节点向量缓存 _curveKnotArrays[curve.name] = BuildKnotVector(total); } /// /// ⭐ 构建钳位(clamped)均匀 B样条节点向量。 /// 首尾各重复 p+1 个 0/1 → 曲线严格过首末控制点(start/end),鱼的出入屏位置与路线数据一致; /// 内部节点在 (0,1) 均匀分布。度数 p = min(3, 控制点数-1),点不足时自动降阶。 /// 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) { List keys = new List(); keys.Add(path); keys.Add(GameManager.Instance.gameData.currGameType.ToString()); AsyncOperationHandle> handles = Addressables.LoadAssetsAsync( keys, null, Addressables.MergeMode.Intersection); await handles.Task; // ⭐ 没有资源 → 直接跳出,不报错 if (handles.Result == null || handles.Result.Count == 0) { Debug.LogWarning($"Bezier 曲线加载跳过:未找到标签 {path} + {GameManager.Instance.gameData.currGameType}"); Addressables.Release(handles); return; } foreach (var text in handles.Result) { var lines = text.text.Split('\n'); foreach (var line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; var parts = line.Split(';'); if (parts.Length < 2) continue; BezierCurveData curve = new BezierCurveData(); curve.name = parts[0]; List points = new List(); for (int i = 1; i < parts.Length; i++) { if (!string.IsNullOrWhiteSpace(parts[i])) points.Add(ParseVector(parts[i])); } if (points.Count == 0) continue; curve.start = points[0]; if (points.Count > 1) curve.end = points[points.Count - 1]; for (int i = 1; i < points.Count - 1; i++) curve.controlPoints.Add(points[i]); curves.Add(curve); // 不覆盖旧数据 if (!dic_BezierCurveDatas.ContainsKey(curve.name)) { dic_BezierCurveDatas.Add(curve.name, curve); // ⭐ 同步构建点数组缓存(批量加载路径) BuildCurvePointsArray(curve); } } } Addressables.Release(handles); Debug.Log("曲线读取完成共" + dic_BezierCurveDatas.Count + "条"); } public static async Task UnLoadBezierTxtAsync(string path) { List keys = new List(); keys.Add(path); keys.Add(GameManager.Instance.gameData.currGameType.ToString()); AsyncOperationHandle> handles = Addressables.LoadAssetsAsync( keys, null, Addressables.MergeMode.Intersection); await handles.Task; // ⭐ 没有资源 → 直接跳出,不报错 if (handles.Result == null || handles.Result.Count == 0) { Debug.LogWarning($"Bezier 曲线卸载跳过:未找到标签 {path} + {GameManager.Instance.gameData.currGameType}"); Addressables.Release(handles); return; } foreach (var text in handles.Result) { var lines = text.text.Split('\n'); foreach (var line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; var parts = line.Split(';'); if (parts.Length == 0) continue; string curveName = parts[0]; if (string.IsNullOrWhiteSpace(curveName)) continue; // 字典存在才移除 if (dic_BezierCurveDatas.ContainsKey(curveName)) { dic_BezierCurveDatas.Remove(curveName); } // ⭐ 同步清理点数组缓存 _curvePointsArrays.Remove(curveName); // ⭐ 同步清理 B样条节点向量缓存 _curveKnotArrays.Remove(curveName); // 列表移除所有同名曲线 curves.RemoveAll(c => c.name == curveName); } } Addressables.Release(handles); Debug.Log("曲线卸载完成,剩余曲线数量:" + dic_BezierCurveDatas.Count); } private static Vector2 ParseVector(string s) { var xy = s.Split(','); return new Vector2(float.Parse(xy[0]), float.Parse(xy[1])); } public BezierCurveData bezierCurveData; public float moveTime; public float moveSpeed; // ⭐ 缓存相机和 UI 根 RectTransform,避免每帧每鱼调用 // UIManager.Instance.uiRoot.GetComponent() 和 Camera.main // 由 MonsterContral.OnEnable 同步赋值 public Camera cachedCamera; public RectTransform cachedUirootRect; public void Init(BezierCurveData bezierCurveData, float Time) { this.bezierCurveData = bezierCurveData; this.moveTime = Time; Debug.Log($"初始化曲线 {this.bezierCurveData.name} ,移动时间 {moveTime}"); } /// /// 主线程零分配计算曲线点(⭐ 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. 现方案:曲线点数组+节点向量静态缓存(共享),主线程直接计算,零 GC 分配 /// 4. Camera/RectTransform 由调用方缓存,避免每帧每鱼重复获取 /// public Vector2 GetBezierPointJob(float t) { // ⭐ 防止除零:moveTime<=0 时直接返回起点,避免 NaN if (moveTime <= 0f) return bezierCurveData.start; if (t > moveTime) t = moveTime; t = t / moveTime; // ⭐ 取静态缓存的曲线点数组(所有鱼共享,零分配) if (!_curvePointsArrays.TryGetValue(bezierCurveData.name, out var pts)) { // 兜底:缓存未命中时实时构建(理论上不应发生,加载时已构建) BuildCurvePointsArray(bezierCurveData); 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); int count = pts.Length; // 控制点总数 N = start + 控制点 + end int p = Mathf.Min(3, count - 1); // ⭐ B样条度数:默认三次,控制点不足时自动降阶 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; var rect = cachedUirootRect != null ? cachedUirootRect : UIManager.Instance.uiRoot.GetComponent(); return CoordinateTransformation.CanvasToWorld(canvasPos, rect, cam); } /// 主线程复用的临时计算缓冲(避免每帧每鱼 new 数组) [ThreadStatic] private static Vector2[] _tempPoints; } #endregion #region zijixie //using System.Collections; //using System.Collections.Generic; //using System.IO; //using System.Threading.Tasks; //using UnityEngine; //using UnityEngine.AddressableAssets; //using UnityEngine.ResourceManagement.AsyncOperations; //public class MonsterMoveLine //{ // public static Dictionary dic_BezierCurveDatas = new Dictionary(); // public static List curves = new List(); // public class BezierCurveData // { // public string name; // public Vector2 start; // public List controlPoints = new List(); // public Vector2 end; // } // public static async void LoadBezier() // { // await LoadBezierTxtAsync("MoveLine"); // } // /// // /// 读取曲线数据 读取单个 // /// // /// // /// // public static async Task LoadSingleBezierTxtAsync(string path) // { // AsyncOperationHandle handle = Addressables.LoadAssetAsync(path); // await handle.Task; // var lines = handle.Result.text.Split('\n'); // foreach (var line in lines) // { // if (string.IsNullOrWhiteSpace(line)) continue; // var parts = line.Split(';'); // BezierCurveData curve = new BezierCurveData(); // // 第一个是曲线名 // curve.name = parts[0]; // // 后面所有都是点 // List points = new List(); // for (int i = 1; i < parts.Length; i++) // { // points.Add(ParseVector(parts[i])); // } // // 第一个点是 start // curve.start = points[0]; // if (points.Count > 1) // { // // 最后一个点是 end // curve.end = points[points.Count - 1]; // } // // 中间的都是 controlPoints // for (int i = 1; i < points.Count - 1; i++) // { // curve.controlPoints.Add(points[i]); // } // curves.Add(curve); // dic_BezierCurveDatas.Add(curve.name, curve); // } // } // /// // /// 读取曲线数据 游戏开始时在awake调用aa包 // /// // /// // /// // public static async Task LoadBezierTxtAsync(string path) // { // List keys = new List(); // keys.Add(path); // keys.Add(GameManager.Instance.gameData.currGameType.ToString()); // AsyncOperationHandle> handles = Addressables.LoadAssetsAsync(keys, null, Addressables.MergeMode.Intersection); // await handles.Task; // foreach (var text in handles.Result) // { // var lines = text.text.Split('\n'); // foreach (var line in lines) // { // if (string.IsNullOrWhiteSpace(line)) continue; // var parts = line.Split(';'); // BezierCurveData curve = new BezierCurveData(); // // 第一个是曲线名 // curve.name = parts[0]; // // 后面所有都是点 // List points = new List(); // for (int i = 1; i < parts.Length; i++) // { // points.Add(ParseVector(parts[i])); // } // // 第一个点是 start // curve.start = points[0]; // if (points.Count > 1) // { // // 最后一个点是 end // curve.end = points[points.Count - 1]; // } // // 中间的都是 controlPoints // for (int i = 1; i < points.Count - 1; i++) // { // curve.controlPoints.Add(points[i]); // } // curves.Add(curve); // dic_BezierCurveDatas.Add(curve.name, curve); // } // } // Debug.Log("曲线读取完成共" + dic_BezierCurveDatas.Count + "条"); // } // private static Vector2 ParseVector(string s) // { // var xy = s.Split(','); // return new Vector2(float.Parse(xy[0]), float.Parse(xy[1])); // } // public BezierCurveData bezierCurveData; // public float moveTime; // public float moveSpeed; // public void Init(BezierCurveData bezierCurveData, float Time) // { // this.bezierCurveData = bezierCurveData; // this.moveTime = time; // Debug.Log($"初始化曲线 {this.bezierCurveData.name} ,移动时间 {moveTime}"); // } // /// // /// 获取曲线上的点 // /// // /// // /// // public Vector2 GetBezierPoint(float t) // { // t = t / moveTime; // BezierCurveData curve = bezierCurveData; // List points = new List(); // points.Add(curve.start); // points.AddRange(curve.controlPoints); // points.Add(curve.end); // return DeCasteljau_BY(points, t % 1); // } // /// // /// 贝塞尔 // /// // /// // /// // /// // private Vector2 DeCasteljau_BSE(List points, float t) // { // while (points.Count > 1) // { // List next = new List(); // for (int i = 0; i < points.Count - 1; i++) // { // next.Add(Vector2.Lerp(points[i], points[i + 1], t)); // } // points = next; // } // return CoordinateTransformation.CanvasToWorld(points[0], UIManager.Instance.uiRoot.GetComponent(), Camera.main); // } // /// // /// B样 // /// // /// // /// // /// // private Vector2 DeCasteljau_BY(List points, float t) // { // // 方法A:保持德卡斯特里奥算法(推荐,数值更稳定) // while (points.Count > 1) // { // List next = new List(); // for (int i = 0; i < points.Count - 1; i++) // { // next.Add(Vector2.Lerp(points[i], points[i + 1], t)); // } // points = next; // } // return CoordinateTransformation.CanvasToWorld(points[0], // UIManager.Instance.uiRoot.GetComponent(), Camera.main); // /* 方法B:伯恩斯坦多项式形式(标准贝塞尔公式)- 如果要用这个替换上面的循环 // int n = points.Count - 1; // Vector2 result = Vector2.zero; // for (int i = 0; i <= n; i++) // { // float bernstein = BinomialCoefficient(n, i) * Mathf.Pow(t, i) * Mathf.Pow(1 - t, n - i); // result += points[i] * bernstein; // } // return CoordinateTransformation.CanvasToWorld(result, // UIManager.Instance.uiRoot.GetComponent(), Camera.main); // */ // } // // 计算二项式系数 // private float BinomialCoefficient(int n, int k) // { // float result = 1f; // for (int i = 1; i <= k; i++) // { // result *= (float)(n - k + i) / i; // } // return result; // } //} #endregion