This commit is contained in:
2026-08-04 14:52:22 +08:00
parent 91c0a2d814
commit 531dcae391
8 changed files with 319 additions and 209 deletions
@@ -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,