diff --git a/Assets/AddressableAssetsData/Windows/addressables_content_state.bin b/Assets/AddressableAssetsData/Windows/addressables_content_state.bin
index c4c6b185f..d08a3ff86 100644
Binary files a/Assets/AddressableAssetsData/Windows/addressables_content_state.bin and b/Assets/AddressableAssetsData/Windows/addressables_content_state.bin differ
diff --git a/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs b/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs
index 29579d61b..76b7c8482 100644
--- a/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs
+++ b/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs
@@ -22,9 +22,9 @@ using UnityEngine;
using static MonsterMoveLine;
///
-/// 鱼实体基类:封装状态机(移动/待机)、贝塞尔曲线路径跟随、动画播放、死亡逻辑。
-/// 具体鱼类型(Boss等)通过继承重写虚方法扩展行为。
-///
+ /// 鱼实体基类:封装状态机(移动/待机)、贝塞尔曲线路径跟随、动画播放、死亡逻辑。
+ /// 具体鱼类型(Boss等)通过继承重写虚方法扩展行为。
+ ///
public class MonsterContral : MonoBehaviour
{
// ----------------------------------------------------------------
@@ -103,9 +103,59 @@ public class MonsterContral : MonoBehaviour
///
/// 前进方向轴,可选 "X" / "-X" / "Y" / "-Y" / "Z" / "-Z"。
/// 为空时默认使用 up(2D)或 forward(3D)。
+ /// ⭐ 运行时不应修改;如需修改请调用 RefreshFacingAxis() 重新解析。
///
public string forwordXYZ;
+ ///
+ /// forwordXYZ 预解析后的朝向轴枚举(OnEnable 时解析)。
+ /// 避免 ApplyFacingDirection 每帧每鱼做 string.IsNullOrEmpty + switch(string)。
+ ///
+ private FacingAxis _facingAxis = FacingAxis.None;
+
+ /// 朝向轴枚举:对应 forwordXYZ 的 7 种取值
+ private enum FacingAxis
+ {
+ None, // "" / null
+ X,
+ NegX, // "-X"
+ Y,
+ NegY, // "-Y"
+ Z,
+ NegZ // "-Z"
+ }
+
+ ///
+ /// 将 forwordXYZ 字符串解析为枚举。OnEnable 调用一次,运行时直接用枚举 switch。
+ ///
+ private void RefreshFacingAxis()
+ {
+ if (string.IsNullOrEmpty(forwordXYZ))
+ {
+ _facingAxis = FacingAxis.None;
+ return;
+ }
+ switch (forwordXYZ)
+ {
+ case "X": _facingAxis = FacingAxis.X; break;
+ case "-X": _facingAxis = FacingAxis.NegX; break;
+ case "Y": _facingAxis = FacingAxis.Y; break;
+ case "-Y": _facingAxis = FacingAxis.NegY; break;
+ case "Z": _facingAxis = FacingAxis.Z; break;
+ case "-Z": _facingAxis = FacingAxis.NegZ; break;
+ default: _facingAxis = FacingAxis.None; break;
+ }
+ }
+
+ ///
+ /// 缓存 yzID 前3字符前缀。Init 时调用一次(普通鱼在 base.Init,Boss 在自己 Init)。
+ /// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
+ ///
+ protected void SetYzPrefix(string id)
+ {
+ _yzPrefix = (!string.IsNullOrEmpty(id) && id.Length >= 3) ? id.Substring(0, 3) : "";
+ }
+
/// 鱼类型枚举(缓存,避免反复类型转换)
public FishTypeEnum fishenum;
@@ -118,6 +168,29 @@ public class MonsterContral : MonoBehaviour
/// 所属鱼阵 ID(空字符串表示普通鱼)
public string yzID = "";
+ // ----------------------------------------------------------------
+ // ⭐ 缓存优化字段
+ // ----------------------------------------------------------------
+
+ /// 缓存的子物体(朝向控制用),避免每帧 transform.GetChild(0)
+ private Transform _cachedChild;
+
+ // ----------------------------------------------------------------
+ // ⭐ 鱼阵 700/701 直线移动缓存
+ // 同一鱼阵起点相同,Init 时一次性算好世界坐标 + 方向系数,
+ // UpdateYZMovement 每帧直接用,避免每帧每鱼 CanvasToWorld + 条件判断。
+ // ----------------------------------------------------------------
+ private bool _isYzLineMove; // 是否为 700/701 直线阵
+ private Vector3 _yzStartWorld; // 直线阵起点世界坐标
+ private float _yzLineDir; // 直线阵方向系数 (-1 向上 / +1 向下)
+ private bool _yzLineInited; // 直线阵缓存是否已初始化
+
+ ///
+ /// yzID 前3字符前缀缓存(Init 时 Substring 一次)。
+ /// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
+ ///
+ private string _yzPrefix = "";
+
// ----------------------------------------------------------------
// 私有字段
// ----------------------------------------------------------------
@@ -249,6 +322,25 @@ public class MonsterContral : MonoBehaviour
_isPlayingCreateVideo = false;
InitAnimList();
+ // ⭐ 预解析 forwordXYZ 为枚举,避免 ApplyFacingDirection 每帧每鱼 string switch
+ RefreshFacingAxis();
+
+ // ⭐ 同步缓存到 moveLine,避免 GetBezierPointJob 内每帧每鱼重复查询
+ // UIManager.Instance.uiRoot.GetComponent() 和 Camera.main
+ monsterData.moveLine.cachedCamera = mainCamera;
+ monsterData.moveLine.cachedUirootRect = uirootRect;
+
+ // ⭐ 缓存子物体引用(朝向控制用),避免 ApplyFacingDirection 每帧 transform.GetChild(0)
+ // 池回收的鱼 transform 可能更换子层级,每次 OnEnable 重新缓存
+ if (transform.childCount > 0)
+ {
+ _cachedChild = transform.GetChild(0);
+ }
+ else
+ {
+ _cachedChild = null;
+ }
+
// ⭐ 池回收的鱼(isInitialized=false)不立即 ChangeState(move),
// 避免 OnMoveUpdata 用旧 moveTime/moveLine 误触发 Id_DieFish_NotFX
// Init 调用后会设置 isInitialized=true 并 ChangeState(move)
@@ -322,8 +414,11 @@ public class MonsterContral : MonoBehaviour
isyz = isyuzhen;
this.yzID = yzID;
+ // ⭐ 缓存 yzID 前3字符前缀,避免 UpdateYZMovement 每帧每鱼 Substring 产生 GC
+ SetYzPrefix(yzID);
+
// 鱼阵特殊处理:700 系扇形阵根据起点 y 值翻转朝向
- if (isyuzhen && yzID.Length >= 3 && yzID.Substring(0, 3) == "700")
+ if (isyuzhen && _yzPrefix == "700")
{
if (bezierData.start.y < 0)
{
@@ -333,6 +428,10 @@ public class MonsterContral : MonoBehaviour
}
}
+ // ⭐ 预计算 700/701 直线阵的世界起点 + 方向系数
+ // 原逻辑每帧每鱼 CanvasToWorld(start) + 判断 goUp,现 Init 一次性算好
+ CacheYzLineMoveData(bezierData);
+
fishenum = (FishTypeEnum)monsterData.fishTypeConfig.fishType;
// isMove=false 的鱼不需要移动路线,直接归位到 (0,0,0)
@@ -413,6 +512,12 @@ public class MonsterContral : MonoBehaviour
///
/// 每帧更新鱼的位置(贝塞尔曲线插值)和朝向。
/// 鱼阵(isyz==true)按阵型 ID 前缀分支,普通鱼走统一逻辑。
+ /// ⭐ 性能优化(零 GC + 缓存):
+ /// - 贝塞尔求值:静态 Vector2[] 缓存 + ThreadStatic 临时缓冲,零 GC 分配
+ /// - 朝向:forwordXYZ 预解析为枚举,避免每帧 string switch
+ /// - 子物体:OnEnable 缓存 _cachedChild,避免每帧 transform.GetChild(0)
+ /// - 鱼阵 700/701:Init 预计算起点世界坐标 + 方向系数
+ /// - yzID 前缀:Init 缓存 _yzPrefix,避免每帧 Substring
///
public virtual void OnMoveUpdata()
{
@@ -429,6 +534,18 @@ public class MonsterContral : MonoBehaviour
moveTime += Time.deltaTime;
+ // ⭐ 终点检测:每帧执行(轻量)
+ if (moveTime >= monsterData.moveLine.moveTime)
+ {
+ // ⭐ 路径走完后立即停止移动,防止 Id_DieFish_NotFX 每帧重复触发
+ // (野鱼 guid 已被覆盖时,Id_DieFish_NotFX 会 NOT_FOUND,但 moveTime 一直 >= moveTime,
+ // 不停止的话每帧都会调用,产生海量 NOT_FOUND 日志)
+ isMove = false;
+ CreateFishManager.Instance.Id_DieFish_NotFX(guid);
+ return;
+ }
+
+ // ⭐ 每帧执行重计算(已无 GC 压力,分帧会导致鱼体一卡一卡)
if (isyz)
{
UpdateYZMovement();
@@ -437,25 +554,53 @@ public class MonsterContral : MonoBehaviour
{
UpdateNormalMovement();
}
- if (moveTime>=monsterData.moveLine.moveTime)
- {
- // ⭐ 路径走完后立即停止移动,防止 Id_DieFish_NotFX 每帧重复触发
- // (野鱼 guid 已被覆盖时,Id_DieFish_NotFX 会 NOT_FOUND,但 moveTime 一直 >= moveTime,
- // 不停止的话每帧都会调用,产生海量 NOT_FOUND 日志)
- isMove = false;
- CreateFishManager.Instance.Id_DieFish_NotFX(guid);
- }
+ }
+
+ ///
+ /// 预计算 700/701 直线阵的世界起点 + 方向系数。
+ /// ⭐ 优化:原 UpdateYZMovement 每帧每鱼 CanvasToWorld(start) + 判断 goUp,
+ /// 现在 Init 一次性算好,运行时直接用缓存值。
+ /// 非直线阵(702/704/710 等)标记 _isYzLineMove=false 跳过。
+ ///
+ private void CacheYzLineMoveData(MonsterMoveLine.BezierCurveData bezierData)
+ {
+ _isYzLineMove = false;
+ _yzLineInited = false;
+
+ if (!isyz || string.IsNullOrEmpty(_yzPrefix)) return;
+
+ if (_yzPrefix != "700" && _yzPrefix != "701") return;
+
+ // 世界起点(CanvasToWorld 已在 OnEnable 缓存 uirootRect/mainCamera)
+ _yzStartWorld = CoordinateTransformation.CanvasToWorld(
+ bezierData.start, uirootRect, mainCamera);
+
+ // 700:根据起点 y 值决定正方向;701:固定向上(dir=-1)
+ bool goUp = (_yzPrefix == "701") || (bezierData.start.y > 0);
+ _yzLineDir = goUp ? -1f : 1f;
+
+ _isYzLineMove = true;
+ _yzLineInited = true;
}
/// 鱼阵移动:按 yzID 前缀选择运动方程。
private void UpdateYZMovement()
{
- if (yzID.Length < 3) return;
- string prefix = yzID.Substring(0, 3);
-
- switch (prefix)
+ // ⭐ 700/701 直线阵:使用预计算缓存,零 CanvasToWorld 调用
+ if (_isYzLineMove && _yzLineInited)
{
- // 700 / 701:直线向前移动(沿 up 方向)
+ Vector2 pos = _yzStartWorld + (Vector3)(_yzLineDir * transform.up * moveTime
+ * monsterData.moveLine.moveTime);
+ transform.position = pos;
+ return;
+ }
+
+ // ⭐ 用 Init 时缓存的 _yzPrefix,避免每帧每鱼 yzID.Substring(0,3) GC 分配
+ if (string.IsNullOrEmpty(_yzPrefix)) return;
+
+ switch (_yzPrefix)
+ {
+ // 700 / 701:直线向前移动(沿 up 方向)—— 兜底分支,正常走缓存路径
case "700":
case "701":
{
@@ -463,7 +608,7 @@ public class MonsterContral : MonoBehaviour
monsterData.moveLine.bezierCurveData.start, uirootRect, mainCamera);
// 700:根据起点 y 值决定正方向;701:固定向上
- bool goUp = (prefix == "701") || (monsterData.moveLine.bezierCurveData.start.y > 0);
+ bool goUp = (_yzPrefix == "701") || (monsterData.moveLine.bezierCurveData.start.y > 0);
float dir = goUp ? -1f : 1f;
Vector2 pos = startWorld + (Vector3)(dir * transform.up * moveTime
@@ -509,7 +654,16 @@ public class MonsterContral : MonoBehaviour
/// 下一帧目标位置
private void ApplyFacingDirection(Vector2 nextPos)
{
- Transform child = transform.GetChild(0);
+ // ⭐ 使用 OnEnable 缓存的 _cachedChild,避免每帧 transform.GetChild(0) 调用开销
+ // 兜底:缓存丢失时重新获取一次(理论上不会发生)
+ Transform child = _cachedChild;
+ if (child == null)
+ {
+ if (transform.childCount == 0) return;
+ child = transform.GetChild(0);
+ _cachedChild = child;
+ }
+
Vector3 localPos = Vector3.zero;
localPos.z = depthOffset;
child.localPosition = localPos;
@@ -518,8 +672,11 @@ public class MonsterContral : MonoBehaviour
Vector2 toNext = nextPos - (Vector2)transform.position;
if (toNext.sqrMagnitude < 0.0001f) return;
+ Vector2 dirNorm = toNext.normalized; // ⭐ 缓存归一化向量,避免分支内重复计算
+
+ // ⭐ 用预解析的枚举 switch,避免每帧每鱼 string 比较
// 处理未指定朝向轴的情况(默认行为)
- if (string.IsNullOrEmpty(forwordXYZ))
+ if (_facingAxis == FacingAxis.None)
{
if (!is3D)
{
@@ -528,7 +685,7 @@ public class MonsterContral : MonoBehaviour
}
else
{
- transform.rotation = Quaternion.LookRotation(toNext.normalized, Vector3.up);
+ transform.rotation = Quaternion.LookRotation(dirNorm, Vector3.up);
}
return;
}
@@ -536,99 +693,77 @@ public class MonsterContral : MonoBehaviour
// 是否为子物体单独控制朝向和深度
if (ischild0)
{
-
-
- //// 父物体朝向
- switch (forwordXYZ)
+ // ⭐ 全部使用缓存 child,不再 transform.GetChild(0)
+ switch (_facingAxis)
{
- case "X":
+ case FacingAxis.X:
{
- transform.GetChild(0).right = toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
-
+ child.right = dirNorm;
+ var vv = child.eulerAngles;
vv.x = rotationPY;
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
break;
- case "-X":
+ case FacingAxis.NegX:
{
- transform.GetChild(0).right = -toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
+ child.right = -dirNorm;
+ var vv = child.eulerAngles;
vv.x = -rotationPY;
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
-
break;
- case "Y":
+ case FacingAxis.Y:
{
-
- transform.GetChild(0).up = toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
+ child.up = dirNorm;
+ var vv = child.eulerAngles;
vv.y = rotationPY;
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
break;
- case "-Y":
+ case FacingAxis.NegY:
{
- transform.GetChild(0).up = -toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
+ child.up = -dirNorm;
+ var vv = child.eulerAngles;
vv.y = -rotationPY;
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
-
break;
- case "Z":
+ case FacingAxis.Z:
{
- transform.GetChild(0).forward = toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
+ child.forward = dirNorm;
+ var vv = child.eulerAngles;
if (isNot1)
{
vv.z = rotationPY;
-
}
else
{
vv.z = rotationPY * (vv.y < 180 ? -1 : 1);
-
}
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
-
break;
- case "-Z":
+ case FacingAxis.NegZ:
{
- transform.GetChild(0).forward = -toNext.normalized;
- var vv = transform.GetChild(0).eulerAngles;
+ child.forward = -dirNorm;
+ var vv = child.eulerAngles;
vv.z = -rotationPY * (vv.y < 180 ? -1 : 1);
- transform.GetChild(0).eulerAngles = vv;
+ child.eulerAngles = vv;
}
-
break;
}
-
- //// 子物体深度偏移:方向正 → 偏移正,方向负 → 偏移负
- //switch (forwordXYZ)
- //{
- // case "X": localPos.z = depthOffset; break;
- // case "-X": localPos.z = -depthOffset; break;
- // case "Y": localPos.x = depthOffset; break;
- // case "-Y": localPos.x = -depthOffset; break;
- // case "Z": localPos.x = depthOffset; break;
- // case "-Z": localPos.x = -depthOffset; break;
- //}
-
}
else
{
// 控制父物体自身朝向
- switch (forwordXYZ)
+ switch (_facingAxis)
{
- case "X": transform.right = toNext.normalized; break;
- case "-X": transform.right = -toNext.normalized; break;
- case "Y": transform.up = toNext.normalized; break;
- case "-Y": transform.up = -toNext.normalized; break;
- case "Z": transform.forward = toNext.normalized; break;
- case "-Z": transform.forward = -toNext.normalized; break;
+ case FacingAxis.X: transform.right = dirNorm; break;
+ case FacingAxis.NegX: transform.right = -dirNorm; break;
+ case FacingAxis.Y: transform.up = dirNorm; break;
+ case FacingAxis.NegY: transform.up = -dirNorm; break;
+ case FacingAxis.Z: transform.forward = dirNorm; break;
+ case FacingAxis.NegZ: transform.forward = -dirNorm; break;
}
}
}
diff --git a/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs b/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs
index e984d1e00..71035c2e3 100644
--- a/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs
+++ b/Assets/MyGame/Scripts/Characters/Monster/MonsterControl_BossBase.cs
@@ -115,6 +115,10 @@ public class MonsterControl_BossBase : MonsterContral
isyz = isyuzhen;
this.yzID = yzID;
+ // ⭐ 缓存 yzID 前缀(Boss 未调 base.Init,需单独缓存)
+ // 避免 UpdateYZMovement 里 _yzPrefix 为空导致鱼阵 Boss 不移动
+ SetYzPrefix(yzID);
+
// ⭐ 创建视频处理:配置了 createVideoClip 时通过 GameMainPanel 的播放器播放
// 播放期间 OnMoveUpdata 会跳过移动,视频结束后才开始走路线
_createVideoDuration = 0f;
diff --git a/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs b/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs
index 3fab45788..b6648bfe2 100644
--- a/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs
+++ b/Assets/MyGame/Scripts/Characters/Monster/MonsterMoveLine.cs
@@ -6,15 +6,21 @@ using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
-using Unity.Collections;
-using Unity.Jobs;
-using Unity.Burst;
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();
[Serializable]
public class BezierCurveData
{
@@ -62,9 +68,29 @@ public class MonsterMoveLine
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;
+ }
+
public static async Task LoadBezierTxtAsync(string path)
{
List keys = new List();
@@ -119,7 +145,11 @@ public class MonsterMoveLine
// 不覆盖旧数据
if (!dic_BezierCurveDatas.ContainsKey(curve.name))
+ {
dic_BezierCurveDatas.Add(curve.name, curve);
+ // ⭐ 同步构建点数组缓存(批量加载路径)
+ BuildCurvePointsArray(curve);
+ }
}
}
@@ -167,6 +197,9 @@ public class MonsterMoveLine
dic_BezierCurveDatas.Remove(curveName);
}
+ // ⭐ 同步清理点数组缓存
+ _curvePointsArrays.Remove(curveName);
+
// 列表移除所有同名曲线
curves.RemoveAll(c => c.name == curveName);
}
@@ -187,6 +220,12 @@ public class MonsterMoveLine
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;
@@ -195,7 +234,14 @@ public class MonsterMoveLine
}
///
- /// 使用 Job 计算 B样条点
+ /// 主线程零分配计算贝塞尔曲线点(De Casteljau 算法)。
+ /// ⭐ 优化说明(原 Job 方案的负优化问题):
+ /// 1. 原 GetBezierPointJob 每次调用 new List + 2 个 NativeArray(TempJob) + ToArray,
+ /// 每条鱼每帧产生 3 次堆分配,N 条鱼 = 3N 次 GC 分配/帧
+ /// 2. 原 Job.Schedule() 后立即 Complete(),Job 实际单线程执行,
+ /// 调度开销 > 计算收益(4-7 个控制点的计算量极小)
+ /// 3. 现方案:曲线点数组静态缓存(共享),主线程直接 Lerp 计算,零 GC 分配
+ /// 4. Camera/RectTransform 由调用方缓存,避免每帧每鱼重复获取
///
public Vector2 GetBezierPointJob(float t)
{
@@ -203,65 +249,46 @@ public class MonsterMoveLine
if (moveTime <= 0f)
return bezierCurveData.start;
- if (t>moveTime)
- {
- t=moveTime;
- }
+ if (t > moveTime)
+ t = moveTime;
t = t / moveTime;
- List pts = new List();
- pts.Add(bezierCurveData.start);
- pts.AddRange(bezierCurveData.controlPoints);
- pts.Add(bezierCurveData.end);
- var controlPoints = new NativeArray(pts.ToArray(), Allocator.TempJob);
- var result = new NativeArray(1, Allocator.TempJob);
-
- var job = new BSplineJob
+ // ⭐ 取静态缓存的曲线点数组(所有鱼共享,零分配)
+ if (!_curvePointsArrays.TryGetValue(bezierCurveData.name, out var pts))
{
- controlPoints = controlPoints,
- t = t,
- result = result
- };
-
- JobHandle handle = job.Schedule();
- handle.Complete();
-
- Vector2 worldPos = CoordinateTransformation.CanvasToWorld(result[0],
- UIManager.Instance.uiRoot.GetComponent(), Camera.main);
-
- controlPoints.Dispose();
- result.Dispose();
-
- return worldPos;
- }
-
- // Job 结构体
- [BurstCompile]
- public struct BSplineJob : IJob
- {
- [ReadOnly] public NativeArray controlPoints;
- public float t;
- public NativeArray result;
-
- public void Execute()
- {
- var temp = new NativeArray(controlPoints.Length, Allocator.Temp);
- controlPoints.CopyTo(temp);
-
- int count = temp.Length;
- while (count > 1)
- {
- for (int i = 0; i < count - 1; i++)
- {
- temp[i] = Vector2.Lerp(temp[i], temp[i + 1], t);
- }
- count--;
- }
-
- result[0] = temp[0];
- temp.Dispose();
+ // 兜底:缓存未命中时实时构建(理论上不应发生,加载时已构建)
+ BuildCurvePointsArray(bezierCurveData);
+ pts = _curvePointsArrays[bezierCurveData.name];
}
+
+ // ⭐ 复用线程静态缓冲区(主线程单线程执行,安全复用)
+ 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--;
+ }
+
+ Vector2 canvasPos = _tempPoints[0];
+
+ // ⭐ 用缓存的 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
diff --git a/Assets/MyGame/Scripts/Net/APIRequest/Params/SupplementParams.cs b/Assets/MyGame/Scripts/Net/APIRequest/Params/SupplementParams.cs
index e65aded28..457fd827d 100644
--- a/Assets/MyGame/Scripts/Net/APIRequest/Params/SupplementParams.cs
+++ b/Assets/MyGame/Scripts/Net/APIRequest/Params/SupplementParams.cs
@@ -13,7 +13,7 @@ namespace Assets.MyGame.Scripts.Net.APIRequest.Params
public class LoginByPasswordParam : RequestParamBase
{
/// 账号(手机号/用户名)
- public string account;
+ public string mobile;
/// 密码
public string password;
}
diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
index d6d75a76d..381c833ac 100644
--- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
+++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
@@ -805,6 +805,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 先创建等待信号(在发送请求前创建,避免响应太快丢失)
_leaveRoomResponseTcs = new TaskCompletionSource();
+ // ⭐ 立即记录当前面板类型,不依赖 socket 返回(超时强制退出时也能正确关闭)
+ _leavingGamePanelType = this.PanelType;
+
// 发送离房请求
WebSocketSendList.LeaveRoom(
GameManager.Instance.gameData.currRoomData.roomId,
diff --git a/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.cs b/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.cs
index 5dd35f6c0..abc3a22c3 100644
--- a/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.cs
+++ b/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.cs
@@ -1,7 +1,5 @@
-using Assets.MyGame.Scripts.Net;
+using Assets.MyGame.Scripts.Net;
using Assets.MyGame.Scripts.Net.APIRequest.Params;
-using System.Collections;
-using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -19,9 +17,6 @@ public class LoginEnterPanel : SubUIBase
private const string KEY_USER = "login_user";
private const string KEY_PASS = "login_pass";
- private string _realPassword = "";
- private Coroutine _maskCoroutine;
-
public override void Awake()
{
base.Awake();
@@ -30,17 +25,20 @@ public class LoginEnterPanel : SubUIBase
tMPInput_UserName.characterLimit = 11;
tMPInput_UserName.contentType = TMP_InputField.ContentType.IntegerNumber;
- // 密码:Standard 模式 + 延迟遮罩
- tMPInput_Password.contentType = TMP_InputField.ContentType.Standard;
- tMPInput_Password.onValueChanged.AddListener(OnPwdChanged);
+ // 密码:使用 TMP 内置 Password 模式,自动遮罩且无光标错位问题
+ tMPInput_Password.contentType = TMP_InputField.ContentType.Password;
+ // 清理 prefab 中 Text 子组件可能残留的零宽空格(\u200B),避免被 InputField 拾取为首字符
+ tMPInput_Password.text = "";
// 加载本地保存的账号密码
if (PlayerPrefs.HasKey(KEY_USER))
tMPInput_UserName.text = PlayerPrefs.GetString(KEY_USER);
if (PlayerPrefs.HasKey(KEY_PASS))
{
- _realPassword = PlayerPrefs.GetString(KEY_PASS);
- tMPInput_Password.SetTextWithoutNotify(new string('*', _realPassword.Length));
+ string saved = PlayerPrefs.GetString(KEY_PASS);
+ // 过滤掉可能被旧版遮罩逻辑污染的非法字符
+ saved = saved.Replace("\u200B", "").Replace("*", "");
+ tMPInput_Password.text = saved;
}
btn_Register.onClick.AddListener(() =>
@@ -55,14 +53,13 @@ public class LoginEnterPanel : SubUIBase
{
// 保存账号密码到本地
PlayerPrefs.SetString(KEY_USER, tMPInput_UserName.text);
- PlayerPrefs.SetString(KEY_PASS, _realPassword);
+ PlayerPrefs.SetString(KEY_PASS, tMPInput_Password.text);
PlayerPrefs.Save();
- //APIRequestList.Login();
- APIRequestList.LoginPhone(new LoginParam
+ APIRequestList.LoginByPassword(new LoginByPasswordParam
{
- mobile=tMPInput_UserName.text,
- code=_realPassword,
+ mobile = tMPInput_UserName.text,
+ password=tMPInput_Password.text,
}, token =>
{
WebSocketManager.Instance.Connect(SocketType.login);
@@ -85,60 +82,4 @@ public class LoginEnterPanel : SubUIBase
// });
});
}
- // Start is called before the first frame update
- void Start()
- {
-
- }
-
- // Update is called once per frame
- void Update()
- {
-
- }
-
- private void OnPwdChanged(string display)
- {
- bool isAdd = _realPassword.Length < display.Length;
-
- if (isAdd)
- {
- for (int i = _realPassword.Length; i < display.Length; i++)
- _realPassword += display[i];
- }
- else if (_realPassword.Length > display.Length)
- {
- _realPassword = _realPassword.Substring(0, display.Length);
- }
-
- int len = _realPassword.Length;
- if (isAdd && len > 0)
- {
- // 新增:只显示最后一个字符明文,其余为 *
- string masked = new string('*', len - 1) + _realPassword[len - 1];
- tMPInput_Password.SetTextWithoutNotify(masked);
- tMPInput_Password.caretPosition = len;
- MaskAfterDelay();
- }
- else
- {
- // 删除或无变化:全星
- tMPInput_Password.SetTextWithoutNotify(new string('*', len));
- tMPInput_Password.caretPosition = len;
- }
- }
-
- private void MaskAfterDelay()
- {
- if (_maskCoroutine != null) StopCoroutine(_maskCoroutine);
- _maskCoroutine = StartCoroutine(MaskRoutine());
- }
-
- private IEnumerator MaskRoutine()
- {
- yield return new WaitForSeconds(0.5f);
- if (string.IsNullOrEmpty(_realPassword)) yield break;
- tMPInput_Password.SetTextWithoutNotify(new string('*', _realPassword.Length));
- tMPInput_Password.caretPosition = _realPassword.Length;
- }
}
diff --git a/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.prefab b/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.prefab
index 9b27b1e46..79bba9322 100644
--- a/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.prefab
+++ b/Assets/MyGame/aa/UI/Login/SubPanel/LoginEnterPanel.prefab
@@ -766,7 +766,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_text: "\u200B"
+ m_text: ""
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 5d4a713fdd931db4c9ce49f068b54e0a, type: 2}
m_sharedMaterial: {fileID: 3593458263369068374, guid: 5d4a713fdd931db4c9ce49f068b54e0a,
@@ -1327,7 +1327,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_text: "\u200B"
+ m_text: ""
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 5d4a713fdd931db4c9ce49f068b54e0a, type: 2}
m_sharedMaterial: {fileID: 3593458263369068374, guid: 5d4a713fdd931db4c9ce49f068b54e0a,