Files
5.24BuYu/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs
T
2026-10-12 15:32:36 +08:00

741 lines
30 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;
using System.Globalization;
[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;
}
/// <summary>加载当前玩法路线。onProgress:0~1 进度回调(供进房进度条;路线文本可能和鱼资源在同一个大包里,
/// 包的解压耗时会体现在这里,避免大包场景进度条失真)</summary>
public static async Task LoadBezier(Action<float> onProgress = null)
{
await LoadBezierTxtAsync("MoveLine", onProgress);
}
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++)
{
if (string.IsNullOrWhiteSpace(parts[i]))
continue;
// ⭐ 单点格式错误只跳过该点并告警,不能让整条路线(乃至进房加载流程)中断
if (TryParseVector(parts[i], out var p))
points.Add(p);
else
Debug.LogWarning($"[MonsterMoveLine] 坐标格式非法已跳过: '{parts[i]}'({handle.Result.name} 曲线 {curve.name})");
}
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]);
// ⭐ 座位镜像:本端在上方座位时路线整条上下翻转(写入字典/建缓存前,只翻一次)
FlipCurveYForMirror(curve);
curves.Add(curve);
dic_BezierCurveDatas.Add(curve.name, curve);
// ⭐ 同步构建点数组缓存(单条加载路径)
BuildCurvePointsArray(curve);
}
}
/// <summary>
/// ⭐ 座位镜像:路线整条上下翻转(所有点 y 关于画布中心取反,画布坐标以中心为原点)。
/// B样条求值(de Boor)是逐点线性插值,对控制点 y 取反后求样条 ≡ 原样条求值后 y 取反,
/// 曲线形状严格镜像不形变。必须在写入 dic_BezierCurveDatas / BuildCurvePointsArray
/// 之前调用(curves 列表与字典引用同一实例,只翻一次)。
/// 下游自动适应:取点/移动/朝向切线/鱼阵 700 起点/翻面/分布角全部基于翻转后数据;
/// 仅 701 环形阵写死的"固定向上"需在 MonsterContral 里随镜像取反。
/// </summary>
private static void FlipCurveYForMirror(BezierCurveData curve)
{
if (!SeatMirror.IsMirrored) return;
curve.start.y = -curve.start.y;
curve.end.y = -curve.end.y;
for (int i = 0; i < curve.controlPoints.Count; i++)
{
Vector2 p = curve.controlPoints[i];
p.y = -p.y;
curve.controlPoints[i] = p;
}
}
/// <summary>
/// ⭐ 将曲线的 [start, controlPoints..., end] 组装成 Vector2[] 并缓存。
/// 所有引用此曲线的鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC。
/// </summary>
private static void BuildCurvePointsArray(BezierCurveData curve)
{
// ⭐ 无条件覆盖重建:同名曲线被覆盖时,点数组/节点缓存必须同步重建,
// 否则字典指向新曲线、缓存还是旧曲线的点 → 路线错乱
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;
}
/// <summary>
/// ⭐ 路线加载句柄(保留到离房才释放):
/// 路线文本常和鱼资源打在同一个大包里。旧实现"读完文本立即 Release",
/// 单一大包场景下引用归零 → 大包立即卸载 → LoadFishAssetsData 又重新加载同一个包
/// (同一个 LZMA 大包解压两次 + 内存暴涨→回落→再暴涨的过山车)。
/// 现在句柄保留,与鱼包句柄共存,离房 UnLoadBezierTxtAsync 统一释放。
/// </summary>
private static AsyncOperationHandle<IList<TextAsset>> _bezierHandles;
public static async Task LoadBezierTxtAsync(string path, Action<float> onProgress = null)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadBezierTxtAsyncCore(path, onProgress); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadBezierTxtAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadBezierTxtAsyncCore(string path, Action<float> onProgress)
{
// ⭐ 加载前先全量清空上一场的路线缓存:
// 各玩法普通路线/鱼阵曲线名完全重名(曲线1~N、鱼阵N)但坐标不同,
// 不清场会把上一玩法的同名坐标带进本场 → 路线错乱(如 HHWK 残留导致 FLZY 鱼按旧路线游);
// 旧玩法独有的 Boss 路线也会残留越积越多。进游戏只需要当前玩法的路线。
ClearAllBezierCache();
List<string> keys = new List<string>();
keys.Add(path);
keys.Add(GameManager.Instance.gameData.currGameType.ToString());
// ⭐ 防御:上一轮句柄未释放(如离房流程被跳过)时先释放,避免引用计数累加
if (_bezierHandles.IsValid())
Addressables.Release(_bezierHandles);
_bezierHandles = Addressables.LoadAssetsAsync<TextAsset>(
keys, null, Addressables.MergeMode.Intersection);
// ⭐ 轮询上报进度(0~85% 为加载段):路线文本常和鱼资源打在同一个大包里,
// 大包的解压耗时发生在这里,必须反映到进度条(否则单一大包场景进度失真:
// 卡在路线段不动,后面的"鱼包段"反而秒过)。无回调时直接等完成。
if (onProgress != null)
{
while (!_bezierHandles.IsDone)
{
onProgress(0.85f * _bezierHandles.PercentComplete);
await Task.Delay(50);
}
}
else
{
await _bezierHandles.Task;
}
// ⭐ 没有资源 → 直接跳出,不报错(缓存已清空,保持干净状态)
// 句柄保留(离房统一释放),防止路线与鱼同包时提前卸掉大包
if (_bezierHandles.Result == null || _bezierHandles.Result.Count == 0)
{
Debug.LogWarning($"Bezier 曲线加载跳过:未找到标签 {path} + {GameManager.Instance.gameData.currGameType}");
onProgress?.Invoke(1f);
return;
}
// ⭐ 主线程只做 Unity API 访问:把每个 TextAsset 的 name/text 读成纯托管字符串。
// .text 返回托管 string 拷贝,句柄保留期间数据稳定,后台线程可安全使用。
// (句柄不再立即 Release —— 路线与鱼同包时提前释放会把整个大包卸掉)
int fileCount = _bezierHandles.Result.Count;
string[] fileNames = new string[fileCount];
string[] fileTexts = new string[fileCount];
for (int i = 0; i < fileCount; i++)
{
fileNames[i] = _bezierHandles.Result[i].name;
fileTexts[i] = _bezierHandles.Result[i].text;
}
onProgress?.Invoke(0.88f);
// ⭐ 座位镜像状态主线程读一次传入后台(后台线程不访问任何 Unity/游戏静态状态)
bool mirrored = SeatMirror.IsMirrored;
var warnings = new List<string>();
// ⭐ 重活挪到后台线程:全部路线文件的 Split + 逐坐标点 float 解析是纯托管计算,
// 文件大时主线程同步解析会卡死数秒(进度条停在20%冻结的根源)。
// await 完成后自动切回 Unity 主线程再写共享缓存。
var parsedCurves = await Task.Run(() => ParseBezierFiles(fileNames, fileTexts, mirrored, warnings));
// 后台收集的告警回主线程输出(Debug API 只能在主线程调用),只打前 20 条防刷屏
for (int i = 0; i < warnings.Count && i < 20; i++)
Debug.LogWarning(warnings[i]);
// ⭐ 回到主线程写共享缓存 + 构建点数组/节点向量缓存
// (同名直接覆盖,清场后理论上无重复,防御性覆盖确保永远用当前玩法数据)
foreach (var curve in parsedCurves)
{
curves.Add(curve);
dic_BezierCurveDatas[curve.name] = curve;
BuildCurvePointsArray(curve);
}
onProgress?.Invoke(1f);
// ⭐ 座位镜像状态提示:镜像未初始化(-1)时按不镜像处理,排查进房时序问题用
if (SeatMirror.LocalServerPos < 0)
Debug.LogWarning("[MonsterMoveLine] 加载路线时 SeatMirror 未初始化(未收到 RoomJoinNotice),本次按不镜像处理");
Debug.Log($"曲线读取完成共{dic_BezierCurveDatas.Count}条 镜像={SeatMirror.IsMirrored}(本地服务端座位={SeatMirror.LocalServerPos})");
}
/// <summary>
/// ⭐ 后台线程解析全部路线文本(纯托管计算,禁止访问任何 Unity API):
/// 逐行 Split + InvariantCulture float 解析 + 曲线组装 + 镜像翻转(镜像标志由主线程传入)。
/// 解析告警收集到 warnings 列表,由调用方回主线程统一输出。
/// </summary>
private static List<BezierCurveData> ParseBezierFiles(string[] fileNames, string[] fileTexts, bool mirrored, List<string> warnings)
{
var result = new List<BezierCurveData>();
for (int f = 0; f < fileTexts.Length; f++)
{
var lines = fileTexts[f].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]))
{
// ⭐ 单点格式错误只跳过该点并告警,不能让整条路线(乃至进房加载流程)中断
if (TryParseVector(parts[i], out var p))
{
points.Add(p);
}
else
{
warnings.Add($"[MonsterMoveLine] 坐标格式非法已跳过: '{parts[i]}'({fileNames[f]} 曲线 {curve.name})");
}
}
}
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]);
// ⭐ 座位镜像:本端在上方座位时路线整条上下翻转(与 FlipCurveYForMirror 同逻辑)。
// B样条求值(de Boor)是逐点线性插值,对控制点 y 取反后求样条 ≡ 原样条求值后 y 取反,
// 曲线形状严格镜像不形变。
if (mirrored)
{
curve.start.y = -curve.start.y;
curve.end.y = -curve.end.y;
for (int i = 0; i < curve.controlPoints.Count; i++)
{
Vector2 p = curve.controlPoints[i];
p.y = -p.y;
curve.controlPoints[i] = p;
}
}
result.Add(curve);
}
}
return result;
}
public static async Task UnLoadBezierTxtAsync(string path)
{
// ⭐ 释放路线加载句柄(与鱼包句柄共同归零后,路线/鱼共用的大包才真正卸载)
if (_bezierHandles.IsValid())
{
Addressables.Release(_bezierHandles);
}
// ⭐ 全量同步清空,不再按 currGameType 重查 Addressables 删除:
// 1. 卸载依赖 currGameType 有时序风险 —— 选房面板在玩家点选新玩法时就改了全局类型,
// 延迟执行的卸载(OnHide 补卸载/异常重跑)会按新玩法 txt 去删 → 旧玩法 Boss 路线全部残留;
// 甚至可能把新玩法刚加载的路线误删。
// 2. 原实现标签查询为空时静默 return,什么都清不掉。
// 卸载只发生在离房时,场上只有当前玩法的路线,直接全清最干净且无竞态。
ClearAllBezierCache();
Debug.Log("曲线卸载完成,剩余曲线数量:" + dic_BezierCurveDatas.Count);
await Task.CompletedTask;
}
/// <summary>全量清空路线缓存(曲线字典/列表 + 点数组缓存 + B样条节点缓存)</summary>
private static void ClearAllBezierCache()
{
dic_BezierCurveDatas.Clear();
curves.Clear();
_curvePointsArrays.Clear();
_curveKnotArrays.Clear();
}
private static bool TryParseVector(string s, out Vector2 v)
{
v = default;
var xy = s.Split(',');
if (xy.Length < 2) return false;
// ⭐ 固定用 InvariantCulture:曲线文件是 "x,y" 机器格式,不能受设备区域的小数点符号影响
if (!float.TryParse(xy[0], NumberStyles.Float, CultureInfo.InvariantCulture, out float x)) return false;
if (!float.TryParse(xy[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y)) return false;
v = new Vector2(x, y);
return true;
}
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