This commit is contained in:
2026-09-11 15:18:49 +08:00
parent 97d6fc93f8
commit 799bb2f7ea
@@ -206,6 +206,13 @@ public class MonsterContral : MonoBehaviour
/// <summary>缓存的子物体(朝向控制用),避免每帧 transform.GetChild(0)</summary>
private Transform _cachedChild;
/// <summary>
/// 左右半边状态(带±0.05滞回):false=朝右半边,true=朝左半边。
/// 用于Z/NegZ单轴转向的反面补偿开关——越过中线明显后才切换,
/// 防止垂直游动时 dirNorm.x 微抖导致补偿来回跳(闪)。
/// </summary>
private bool _defaultMirror = false;
// ----------------------------------------------------------------
// ⭐ 鱼阵 700/701 直线移动缓存
// 同一鱼阵起点相同,Init 时一次性算好世界坐标 + 方向系数,
@@ -319,50 +326,6 @@ public class MonsterContral : MonoBehaviour
public int rotationPY = 60;
/// <summary>
/// 是否启用左右镜像翻转(仅 ischild0 配置了 forwordXYZ 的 Z/NegZ 鱼生效)。
/// ⚠️ 默认关闭:镜像在跨左右中线的瞬间切换(旋转跳180°),适合"水平单向横穿"的鱼;
/// 路线会绕圈/反复跨中线的鱼(如曲线游动的Boss)勾上会频繁翻转,不要开。
/// </summary>
[Tooltip("左右镜像翻转(仅forwordXYZ=Z/-Z且ischild0的鱼生效):往左游时叠加180°让背朝向与往右一致。跨中线瞬间切换会翻转180°,曲线绕行路线勿开")]
public bool useFacingMirror = false;
/// <summary>
/// 平滑转头速度(度/秒,ischild0 轴对齐鱼生效)。
/// heading 每帧直接对准移动方向,路线急弯处方向剧变会导致鱼头单帧瞬跳(视觉"翻转一下");
/// 按此速度限制转头,急弯时快速甩头跟随而非瞬跳。0=关闭平滑(瞬跳)。
/// </summary>
[Tooltip("平滑转头速度(度/秒):限制heading跟随速度,路线急弯时鱼头平滑甩过去而非单帧瞬跳。0=关闭")]
public float facingTurnSpeed = 720f;
/// <summary>
/// 模型装反修正(仅 forwordXYZ=Z/-Z 且 ischild0 的鱼生效):
/// 绕头尾轴整体再转180°(等效 Y 轴旋转×-1:背/左右侧互换,鱼头朝向不变)。
/// 模型 Y 轴(背)朝向与预期相反时勾选。
/// </summary>
[Tooltip("模型装反修正(仅forwordXYZ=Z/-Z且ischild0):绕头尾轴转180°修正Y轴装反的模型,鱼头朝向不变")]
public bool flipModelRoll = false;
/// <summary>
/// 双数路线镜像(仅配置了 forwordXYZ 且 ischild0 的 Boss 生效):
/// 双数路线(右→左)绕头尾轴补180°,背腹与单数路线(左→右)镜像一致。
/// 按路线编号奇偶判定,整条路线固定不变;不会像 useFacingMirror
/// 因曲线中途跨中线而翻转。语义对齐未配置朝向轴 Boss 的 ApplyBossRouteFlip。
/// </summary>
[Tooltip("双数路线镜像(仅配置forwordXYZ且ischild0的Boss):双数路线(右→左)绕头尾轴补180°修正背腹,按路线奇偶全程固定,不随中线切换")]
public bool routeFlipMirror = false;
/// <summary>
/// 默认朝向分支(无forwordXYZ配置的普通鱼)的左右镜像状态(带滞回):
/// false=朝右正向,true=朝左镜像。越过中线±0.05才切换,防止垂直游动时抖动闪跳。
/// </summary>
private bool _defaultMirror = false;
/// <summary>是否已锁存初始欧拉(每条鱼每次出场锁一次)</summary>
private bool _facingApplied = false;
/// <summary>初始欧拉(美术摆放姿态):X/Y分量锁死,转向只叠加Z分量</summary>
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
{