2026-07-28 10:59:08 +08:00
|
|
|
|
#region AI优化
|
2026-03-31 18:30:49 +08:00
|
|
|
|
using System.Collections;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.AddressableAssets;
|
|
|
|
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
2026-07-05 16:07:22 +08:00
|
|
|
|
using System;
|
2026-09-28 02:32:57 +08:00
|
|
|
|
using System.Globalization;
|
2026-07-05 16:07:22 +08:00
|
|
|
|
[Serializable]
|
2025-12-26 11:15:56 +08:00
|
|
|
|
public class MonsterMoveLine
|
|
|
|
|
|
{
|
2026-03-31 18:30:49 +08:00
|
|
|
|
public static Dictionary<string, BezierCurveData> dic_BezierCurveDatas = new Dictionary<string, BezierCurveData>();
|
2025-12-26 11:15:56 +08:00
|
|
|
|
public static List<BezierCurveData> curves = new List<BezierCurveData>();
|
2026-08-04 14:52:22 +08:00
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// ⭐ 性能优化:曲线点数组静态缓存
|
|
|
|
|
|
// 每条曲线的 [start, controlPoints..., end] 预组装成 Vector2[],
|
|
|
|
|
|
// 所有鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC 分配。
|
|
|
|
|
|
// 加载时构建,卸载时清理。
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
private static readonly Dictionary<string, Vector2[]> _curvePointsArrays
|
|
|
|
|
|
= new Dictionary<string, Vector2[]>();
|
2026-09-18 08:15:08 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>⭐ B样条节点向量缓存(曲线名 → 节点数组,与点数组同步构建/清理)</summary>
|
|
|
|
|
|
private static readonly Dictionary<string, float[]> _curveKnotArrays
|
|
|
|
|
|
= new Dictionary<string, float[]>();
|
2026-07-05 16:07:22 +08:00
|
|
|
|
[Serializable]
|
2025-12-26 11:15:56 +08:00
|
|
|
|
public class BezierCurveData
|
|
|
|
|
|
{
|
|
|
|
|
|
public string name;
|
|
|
|
|
|
public Vector2 start;
|
|
|
|
|
|
public List<Vector2> controlPoints = new List<Vector2>();
|
|
|
|
|
|
public Vector2 end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-09 20:20:14 +08:00
|
|
|
|
/// <summary>加载当前玩法路线。onProgress:0~1 进度回调(供进房进度条;路线文本可能和鱼资源在同一个大包里,
|
|
|
|
|
|
/// 包的解压耗时会体现在这里,避免大包场景进度条失真)</summary>
|
|
|
|
|
|
public static async Task LoadBezier(Action<float> onProgress = null)
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-10-09 20:20:14 +08:00
|
|
|
|
await LoadBezierTxtAsync("MoveLine", onProgress);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
2026-05-01 06:26:50 +08:00
|
|
|
|
public static async Task UnLoadBezier()
|
|
|
|
|
|
{
|
|
|
|
|
|
await UnLoadBezierTxtAsync("MoveLine");
|
|
|
|
|
|
}
|
2026-03-31 18:30:49 +08:00
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public static async Task LoadSingleBezierTxtAsync(string path)
|
2025-12-26 11:15:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>(path);
|
|
|
|
|
|
await handle.Task;
|
|
|
|
|
|
var lines = handle.Result.text.Split('\n');
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2025-12-26 11:15:56 +08:00
|
|
|
|
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++)
|
|
|
|
|
|
{
|
2026-09-28 02:32:57 +08:00
|
|
|
|
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})");
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 02:32:57 +08:00
|
|
|
|
if (points.Count == 0) continue;
|
|
|
|
|
|
|
2025-12-26 11:15:56 +08:00
|
|
|
|
curve.start = points[0];
|
2026-02-03 11:35:19 +08:00
|
|
|
|
if (points.Count > 1)
|
|
|
|
|
|
curve.end = points[points.Count - 1];
|
2025-12-26 11:15:56 +08:00
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < points.Count - 1; i++)
|
|
|
|
|
|
curve.controlPoints.Add(points[i]);
|
|
|
|
|
|
|
2026-09-27 19:11:25 +08:00
|
|
|
|
// ⭐ 座位镜像:本端在上方座位时路线整条上下翻转(写入字典/建缓存前,只翻一次)
|
|
|
|
|
|
FlipCurveYForMirror(curve);
|
|
|
|
|
|
|
2025-12-26 11:15:56 +08:00
|
|
|
|
curves.Add(curve);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
dic_BezierCurveDatas.Add(curve.name, curve);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 同步构建点数组缓存(单条加载路径)
|
|
|
|
|
|
BuildCurvePointsArray(curve);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-31 18:30:49 +08:00
|
|
|
|
|
2026-09-27 19:11:25 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// ⭐ 将曲线的 [start, controlPoints..., end] 组装成 Vector2[] 并缓存。
|
|
|
|
|
|
/// 所有引用此曲线的鱼共享同一份数组,避免每帧每鱼 new List + ToArray 的 GC。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void BuildCurvePointsArray(BezierCurveData curve)
|
|
|
|
|
|
{
|
2026-09-27 15:47:27 +08:00
|
|
|
|
// ⭐ 无条件覆盖重建:同名曲线被覆盖时,点数组/节点缓存必须同步重建,
|
|
|
|
|
|
// 否则字典指向新曲线、缓存还是旧曲线的点 → 路线错乱
|
2026-08-04 14:52:22 +08:00
|
|
|
|
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;
|
2026-09-18 08:15:08 +08:00
|
|
|
|
|
|
|
|
|
|
// ⭐ 同步构建 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;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-09 20:20:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// ⭐ 路线加载句柄(保留到离房才释放):
|
|
|
|
|
|
/// 路线文本常和鱼资源打在同一个大包里。旧实现"读完文本立即 Release",
|
|
|
|
|
|
/// 单一大包场景下引用归零 → 大包立即卸载 → LoadFishAssetsData 又重新加载同一个包
|
|
|
|
|
|
/// (同一个 LZMA 大包解压两次 + 内存暴涨→回落→再暴涨的过山车)。
|
|
|
|
|
|
/// 现在句柄保留,与鱼包句柄共存,离房 UnLoadBezierTxtAsync 统一释放。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static AsyncOperationHandle<IList<TextAsset>> _bezierHandles;
|
|
|
|
|
|
|
|
|
|
|
|
public static async Task LoadBezierTxtAsync(string path, Action<float> onProgress = null)
|
2026-10-12 15:32:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
// ⭐ 资源加载超过 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)
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-09-27 15:47:27 +08:00
|
|
|
|
// ⭐ 加载前先全量清空上一场的路线缓存:
|
|
|
|
|
|
// 各玩法普通路线/鱼阵曲线名完全重名(曲线1~N、鱼阵N)但坐标不同,
|
|
|
|
|
|
// 不清场会把上一玩法的同名坐标带进本场 → 路线错乱(如 HHWK 残留导致 FLZY 鱼按旧路线游);
|
|
|
|
|
|
// 旧玩法独有的 Boss 路线也会残留越积越多。进游戏只需要当前玩法的路线。
|
|
|
|
|
|
ClearAllBezierCache();
|
|
|
|
|
|
|
2026-03-31 18:30:49 +08:00
|
|
|
|
List<string> keys = new List<string>();
|
2026-03-27 15:18:35 +08:00
|
|
|
|
keys.Add(path);
|
|
|
|
|
|
keys.Add(GameManager.Instance.gameData.currGameType.ToString());
|
2026-05-01 06:26:50 +08:00
|
|
|
|
|
2026-10-09 20:20:14 +08:00
|
|
|
|
// ⭐ 防御:上一轮句柄未释放(如离房流程被跳过)时先释放,避免引用计数累加
|
|
|
|
|
|
if (_bezierHandles.IsValid())
|
|
|
|
|
|
Addressables.Release(_bezierHandles);
|
|
|
|
|
|
|
|
|
|
|
|
_bezierHandles = Addressables.LoadAssetsAsync<TextAsset>(
|
2026-05-01 06:26:50 +08:00
|
|
|
|
keys, null, Addressables.MergeMode.Intersection);
|
2026-10-09 20:20:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ⭐ 轮询上报进度(0~85% 为加载段):路线文本常和鱼资源打在同一个大包里,
|
|
|
|
|
|
// 大包的解压耗时发生在这里,必须反映到进度条(否则单一大包场景进度失真:
|
|
|
|
|
|
// 卡在路线段不动,后面的"鱼包段"反而秒过)。无回调时直接等完成。
|
|
|
|
|
|
if (onProgress != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!_bezierHandles.IsDone)
|
|
|
|
|
|
{
|
|
|
|
|
|
onProgress(0.85f * _bezierHandles.PercentComplete);
|
|
|
|
|
|
await Task.Delay(50);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await _bezierHandles.Task;
|
|
|
|
|
|
}
|
2026-03-31 18:30:49 +08:00
|
|
|
|
|
2026-09-27 15:47:27 +08:00
|
|
|
|
// ⭐ 没有资源 → 直接跳出,不报错(缓存已清空,保持干净状态)
|
2026-10-09 20:20:14 +08:00
|
|
|
|
// 句柄保留(离房统一释放),防止路线与鱼同包时提前卸掉大包
|
|
|
|
|
|
if (_bezierHandles.Result == null || _bezierHandles.Result.Count == 0)
|
2026-05-07 03:46:03 +08:00
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"Bezier 曲线加载跳过:未找到标签 {path} + {GameManager.Instance.gameData.currGameType}");
|
2026-10-09 20:20:14 +08:00
|
|
|
|
onProgress?.Invoke(1f);
|
2026-05-07 03:46:03 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-09 20:20:14 +08:00
|
|
|
|
// ⭐ 主线程只做 Unity API 访问:把每个 TextAsset 的 name/text 读成纯托管字符串。
|
|
|
|
|
|
// .text 返回托管 string 拷贝,句柄保留期间数据稳定,后台线程可安全使用。
|
|
|
|
|
|
// (句柄不再立即 Release —— 路线与鱼同包时提前释放会把整个大包卸掉)
|
|
|
|
|
|
int fileCount = _bezierHandles.Result.Count;
|
2026-09-29 18:09:08 +08:00
|
|
|
|
string[] fileNames = new string[fileCount];
|
|
|
|
|
|
string[] fileTexts = new string[fileCount];
|
|
|
|
|
|
for (int i = 0; i < fileCount; i++)
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-10-09 20:20:14 +08:00
|
|
|
|
fileNames[i] = _bezierHandles.Result[i].name;
|
|
|
|
|
|
fileTexts[i] = _bezierHandles.Result[i].text;
|
2026-09-29 18:09:08 +08:00
|
|
|
|
}
|
2026-10-09 20:20:14 +08:00
|
|
|
|
onProgress?.Invoke(0.88f);
|
2026-09-29 18:09:08 +08:00
|
|
|
|
|
|
|
|
|
|
// ⭐ 座位镜像状态主线程读一次传入后台(后台线程不访问任何 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);
|
|
|
|
|
|
}
|
2026-10-09 20:20:14 +08:00
|
|
|
|
onProgress?.Invoke(1f);
|
2026-09-29 18:09:08 +08:00
|
|
|
|
|
|
|
|
|
|
// ⭐ 座位镜像状态提示:镜像未初始化(-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');
|
2026-02-03 11:35:19 +08:00
|
|
|
|
foreach (var line in lines)
|
|
|
|
|
|
{
|
2026-05-07 03:46:03 +08:00
|
|
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
|
|
|
|
continue;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
|
|
|
|
|
var parts = line.Split(';');
|
2026-05-07 03:46:03 +08:00
|
|
|
|
if (parts.Length < 2)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
BezierCurveData curve = new BezierCurveData();
|
|
|
|
|
|
curve.name = parts[0];
|
|
|
|
|
|
|
|
|
|
|
|
List<Vector2> points = new List<Vector2>();
|
|
|
|
|
|
for (int i = 1; i < parts.Length; i++)
|
|
|
|
|
|
{
|
2026-05-07 03:46:03 +08:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(parts[i]))
|
2026-09-28 02:32:57 +08:00
|
|
|
|
{
|
|
|
|
|
|
// ⭐ 单点格式错误只跳过该点并告警,不能让整条路线(乃至进房加载流程)中断
|
|
|
|
|
|
if (TryParseVector(parts[i], out var p))
|
|
|
|
|
|
{
|
|
|
|
|
|
points.Add(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-09-29 18:09:08 +08:00
|
|
|
|
warnings.Add($"[MonsterMoveLine] 坐标格式非法已跳过: '{parts[i]}'({fileNames[f]} 曲线 {curve.name})");
|
2026-09-28 02:32:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 03:46:03 +08:00
|
|
|
|
if (points.Count == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
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]);
|
|
|
|
|
|
|
2026-09-29 18:09:08 +08:00
|
|
|
|
// ⭐ 座位镜像:本端在上方座位时路线整条上下翻转(与 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-27 19:11:25 +08:00
|
|
|
|
|
2026-09-29 18:09:08 +08:00
|
|
|
|
result.Add(curve);
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-29 18:09:08 +08:00
|
|
|
|
return result;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 06:26:50 +08:00
|
|
|
|
public static async Task UnLoadBezierTxtAsync(string path)
|
|
|
|
|
|
{
|
2026-10-09 20:20:14 +08:00
|
|
|
|
// ⭐ 释放路线加载句柄(与鱼包句柄共同归零后,路线/鱼共用的大包才真正卸载)
|
|
|
|
|
|
if (_bezierHandles.IsValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
Addressables.Release(_bezierHandles);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 15:47:27 +08:00
|
|
|
|
// ⭐ 全量同步清空,不再按 currGameType 重查 Addressables 删除:
|
|
|
|
|
|
// 1. 卸载依赖 currGameType 有时序风险 —— 选房面板在玩家点选新玩法时就改了全局类型,
|
|
|
|
|
|
// 延迟执行的卸载(OnHide 补卸载/异常重跑)会按新玩法 txt 去删 → 旧玩法 Boss 路线全部残留;
|
|
|
|
|
|
// 甚至可能把新玩法刚加载的路线误删。
|
|
|
|
|
|
// 2. 原实现标签查询为空时静默 return,什么都清不掉。
|
|
|
|
|
|
// 卸载只发生在离房时,场上只有当前玩法的路线,直接全清最干净且无竞态。
|
|
|
|
|
|
ClearAllBezierCache();
|
2026-05-01 06:26:50 +08:00
|
|
|
|
|
|
|
|
|
|
Debug.Log("曲线卸载完成,剩余曲线数量:" + dic_BezierCurveDatas.Count);
|
2026-09-27 15:47:27 +08:00
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>全量清空路线缓存(曲线字典/列表 + 点数组缓存 + B样条节点缓存)</summary>
|
|
|
|
|
|
private static void ClearAllBezierCache()
|
|
|
|
|
|
{
|
|
|
|
|
|
dic_BezierCurveDatas.Clear();
|
|
|
|
|
|
curves.Clear();
|
|
|
|
|
|
_curvePointsArrays.Clear();
|
|
|
|
|
|
_curveKnotArrays.Clear();
|
2026-05-01 06:26:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 02:32:57 +08:00
|
|
|
|
private static bool TryParseVector(string s, out Vector2 v)
|
2025-12-26 11:15:56 +08:00
|
|
|
|
{
|
2026-09-28 02:32:57 +08:00
|
|
|
|
v = default;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
var xy = s.Split(',');
|
2026-09-28 02:32:57 +08:00
|
|
|
|
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;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public BezierCurveData bezierCurveData;
|
|
|
|
|
|
public float moveTime;
|
|
|
|
|
|
public float moveSpeed;
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 缓存相机和 UI 根 RectTransform,避免每帧每鱼调用
|
|
|
|
|
|
// UIManager.Instance.uiRoot.GetComponent<RectTransform>() 和 Camera.main
|
|
|
|
|
|
// 由 MonsterContral.OnEnable 同步赋值
|
|
|
|
|
|
public Camera cachedCamera;
|
|
|
|
|
|
public RectTransform cachedUirootRect;
|
|
|
|
|
|
|
2025-12-26 11:15:56 +08:00
|
|
|
|
public void Init(BezierCurveData bezierCurveData, float Time)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.bezierCurveData = bezierCurveData;
|
|
|
|
|
|
this.moveTime = Time;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
Debug.Log($"初始化曲线 {this.bezierCurveData.name} ,移动时间 {moveTime}");
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
2026-03-31 18:30:49 +08:00
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
/// <summary>
|
2026-09-18 08:15:08 +08:00
|
|
|
|
/// 主线程零分配计算曲线点(⭐ B样条 de Boor 算法;原贝塞尔 De Casteljau 已注释保留备查,方法名保留兼容调用处)。
|
|
|
|
|
|
/// ⭐ 钳位(clamped)均匀三次 B样条:
|
|
|
|
|
|
/// - 节点向量首尾各重复 p+1 个 0/1 → 曲线严格过首控制点(start)和末控制点(end),
|
|
|
|
|
|
/// 鱼的出入屏位置与路线数据一致
|
|
|
|
|
|
/// - 中间控制点只被逼近不再穿过(与贝塞尔的本质区别)
|
|
|
|
|
|
/// - 控制点总数 ≤ 4 时钳位B样条数学上退化为同阶贝塞尔,短路线行为天然兼容
|
|
|
|
|
|
/// - 度数 p = min(3, N-1):仅 start+end 两点时退化为直线段
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// ⭐ 优化说明(原 Job 方案的负优化问题):
|
|
|
|
|
|
/// 1. 原 GetBezierPointJob 每次调用 new List + 2 个 NativeArray(TempJob) + ToArray,
|
|
|
|
|
|
/// 每条鱼每帧产生 3 次堆分配,N 条鱼 = 3N 次 GC 分配/帧
|
|
|
|
|
|
/// 2. 原 Job.Schedule() 后立即 Complete(),Job 实际单线程执行,
|
|
|
|
|
|
/// 调度开销 > 计算收益(4-7 个控制点的计算量极小)
|
2026-09-18 08:15:08 +08:00
|
|
|
|
/// 3. 现方案:曲线点数组+节点向量静态缓存(共享),主线程直接计算,零 GC 分配
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// 4. Camera/RectTransform 由调用方缓存,避免每帧每鱼重复获取
|
2026-02-03 11:35:19 +08:00
|
|
|
|
/// </summary>
|
2026-03-31 18:30:49 +08:00
|
|
|
|
public Vector2 GetBezierPointJob(float t)
|
2025-12-26 11:15:56 +08:00
|
|
|
|
{
|
2026-07-28 10:59:08 +08:00
|
|
|
|
// ⭐ 防止除零:moveTime<=0 时直接返回起点,避免 NaN
|
|
|
|
|
|
if (moveTime <= 0f)
|
|
|
|
|
|
return bezierCurveData.start;
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
if (t > moveTime)
|
|
|
|
|
|
t = moveTime;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
t = t / moveTime;
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 取静态缓存的曲线点数组(所有鱼共享,零分配)
|
|
|
|
|
|
if (!_curvePointsArrays.TryGetValue(bezierCurveData.name, out var pts))
|
2025-12-26 11:15:56 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// 兜底:缓存未命中时实时构建(理论上不应发生,加载时已构建)
|
|
|
|
|
|
BuildCurvePointsArray(bezierCurveData);
|
|
|
|
|
|
pts = _curvePointsArrays[bezierCurveData.name];
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
2026-08-04 14:52:22 +08:00
|
|
|
|
|
2026-09-18 08:15:08 +08:00
|
|
|
|
// ⭐ 取静态缓存的 B样条节点向量(加载时与点数组同步构建)
|
|
|
|
|
|
if (!_curveKnotArrays.TryGetValue(bezierCurveData.name, out var knots))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 兜底:缓存未命中时实时构建
|
|
|
|
|
|
knots = BuildKnotVector(pts.Length);
|
|
|
|
|
|
_curveKnotArrays[bezierCurveData.name] = knots;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 复用线程静态缓冲区(主线程单线程执行,安全复用)
|
|
|
|
|
|
if (_tempPoints == null || _tempPoints.Length < pts.Length)
|
|
|
|
|
|
_tempPoints = new Vector2[pts.Length];
|
|
|
|
|
|
Array.Copy(pts, _tempPoints, pts.Length);
|
|
|
|
|
|
|
2026-09-18 08:15:08 +08:00
|
|
|
|
int count = pts.Length; // 控制点总数 N = start + 控制点 + end
|
|
|
|
|
|
int p = Mathf.Min(3, count - 1); // ⭐ B样条度数:默认三次,控制点不足时自动降阶
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 canvasPos;
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 钳位B样条端点:曲线严格过首末控制点;de Boor 在端点节点区间退化,直接取端点等价且更稳
|
|
|
|
|
|
if (t <= 0f)
|
2026-08-04 14:52:22 +08:00
|
|
|
|
{
|
2026-09-18 08:15:08 +08:00
|
|
|
|
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];
|
|
|
|
|
|
// ====================================================================
|
2026-08-04 14:52:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 用缓存的 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);
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
2026-08-04 14:52:22 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>主线程复用的临时计算缓冲(避免每帧每鱼 new 数组)</summary>
|
|
|
|
|
|
[ThreadStatic]
|
|
|
|
|
|
private static Vector2[] _tempPoints;
|
2025-12-26 11:15:56 +08:00
|
|
|
|
}
|
2026-03-31 18:30:49 +08:00
|
|
|
|
#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
|