1
This commit is contained in:
Binary file not shown.
@@ -22,9 +22,9 @@ using UnityEngine;
|
||||
using static MonsterMoveLine;
|
||||
|
||||
/// <summary>
|
||||
/// 鱼实体基类:封装状态机(移动/待机)、贝塞尔曲线路径跟随、动画播放、死亡逻辑。
|
||||
/// 具体鱼类型(Boss等)通过继承重写虚方法扩展行为。
|
||||
/// </summary>
|
||||
/// 鱼实体基类:封装状态机(移动/待机)、贝塞尔曲线路径跟随、动画播放、死亡逻辑。
|
||||
/// 具体鱼类型(Boss等)通过继承重写虚方法扩展行为。
|
||||
/// </summary>
|
||||
public class MonsterContral : MonoBehaviour
|
||||
{
|
||||
// ----------------------------------------------------------------
|
||||
@@ -103,9 +103,59 @@ public class MonsterContral : MonoBehaviour
|
||||
/// <summary>
|
||||
/// 前进方向轴,可选 "X" / "-X" / "Y" / "-Y" / "Z" / "-Z"。
|
||||
/// 为空时默认使用 up(2D)或 forward(3D)。
|
||||
/// ⭐ 运行时不应修改;如需修改请调用 RefreshFacingAxis() 重新解析。
|
||||
/// </summary>
|
||||
public string forwordXYZ;
|
||||
|
||||
/// <summary>
|
||||
/// forwordXYZ 预解析后的朝向轴枚举(OnEnable 时解析)。
|
||||
/// 避免 ApplyFacingDirection 每帧每鱼做 string.IsNullOrEmpty + switch(string)。
|
||||
/// </summary>
|
||||
private FacingAxis _facingAxis = FacingAxis.None;
|
||||
|
||||
/// <summary>朝向轴枚举:对应 forwordXYZ 的 7 种取值</summary>
|
||||
private enum FacingAxis
|
||||
{
|
||||
None, // "" / null
|
||||
X,
|
||||
NegX, // "-X"
|
||||
Y,
|
||||
NegY, // "-Y"
|
||||
Z,
|
||||
NegZ // "-Z"
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 forwordXYZ 字符串解析为枚举。OnEnable 调用一次,运行时直接用枚举 switch。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存 yzID 前3字符前缀。Init 时调用一次(普通鱼在 base.Init,Boss 在自己 Init)。
|
||||
/// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
|
||||
/// </summary>
|
||||
protected void SetYzPrefix(string id)
|
||||
{
|
||||
_yzPrefix = (!string.IsNullOrEmpty(id) && id.Length >= 3) ? id.Substring(0, 3) : "";
|
||||
}
|
||||
|
||||
/// <summary>鱼类型枚举(缓存,避免反复类型转换)</summary>
|
||||
public FishTypeEnum fishenum;
|
||||
|
||||
@@ -118,6 +168,29 @@ public class MonsterContral : MonoBehaviour
|
||||
/// <summary>所属鱼阵 ID(空字符串表示普通鱼)</summary>
|
||||
public string yzID = "";
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// ⭐ 缓存优化字段
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/// <summary>缓存的子物体(朝向控制用),避免每帧 transform.GetChild(0)</summary>
|
||||
private Transform _cachedChild;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// ⭐ 鱼阵 700/701 直线移动缓存
|
||||
// 同一鱼阵起点相同,Init 时一次性算好世界坐标 + 方向系数,
|
||||
// UpdateYZMovement 每帧直接用,避免每帧每鱼 CanvasToWorld + 条件判断。
|
||||
// ----------------------------------------------------------------
|
||||
private bool _isYzLineMove; // 是否为 700/701 直线阵
|
||||
private Vector3 _yzStartWorld; // 直线阵起点世界坐标
|
||||
private float _yzLineDir; // 直线阵方向系数 (-1 向上 / +1 向下)
|
||||
private bool _yzLineInited; // 直线阵缓存是否已初始化
|
||||
|
||||
/// <summary>
|
||||
/// yzID 前3字符前缀缓存(Init 时 Substring 一次)。
|
||||
/// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
|
||||
/// </summary>
|
||||
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<RectTransform>() 和 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
|
||||
/// <summary>
|
||||
/// 每帧更新鱼的位置(贝塞尔曲线插值)和朝向。
|
||||
/// 鱼阵(isyz==true)按阵型 ID 前缀分支,普通鱼走统一逻辑。
|
||||
/// ⭐ 性能优化(零 GC + 缓存):
|
||||
/// - 贝塞尔求值:静态 Vector2[] 缓存 + ThreadStatic 临时缓冲,零 GC 分配
|
||||
/// - 朝向:forwordXYZ 预解析为枚举,避免每帧 string switch
|
||||
/// - 子物体:OnEnable 缓存 _cachedChild,避免每帧 transform.GetChild(0)
|
||||
/// - 鱼阵 700/701:Init 预计算起点世界坐标 + 方向系数
|
||||
/// - yzID 前缀:Init 缓存 _yzPrefix,避免每帧 Substring
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预计算 700/701 直线阵的世界起点 + 方向系数。
|
||||
/// ⭐ 优化:原 UpdateYZMovement 每帧每鱼 CanvasToWorld(start) + 判断 goUp,
|
||||
/// 现在 Init 一次性算好,运行时直接用缓存值。
|
||||
/// 非直线阵(702/704/710 等)标记 _isYzLineMove=false 跳过。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>鱼阵移动:按 yzID 前缀选择运动方程。</summary>
|
||||
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
|
||||
/// <param name="nextPos">下一帧目标位置</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<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[]>();
|
||||
[Serializable]
|
||||
public class BezierCurveData
|
||||
{
|
||||
@@ -62,9 +68,29 @@ public class MonsterMoveLine
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static async Task LoadBezierTxtAsync(string path)
|
||||
{
|
||||
List<string> keys = new List<string>();
|
||||
@@ -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<RectTransform>() 和 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 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 由调用方缓存,避免每帧每鱼重复获取
|
||||
/// </summary>
|
||||
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<Vector2> pts = new List<Vector2>();
|
||||
pts.Add(bezierCurveData.start);
|
||||
pts.AddRange(bezierCurveData.controlPoints);
|
||||
pts.Add(bezierCurveData.end);
|
||||
|
||||
var controlPoints = new NativeArray<Vector2>(pts.ToArray(), Allocator.TempJob);
|
||||
var result = new NativeArray<Vector2>(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<RectTransform>(), Camera.main);
|
||||
|
||||
controlPoints.Dispose();
|
||||
result.Dispose();
|
||||
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
// Job 结构体
|
||||
[BurstCompile]
|
||||
public struct BSplineJob : IJob
|
||||
{
|
||||
[ReadOnly] public NativeArray<Vector2> controlPoints;
|
||||
public float t;
|
||||
public NativeArray<Vector2> result;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
var temp = new NativeArray<Vector2>(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<RectTransform>();
|
||||
|
||||
return CoordinateTransformation.CanvasToWorld(canvasPos, rect, cam);
|
||||
}
|
||||
|
||||
/// <summary>主线程复用的临时计算缓冲(避免每帧每鱼 new 数组)</summary>
|
||||
[ThreadStatic]
|
||||
private static Vector2[] _tempPoints;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Assets.MyGame.Scripts.Net.APIRequest.Params
|
||||
public class LoginByPasswordParam : RequestParamBase
|
||||
{
|
||||
/// <summary>账号(手机号/用户名)</summary>
|
||||
public string account;
|
||||
public string mobile;
|
||||
/// <summary>密码</summary>
|
||||
public string password;
|
||||
}
|
||||
|
||||
@@ -805,6 +805,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
// 先创建等待信号(在发送请求前创建,避免响应太快丢失)
|
||||
_leaveRoomResponseTcs = new TaskCompletionSource<bool>();
|
||||
|
||||
// ⭐ 立即记录当前面板类型,不依赖 socket 返回(超时强制退出时也能正确关闭)
|
||||
_leavingGamePanelType = this.PanelType;
|
||||
|
||||
// 发送离房请求
|
||||
WebSocketSendList.LeaveRoom(
|
||||
GameManager.Instance.gameData.currRoomData.roomId,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user