diff --git a/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs b/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs index 29114ce5e..f49be5b53 100644 --- a/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs +++ b/Assets/MyGame/Scripts/Characters/Monster/MonsterContral.cs @@ -206,6 +206,13 @@ public class MonsterContral : MonoBehaviour /// 缓存的子物体(朝向控制用),避免每帧 transform.GetChild(0) private Transform _cachedChild; + /// + /// 左右半边状态(带±0.05滞回):false=朝右半边,true=朝左半边。 + /// 用于Z/NegZ单轴转向的反面补偿开关——越过中线明显后才切换, + /// 防止垂直游动时 dirNorm.x 微抖导致补偿来回跳(闪)。 + /// + private bool _defaultMirror = false; + // ---------------------------------------------------------------- // ⭐ 鱼阵 700/701 直线移动缓存 // 同一鱼阵起点相同,Init 时一次性算好世界坐标 + 方向系数, @@ -319,50 +326,6 @@ public class MonsterContral : MonoBehaviour public int rotationPY = 60; - /// - /// 是否启用左右镜像翻转(仅 ischild0 配置了 forwordXYZ 的 Z/NegZ 鱼生效)。 - /// ⚠️ 默认关闭:镜像在跨左右中线的瞬间切换(旋转跳180°),适合"水平单向横穿"的鱼; - /// 路线会绕圈/反复跨中线的鱼(如曲线游动的Boss)勾上会频繁翻转,不要开。 - /// - [Tooltip("左右镜像翻转(仅forwordXYZ=Z/-Z且ischild0的鱼生效):往左游时叠加180°让背朝向与往右一致。跨中线瞬间切换会翻转180°,曲线绕行路线勿开")] - public bool useFacingMirror = false; - - /// - /// 平滑转头速度(度/秒,ischild0 轴对齐鱼生效)。 - /// heading 每帧直接对准移动方向,路线急弯处方向剧变会导致鱼头单帧瞬跳(视觉"翻转一下"); - /// 按此速度限制转头,急弯时快速甩头跟随而非瞬跳。0=关闭平滑(瞬跳)。 - /// - [Tooltip("平滑转头速度(度/秒):限制heading跟随速度,路线急弯时鱼头平滑甩过去而非单帧瞬跳。0=关闭")] - public float facingTurnSpeed = 720f; - - /// - /// 模型装反修正(仅 forwordXYZ=Z/-Z 且 ischild0 的鱼生效): - /// 绕头尾轴整体再转180°(等效 Y 轴旋转×-1:背/左右侧互换,鱼头朝向不变)。 - /// 模型 Y 轴(背)朝向与预期相反时勾选。 - /// - [Tooltip("模型装反修正(仅forwordXYZ=Z/-Z且ischild0):绕头尾轴转180°修正Y轴装反的模型,鱼头朝向不变")] - public bool flipModelRoll = false; - - /// - /// 双数路线镜像(仅配置了 forwordXYZ 且 ischild0 的 Boss 生效): - /// 双数路线(右→左)绕头尾轴补180°,背腹与单数路线(左→右)镜像一致。 - /// 按路线编号奇偶判定,整条路线固定不变;不会像 useFacingMirror - /// 因曲线中途跨中线而翻转。语义对齐未配置朝向轴 Boss 的 ApplyBossRouteFlip。 - /// - [Tooltip("双数路线镜像(仅配置forwordXYZ且ischild0的Boss):双数路线(右→左)绕头尾轴补180°修正背腹,按路线奇偶全程固定,不随中线切换")] - public bool routeFlipMirror = false; - - /// - /// 默认朝向分支(无forwordXYZ配置的普通鱼)的左右镜像状态(带滞回): - /// false=朝右正向,true=朝左镜像。越过中线±0.05才切换,防止垂直游动时抖动闪跳。 - /// - private bool _defaultMirror = false; - - /// 是否已锁存初始欧拉(每条鱼每次出场锁一次) - private bool _facingApplied = false; - /// 初始欧拉(美术摆放姿态):X/Y分量锁死,转向只叠加Z分量 - private Vector3 _initEuler = Vector3.zero; - // ================================================================ // Unity 生命周期 @@ -411,11 +374,9 @@ public class MonsterContral : MonoBehaviour // (金龙/雷龙/玉龙等不走路径,直到爆奖结束销毁) // - 冰冻中重新激活的鱼,末尾会覆盖为 false,安全 isMove = baseMmove; - // ⭐ 重置左右镜像状态:池复用残留会在出场瞬间错误切换(跳180°)。 - // 出场第一帧 ApplyFacingDirection 的滞回会按实际移动方向立即修正(设置旋转前,同帧生效) + // ⭐ 重置左右半边状态:池复用残留会在出场瞬间带错误反面补偿, + // 第一帧滞回按实际移动方向修正后同帧生效 _defaultMirror = false; - // ⭐ 重置朝向锁存:池复用每次出场重新锁存初始欧拉 - _facingApplied = false; // ⭐ 重置可锁定状态:needLockEvent=true 的鱼每次重新生成都需等 NotifyLockable() // needLockEvent=false 的鱼一直可锁定 isLockable = !needLockEvent; @@ -819,12 +780,11 @@ public class MonsterContral : MonoBehaviour if (!isRotate) return; Vector2 toNext = nextPos - (Vector2)transform.position; - if (toNext.sqrMagnitude < 0.0001f) return; // 方向未知,下一帧再指定 + if (toNext.sqrMagnitude < 0.0001f) return; Vector2 dirNorm = toNext.normalized; // ⭐ 缓存归一化向量,避免分支内重复计算 - // ⭐ 左右镜像滞回(默认分支与Z/NegZ轴对齐分支共用):只在明显越过中线时切换, - // 避免鱼垂直方向游动时 dx 微小抖动导致镜像来回闪跳(镜像切换必须瞬间完成,不能平滑) + // ⭐ 左右半边滞回:越过中线±0.05才切换(反面补偿的开关,防垂直游抖动闪跳) if (!_defaultMirror && dirNorm.x < -0.05f) _defaultMirror = true; else if (_defaultMirror && dirNorm.x > 0.05f) _defaultMirror = false; @@ -841,32 +801,10 @@ 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 { - // ⭐ 连续构造替代 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; + transform.rotation = Quaternion.LookRotation(dirNorm, Vector3.up); } return; } @@ -874,30 +812,72 @@ public class MonsterContral : MonoBehaviour // 是否为子物体单独控制朝向和深度 if (ischild0) { - float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg; // 路径方向角 - - // ⭐ 首次:读回初始欧拉锁存,之后每帧复用—— - // 若每帧读回,Z分量里已含上帧的heading,会逐帧累积疯转 - if (!_facingApplied) - { - _initEuler = child.eulerAngles; - _facingApplied = true; - } - - float offsetX = _initEuler.x, offsetY = _initEuler.y, offsetZ = _initEuler.z; - //方向偏移(视角偏移写着看透视) + // ⭐ 全部使用缓存 child,不再 transform.GetChild(0) switch (_facingAxis) { - case FacingAxis.X: offsetX = rotationPY; break; - case FacingAxis.NegX: offsetX = rotationPY; break; - case FacingAxis.Y: offsetY = rotationPY; break; - case FacingAxis.NegY: offsetY = rotationPY; break; - case FacingAxis.Z: offsetZ = rotationPY; break; - case FacingAxis.NegZ: offsetZ = rotationPY; break; + case FacingAxis.X: + { + 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); + child.eulerAngles = vv; + } + break; + case FacingAxis.NegX: + { + child.right = -dirNorm; + var vv = child.eulerAngles; + vv.x = -rotationPY * (vv.z < 180 ? -1 : 1); + child.eulerAngles = vv; + } + break; + case FacingAxis.Y: + { + 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); + child.eulerAngles = vv; + } + break; + case FacingAxis.NegY: + { + child.up = -dirNorm; + var vv = child.eulerAngles; + vv.y = -rotationPY * (vv.z < 180 ? -1 : 1); + child.eulerAngles = vv; + } + break; + case FacingAxis.Z: + { + // ⭐ 换成旋转角度(不指定forward方向):鱼头朝Z的模型, + // 转向只动X分量 = -heading(唯一随路径变的轴); + // Y=90 固定摆平(鱼头从Z轴转到屏幕平面,初始化锁死); + // Z=rotationPY 绕鱼头轴的姿态偏移(不改鱼头方向) + // ⭐ 反面补偿:往左掉头(X分量≈180)时绕鱼头轴的透视偏移会镜像, + // z翻成180-z,保持与往右同一透视方向(背朝向一致) + float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg; + float rollZ = -rotationPY; + if (_defaultMirror) 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条件相反) + float heading = Mathf.Atan2(dirNorm.y, dirNorm.x) * Mathf.Rad2Deg; + float rollZ = -rotationPY; + if (!_defaultMirror) rollZ = 180f - rollZ; + child.rotation = Quaternion.Euler(-heading + 180f, 90f, rollZ); + } + break; } - - // ⭐ 转向:heading只叠加Z分量(唯一随路径变的轴),X/Y保持初始值 - child.rotation = Quaternion.Euler(offsetX, offsetY, heading + offsetZ); } else {