Files
5.24BuYu/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs
T
2026-09-18 08:15:08 +08:00

617 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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<string, BezierCurveData> dic_BezierCurveDatas = new Dictionary<string, BezierCurveData>();
public static List<BezierCurveData> curves = new List<BezierCurveData>();
// ----------------------------------------------------------------
// ⭐ 性能优化:曲线点数组静态缓存
// 每条曲线的 [start, controlPoints..., end] 预组装成 Vector2[],
// 所有鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC 分配。
// 加载时构建,卸载时清理。
// ----------------------------------------------------------------
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
{
public string name;
public Vector2 start;
public List<Vector2> controlPoints = new List<Vector2>();
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<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>(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<Vector2> points = new List<Vector2>();
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);
}
}
/// <summary>
/// ⭐ 将曲线的 [start, controlPoints..., end] 组装成 Vector2[] 并缓存。
/// 所有引用此曲线的鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC。
/// </summary>
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);
}
/// <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)
{
List<string> keys = new List<string>();
keys.Add(path);
keys.Add(GameManager.Instance.gameData.currGameType.ToString());
AsyncOperationHandle<IList<TextAsset>> handles = Addressables.LoadAssetsAsync<TextAsset>(
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<Vector2> points = new List<Vector2>();
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<string> keys = new List<string>();
keys.Add(path);
keys.Add(GameManager.Instance.gameData.currGameType.ToString());
AsyncOperationHandle<IList<TextAsset>> handles = Addressables.LoadAssetsAsync<TextAsset>(
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<RectTransform>() 和 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}");
}
/// <summary>
/// 主线程零分配计算曲线点(⭐ 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 由调用方缓存,避免每帧每鱼重复获取
/// </summary>
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<RectTransform>();
return CoordinateTransformation.CanvasToWorld(canvasPos, rect, cam);
}
/// <summary>主线程复用的临时计算缓冲(避免每帧每鱼 new 数组)</summary>
[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<string, BezierCurveData> dic_BezierCurveDatas = new Dictionary<string, BezierCurveData>();
// public static List<BezierCurveData> curves = new List<BezierCurveData>();
// public class BezierCurveData
// {
// public string name;
// public Vector2 start;
// public List<Vector2> controlPoints = new List<Vector2>();
// public Vector2 end;
// }
// public static async void LoadBezier()
// {
// await LoadBezierTxtAsync("MoveLine");
// }
// /// <summary>
// /// 读取曲线数据 读取单个
// /// </summary>
// /// <param name="path"></param>
// /// <returns></returns>
// public static async Task LoadSingleBezierTxtAsync(string path)
// {
// AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>(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<Vector2> points = new List<Vector2>();
// 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);
// }
// }
// /// <summary>
// /// 读取曲线数据 游戏开始时在awake调用aa包
// /// </summary>
// /// <param name="path"></param>
// /// <returns></returns>
// public static async Task LoadBezierTxtAsync(string path)
// {
// List<string> keys = new List<string>();
// keys.Add(path);
// keys.Add(GameManager.Instance.gameData.currGameType.ToString());
// AsyncOperationHandle<IList<TextAsset>> handles = Addressables.LoadAssetsAsync<TextAsset>(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<Vector2> points = new List<Vector2>();
// 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}");
// }
// /// <summary>
// /// 获取曲线上的点
// /// </summary>
// /// <param name="t"></param>
// /// <returns></returns>
// public Vector2 GetBezierPoint(float t)
// {
// t = t / moveTime;
// BezierCurveData curve = bezierCurveData;
// List<Vector2> points = new List<Vector2>();
// points.Add(curve.start);
// points.AddRange(curve.controlPoints);
// points.Add(curve.end);
// return DeCasteljau_BY(points, t % 1);
// }
// /// <summary>
// /// 贝塞尔
// /// </summary>
// /// <param name="points"></param>
// /// <param name="t"></param>
// /// <returns></returns>
// private Vector2 DeCasteljau_BSE(List<Vector2> points, float t)
// {
// while (points.Count > 1)
// {
// List<Vector2> next = new List<Vector2>();
// 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<RectTransform>(), Camera.main);
// }
// /// <summary>
// /// B样
// /// </summary>
// /// <param name="points"></param>
// /// <param name="t"></param>
// /// <returns></returns>
// private Vector2 DeCasteljau_BY(List<Vector2> points, float t)
// {
// // 方法A:保持德卡斯特里奥算法(推荐,数值更稳定)
// while (points.Count > 1)
// {
// List<Vector2> next = new List<Vector2>();
// 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<RectTransform>(), 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<RectTransform>(), 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