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
{