This commit is contained in:
2026-09-17 22:57:46 +08:00
parent b808170da9
commit 3a27552995
62 changed files with 607 additions and 155 deletions
@@ -330,6 +330,9 @@ public class MonsterContral : MonoBehaviour
[Tooltip("反面补偿:掉头(跨左右中线)时绕鱼头轴z翻180-z,保持透视方向/背朝向一致。往返路线开;环形路线绕圈会跨中线两次导致跳变闪,环形路线的鱼关掉(全程连续转不翻面)")]
public bool useMirrorCompensate = true;
[Tooltip("Z轴侧倾按左右符号翻转(原始语义):向左→+rotationPY/向右→-rotationPY,纯符号切换无180°翻转。true=启用此模式(忽略反面补偿);false=走useMirrorCompensate的180°反面补偿")]
public bool useRollSignFlip = false;
[Tooltip("Boss路线奇偶翻转X轴:单数路线(左→右)保持正向,双数路线(右→左)X取-1镜像。默认开;特殊怪物不需要镜像时关闭")]
public bool useRouteFlipX = true;
@@ -389,6 +392,9 @@ public class MonsterContral : MonoBehaviour
isLockable = !needLockEvent;
// ⭐ 重置死亡标记:池回收复用的鱼重新激活时是活鱼
isDead = false;
// ⭐ 重置路线方向缓存:池复用会换路线,需重新由起终点判定
_routeDirXCached = false;
_routeDirX = 0;
fishenum = (FishTypeEnum)monsterData.fishTypeConfig.fishType;
uirootRect = UIManager.Instance.uiRoot.GetComponent<RectTransform>();
mainCamera = Camera.main;
@@ -696,8 +702,17 @@ public class MonsterContral : MonoBehaviour
// ⭐ 700/701 直线阵:使用预计算缓存,零 CanvasToWorld 调用
if (_isYzLineMove && _yzLineInited)
{
Vector2 pos = _yzStartWorld + (Vector3)(_yzLineDir * transform.up * moveTime
* monsterData.moveLine.moveTime);
// ⭐ 移动方向改用固定世界向量 (0,±1),不再用 transform.up:
// 加轴朝向(forwordXYZ)后鱼会被旋转,transform.up 随之改变,
// 原公式会把鱼带斜飞(隐含"鱼永不旋转"前提,现已不成立)
Vector2 lineDir = new Vector2(0f, _yzLineDir);
Vector2 pos = (Vector2)_yzStartWorld + lineDir * moveTime * monsterData.moveLine.moveTime;
// ⭐ 直线阵朝向:沿移动方向取前方一点,复用 ApplyFacingDirection 的轴逻辑
// (原直线阵从不调朝向,设置 forwordXYZ 无效——鱼保持预制体朝向平移)
if (fishenum != FishTypeEnum.Boss鱼)
ApplyFacingDirection(pos + lineDir);
// ⭐ 保留根节点当前 z(2D 小鱼 z 一直为 0,行为不变)
transform.position = new Vector3(pos.x, pos.y, transform.position.z);
return;
@@ -708,7 +723,7 @@ public class MonsterContral : MonoBehaviour
switch (_yzPrefix)
{
// 700 / 701:直线向前移动(沿 up 方向)—— 兜底分支,正常走缓存路径
// 700 / 701:直线向前移动—— 兜底分支,正常走缓存路径
case "700":
case "701":
{
@@ -719,8 +734,13 @@ public class MonsterContral : MonoBehaviour
bool goUp = (_yzPrefix == "701") || (monsterData.moveLine.bezierCurveData.start.y > 0);
float dir = goUp ? -1f : 1f;
Vector2 pos = startWorld + (Vector3)(dir * transform.up * moveTime
* monsterData.moveLine.moveTime);
// ⭐ 同缓存分支:方向用固定世界向量,与朝向旋转解耦
Vector2 lineDir = new Vector2(0f, dir);
Vector2 pos = (Vector2)startWorld + lineDir * moveTime * monsterData.moveLine.moveTime;
if (fishenum != FishTypeEnum.Boss鱼)
ApplyFacingDirection(pos + lineDir);
// ⭐ 保留根节点当前 z(2D 小鱼 z 一直为 0,行为不变)
transform.position = new Vector3(pos.x, pos.y, transform.position.z);
break;
@@ -759,6 +779,16 @@ public class MonsterContral : MonoBehaviour
}
public bool isRotate = true;
public bool isNot1 = false;
/// <summary>
/// 路线水平方向缓存:+1=左→右,-1=右→左,0=纯垂直。
/// 由贝塞尔曲线起终点一次判定(终点在起点右边=左→右),路线终身不变。
/// ⭐ 侧倾符号判断统一用它,替代每帧 dirNorm 分量:曲线中段波动会让 dirNorm 分量
/// 频繁穿0,符号每帧翻转导致侧倾 ±rotationPY 来回闪(±90跳)。
/// </summary>
private int _routeDirX = 0;
/// <summary>_routeDirX 是否已计算(ApplyFacingDirection 首次调用时懒算,OnEnable 重置供池复用)</summary>
private bool _routeDirXCached = false;
/// <summary>
/// 根据 forwordXYZ 字段将物体朝向目标点方向。
/// 为空时 2D 鱼用 up,3D 鱼用 forward。
@@ -776,14 +806,19 @@ public class MonsterContral : MonoBehaviour
_cachedChild = child;
}
Vector3 localPos = child.localPosition;
// ⭐ x/y 归零摆正模型;z 深度只在显式配置 depthOffset(≠0)时才覆盖,
// 否则保留预制体上设置的 3D 深度(3D Boss 用,之前被强制归零导致深度无效)
localPos.x = 0f;
localPos.y = 0f;
if (depthOffset != 0)
localPos.z = depthOffset;
child.localPosition = localPos;
// ⭐ 700/701直线阵(扇形/圈):保留child本地偏移——阵型靠"生成时的根旋转角度+本地偏移"
// 实现均匀分布,归零会把分布抹成一点(所有鱼缩到根中心);其余鱼x/y归零摆正模型
if (_yzPrefix != "700" && _yzPrefix != "701")
{
Vector3 localPos = child.localPosition;
// ⭐ x/y 归零摆正模型;z 深度只在显式配置 depthOffset(≠0)时才覆盖,
// 否则保留预制体上设置的 3D 深度(3D Boss 用,之前被强制归零导致深度无效)
localPos.x = 0f;
localPos.y = 0f;
if (depthOffset != 0)
localPos.z = depthOffset;
child.localPosition = localPos;
}
if (!isRotate) return;
Vector2 toNext = nextPos - (Vector2)transform.position;
@@ -791,6 +826,17 @@ public class MonsterContral : MonoBehaviour
Vector2 dirNorm = toNext.normalized; // ⭐ 缓存归一化向量,避免分支内重复计算
// ⭐ 路线左右方向懒缓存:终点在起点右边=左→右(+1)/左边=右→左(-1)/相同=纯垂直(0)
// 由起终点一次判定,路线终身不变——侧倾符号判断不再受曲线中段波动影响
if (!_routeDirXCached)
{
_routeDirXCached = true;
var bd = monsterData.moveLine.bezierCurveData;
if (bd.end.x > bd.start.x) _routeDirX = 1;
else if (bd.end.x < bd.start.x) _routeDirX = -1;
else _routeDirX = 0;
}
// ⭐ 左右半边滞回:越过中线±0.05才切换(反面补偿的开关,防垂直游抖动闪跳)
if (!_defaultMirror && dirNorm.x < -0.05f) _defaultMirror = true;
else if (_defaultMirror && dirNorm.x > 0.05f) _defaultMirror = false;
@@ -808,10 +854,32 @@ public class MonsterContral : MonoBehaviour
{
float angle = Mathf.Atan2(toNext.y, toNext.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle - 90f, Vector3.forward);
// ⭐ 左右镜像(修复"出鱼有的正有的反",不对称鱼往左显示反):
// 贴图头朝上的鱼纯旋转对齐时,往左游=整体转180°=肚皮朝上(反)。
// 往左时叠加X轴镜像:旋转角不变(镜像不改变鱼头方向),
// 只翻回背腹朝向,左右游姿态一致。以预制体基准绝对赋值,池复用不叠加
float mirrorX = _defaultMirror ? -_prefabScaleX : _prefabScaleX;
var s = transform.localScale;
if (!Mathf.Approximately(s.x, mirrorX))
{
s.x = mirrorX;
transform.localScale = s;
}
}
else
{
transform.rotation = Quaternion.LookRotation(dirNorm, Vector3.up);
// ⭐ 连续构造替代 LookRotation(dirNorm, up):朝向接近正上/正下时
// LookRotation 参考系退化(cross(up,forward)≈0),左右轴随dx符号
// 瞬间翻转180°导致鱼抖。此写法与 LookRotation 稳态等价且全程连续
float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg;
var rot = Quaternion.AngleAxis(heading, Vector3.forward)
* Quaternion.AngleAxis(90f, Vector3.up);
// ⭐ 往左时绕头尾轴再转180°:3D鱼左右近似对称,等效镜像,
// 背始终朝上(与往右一致),避免负缩放的法线翻转副作用
if (_defaultMirror)
rot *= Quaternion.AngleAxis(180f, Vector3.forward);
transform.rotation = rot;
}
return;
}
@@ -826,10 +894,11 @@ public class MonsterContral : MonoBehaviour
{
child.right = dirNorm;
var vv = child.eulerAngles;
// ⭐ 根据 z 轴角度判断鱼朝向(左/右半边),决定 x 侧倾符号
// child.right=dirNorm 在 2D 平面主要改变 z 轴旋转
// z<180(朝上半边)→负, z>=180(朝下半边)→正
vv.x = rotationPY * (vv.z < 180 ? -1 : 1);
// ⭐ 侧倾符号由路线方向一次判定(_routeDirX,起终点计算):
// 横向路线 向右→负/向左→正;曲线中段 dy 波动穿0不再翻号(±90闪已除)。
// 纯垂直路线(_routeDirX==0)回退用 dy(垂直游动恒号,稳定)
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y >= 0f;
vv.x = rotationPY * (neg ? -1f : 1f);
child.eulerAngles = vv;
}
break;
@@ -837,7 +906,9 @@ public class MonsterContral : MonoBehaviour
{
child.right = -dirNorm;
var vv = child.eulerAngles;
vv.x = -rotationPY * (vv.z < 180 ? -1 : 1);
// ⭐ 同上:符号由路线方向一次判定,垂直路线回退用 dy
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y <= 0f;
vv.x = -rotationPY * (neg ? -1f : 1f);
child.eulerAngles = vv;
}
break;
@@ -845,10 +916,11 @@ public class MonsterContral : MonoBehaviour
{
child.up = dirNorm;
var vv = child.eulerAngles;
// ⭐ 根据 z 轴角度判断鱼朝向(左/右半边),决定 y 侧倾符号
// child.up=dirNorm 在 2D 平面主要改变 z 轴旋转
// z<180(朝左半边)→负, z>=180(朝右半边)→正
vv.y = rotationPY * (vv.z < 180 ? -1 : 1);
// ⭐ 同上:符号由路线方向一次判定。
// 横向路线 向左→负/向右→正(与原 dx 判断语义一致且不再受波动影响);
// 纯垂直路线回退用 dy(上游→负/下游→正)
bool neg = _routeDirX != 0 ? _routeDirX < 0 : dirNorm.y >= 0f;
vv.y = rotationPY * (neg ? -1f : 1f);
child.eulerAngles = vv;
}
break;
@@ -856,7 +928,9 @@ public class MonsterContral : MonoBehaviour
{
child.up = -dirNorm;
var vv = child.eulerAngles;
vv.y = -rotationPY * (vv.z < 180 ? -1 : 1);
// ⭐ 同上:符号由路线方向一次判定,垂直路线回退用 dy
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y <= 0f;
vv.y = -rotationPY * (neg ? -1f : 1f);
child.eulerAngles = vv;
}
break;
@@ -866,23 +940,41 @@ public class MonsterContral : MonoBehaviour
// 转向只动X分量 = -heading(唯一随路径变的轴);
// Y=90 固定摆平(鱼头从Z轴转到屏幕平面,初始化锁死);
// Z=rotationPY 绕鱼头轴的姿态偏移(不改鱼头方向)
// ⭐ 反面补偿:往左掉头(X分量≈180)时绕鱼头轴的透视偏移会镜像,
// z翻成180-z,保持与往右同一透视方向(背朝向一致)。
// 环形路线(绕圈跨中线两次)的鱼关掉useMirrorCompensate避免闪
float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg;
float rollZ = -rotationPY;
if (useMirrorCompensate && _defaultMirror) rollZ = 180f - rollZ;
float rollZ;
if (useRollSignFlip)
{
// ⭐ 原始语义:按路线左右纯符号翻转(向左→+rotationPY/向右→-rotationPY),
// 无180°翻转;_routeDirX 起终点一次判定,终身不变不闪
rollZ = _routeDirX < 0 ? rotationPY : -rotationPY;
}
else
{
// ⭐ 反面补偿模式:由路线方向一次判定(_routeDirX),向左路线才补偿。
// 原 _defaultMirror 每帧滞回,环形路线绕圈跨中线两次会来回翻转
// → 模型闪 + Y欧拉±90跳。环形路线起终点相同(_routeDirX==0)天然不补偿
rollZ = -rotationPY;
if (useMirrorCompensate && _routeDirX < 0) rollZ = 180f - rollZ;
}
child.rotation = Quaternion.Euler(-heading, 90f, rollZ);
}
break;
case FacingAxis.NegZ:
{
// ⭐ 鱼头=-Z:同样X分量转向,基准反向(+180);Z取-rotationPY
// ⭐ 反面补偿:鱼头朝右掉头(X分量≈180)时z翻180-z(与Z case条件相反);
// 环形路线的鱼关掉useMirrorCompensate避免绕圈闪
float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg;
float rollZ = -rotationPY;
if (useMirrorCompensate && !_defaultMirror) rollZ = 180f - rollZ;
float rollZ;
if (useRollSignFlip)
{
// ⭐ 原始语义(与Z case条件相反):向右→+rotationPY/向左→-rotationPY
rollZ = _routeDirX > 0 ? rotationPY : -rotationPY;
}
else
{
// ⭐ 反面补偿模式(与Z case条件相反):向右路线才补偿
rollZ = -rotationPY;
if (useMirrorCompensate && _routeDirX > 0) rollZ = 180f - rollZ;
}
child.rotation = Quaternion.Euler(-heading + 180f, 90f, rollZ);
}
break;
@@ -890,15 +982,86 @@ public class MonsterContral : MonoBehaviour
}
else
{
// 控制父物体自身朝向
// ⭐ 控制父物体自身朝向:与 ischild0 分支同款完整逻辑
// (侧倾符号由方向直接算 + Z/NegZ 连续构造 + 镜像补偿),
// 只是操作对象是 transform 自身——未勾 ischild0 的鱼(如鱼阵普通鱼)
// 同样获得防闪处理,不再只有一行式轴对齐
switch (_facingAxis)
{
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;
case FacingAxis.X:
{
transform.right = dirNorm;
var vv = transform.eulerAngles;
// ⭐ 侧倾符号由路线方向一次判定(_routeDirX,起终点计算),垂直路线回退用 dy
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y >= 0f;
vv.x = rotationPY * (neg ? -1f : 1f);
transform.eulerAngles = vv;
}
break;
case FacingAxis.NegX:
{
transform.right = -dirNorm;
var vv = transform.eulerAngles;
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y <= 0f;
vv.x = -rotationPY * (neg ? -1f : 1f);
transform.eulerAngles = vv;
}
break;
case FacingAxis.Y:
{
transform.up = dirNorm;
var vv = transform.eulerAngles;
// ⭐ 同上:横向路线 向左→负/向右→正,垂直路线回退用 dy
bool neg = _routeDirX != 0 ? _routeDirX < 0 : dirNorm.y >= 0f;
vv.y = rotationPY * (neg ? -1f : 1f);
transform.eulerAngles = vv;
}
break;
case FacingAxis.NegY:
{
transform.up = -dirNorm;
var vv = transform.eulerAngles;
bool neg = _routeDirX != 0 ? _routeDirX > 0 : dirNorm.y <= 0f;
vv.y = -rotationPY * (neg ? -1f : 1f);
transform.eulerAngles = vv;
}
break;
case FacingAxis.Z:
{
// ⭐ 连续构造:转向只动X分量=-heading,Y=90固定摆平,Z=rotationPY
float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg;
float rollZ;
if (useRollSignFlip)
{
// ⭐ 原始语义:按路线左右纯符号翻转(向左→+rotationPY/向右→-rotationPY)
rollZ = _routeDirX < 0 ? rotationPY : -rotationPY;
}
else
{
// ⭐ 反面补偿模式:向左路线才补偿,环形(_routeDirX==0)不补偿
rollZ = -rotationPY;
if (useMirrorCompensate && _routeDirX < 0) rollZ = 180f - rollZ;
}
transform.rotation = Quaternion.Euler(-heading, 90f, rollZ);
}
break;
case FacingAxis.NegZ:
{
// ⭐ 鱼头=-Z:基准反向(+180);符号条件与Z case相反(向右路线)
float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg;
float rollZ;
if (useRollSignFlip)
{
rollZ = _routeDirX > 0 ? rotationPY : -rotationPY;
}
else
{
rollZ = -rotationPY;
if (useMirrorCompensate && _routeDirX > 0) rollZ = 180f - rollZ;
}
transform.rotation = Quaternion.Euler(-heading + 180f, 90f, rollZ);
}
break;
}
}
}
@@ -1170,7 +1170,7 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
{
var cfg = spawnDic[createFishData.fishNumber];
for (int i = 1; i <= 16; i++)
for (int i = 1; i <= 17; i++)
{
// 交替正负角度,形成扇形分布
float angle = i * 5f * (i % 2 == 0 ? -1f : 1f);
@@ -94,8 +94,11 @@ public class RoomPlayerInfo
/// <summary>玩家等级(后端 RoomInfo 下发)</summary>
public int level;
/// <summary>玩家经验(0~1,后端暂未下发,字段先占位等补充)</summary>
public float exp;
/// <summary>玩家当前经验(原始值)</summary>
public long exp;
/// <summary>升级所需总经验(经验条进度 = exp / maxExp)</summary>
public long maxExp;
/// <summary>卡牌等级</summary>
public int userCardLevel;
@@ -137,6 +140,15 @@ public class RoomPlayerInfo
/// </summary>
public object playerFactor;
/// <summary>累计充值金币</summary>
public long totalRechargeGold;
/// <summary>保险箱金币</summary>
public long safeBoxGold;
/// <summary>是否为机器人(后端下发)</summary>
public bool isBot;
/// <summary>是否挂机中</summary>
public bool isAfk;
}
@@ -39,9 +39,14 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
public int Level { get; set; } = 0;
/// <summary>
/// 玩家经验(0~1,后端暂未下发,字段先占位等补充)
/// 玩家当前经验(原始值)
/// </summary>
public float Exp { get; set; } = 0f;
public long Exp { get; set; } = 0;
/// <summary>
/// 升级所需总经验(经验条进度 = exp / maxExp)
/// </summary>
public long MaxExp { get; set; } = 0;
/// <summary>
/// Vip等级
@@ -108,6 +113,21 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
/// </summary>
public int State { get; set; } = 0;
/// <summary>
/// 玩家因子(后端风控/补偿参数,暂不解析具体结构)
/// </summary>
public object PlayerFactor { get; set; }
/// <summary>
/// 累计充值金币
/// </summary>
public long TotalRechargeGold { get; set; } = 0;
/// <summary>
/// 保险箱金币
/// </summary>
public long SafeBoxGold { get; set; } = 0;
/// <summary>
/// 是否为机器人
/// </summary>
@@ -190,6 +190,7 @@ namespace Assets.MyGame.Scripts.Net
p.isRedName = notice.IsRedName;
p.level = notice.Level;
p.exp = notice.Exp;
p.maxExp = notice.MaxExp;
p.vipLevel = notice.VipLevel;
p.cId = notice.CId;
p.gold = notice.Gold;
@@ -202,7 +203,11 @@ namespace Assets.MyGame.Scripts.Net
p.beginCoin = notice.BeginCoin;
p.beginTime = notice.BeginTime;
p.state = notice.State;
p.playerFactor = notice.PlayerFactor;
p.totalRechargeGold = notice.TotalRechargeGold;
p.safeBoxGold = notice.SafeBoxGold;
p.isBot = notice.IsBot;
p.isAfk = notice.IsAfk;
}
/// <summary>
+5 -4
View File
@@ -981,8 +981,9 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
/// <param name="vipLevel">VIP 等级 0~10</param>
/// <param name="userNumber">玩家编号(短ID,显示在 tmp_UserNumber;空则回退显示 userId)</param>
/// <param name="level">玩家等级(显示在 tmp_UserLevel)</param>
/// <param name="exp">玩家经验 0~1(显示在 slider_UserLevelEXP;后端暂未下发,先占位)</param>
public void SetUserInfo(string userId, string userName, string avatar, int vipLevel, string userNumber = null, int level = 0, float exp = 0f)
/// <param name="exp">玩家当前经验(原始值)</param>
/// <param name="maxExp">升级所需总经验(经验条进度 = exp / maxExp)</param>
public void SetUserInfo(string userId, string userName, string avatar, int vipLevel, string userNumber = null, int level = 0, long exp = 0, long maxExp = 0)
{
// 玩家 ID:优先显示后端下发的短编号 UserNumber,没有再用 userId。带 "ID:" 前缀
if (tmp_UserNumber != null)
@@ -1002,9 +1003,9 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
if (tmp_UserLevel != null && level > 0)
tmp_UserLevel.text = "LV:" + level;
// 经验条:exp 为 0~1 的进度值(后端经验值字段暂未下发,明天补充后直接生效)
// 经验条:后端下发原始经验值 + 升级所需总经验,换算成 0~1 进度
if (slider_UserLevelEXP != null)
slider_UserLevelEXP.value = Mathf.Clamp01(exp);
slider_UserLevelEXP.value = maxExp > 0 ? Mathf.Clamp01((float)exp / maxExp) : 0f;
// 头像:优先 GameManager 头像缓存(Resources/Avatar),查不到用默认头像
if (img_Head != null)