This commit is contained in:
2026-10-08 02:01:28 +08:00
parent e2741fa837
commit ba077b4c80
7 changed files with 278 additions and 13 deletions
@@ -2234,6 +2234,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// <summary>召唤鱼 guid(= 服务端 fishId.ToString()):Notice 比 OnFishSpawn 先到时记录,等鱼建出来再检索</summary>
private string _summonFishGuid;
[Header("召唤卡漩涡特效")]
[Tooltip("召唤鱼漩涡特效预制体(鱼本体先隐藏,特效旋转到指定位置后鱼才激活出现在屏幕中央)")]
public GameObject summonVortexFXPrefab;
[Tooltip("漩涡特效时长(秒):特效转完这一刻激活鱼开始移动(需与特效动画时长一致)")]
public float summonVortexDuration = 1.5f;
[Header("召唤卡出场(旋转放大)")]
[Tooltip("出场时长(秒):原地旋转 + 放大到正常大小的时间")]
public float summonEntranceDuration = 0.5f;
[Tooltip("出场自转圈数(正数=顺时针,负数=逆时针)。以 0.5 位置的正确朝向为终点倒推,转完正好落在正确朝向")]
public float summonSelfSpinTurns = 2f;
[Tooltip("出场自转轴:默认 Z 轴(屏幕平面内自转)")]
public Vector3 summonSelfSpinAxis = new Vector3(0f, 0f, 1f);
[Tooltip("出场位置偏移(世界坐标):微调漩涡/出场落点")]
public Vector3 summonEntranceTargetOffset = Vector3.zero;
/// <summary>
/// 服务端 SummonCardFishNotice 房间广播回调:某玩家使用召唤卡,服务端已创建召唤鱼。
/// 仅播放召唤提示(飞机场召唤Boss鱼,其他场次召唤黄金鱼),
@@ -2292,19 +2308,120 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
/// <summary>
/// 召唤鱼特殊处理:保存引用 + 开始时间加整体时间的一半。
/// moveTime 初始 = moveLine.moveTime * 0.5 → 鱼直接出现在路线 0.5 位置(屏幕中央),
/// 之后从中间继续向终点游(moveTime 继续累计,走满总时长离场)。
/// isMove=false 的鱼不走路线(固定位置播动画),跳过偏移。
/// 召唤鱼特殊处理:保存引用 + 漩涡出场流程。
/// 流程:① 鱼本体先未激活(隐藏)→ ② 漩涡特效在指定位置(路线 0.5 = 屏幕中央)播放旋转
/// → ③ 特效时长到了再激活鱼(OnEnable 复位后 ChangeState(move) 自动开始移动)
/// → ④ 激活后重设 moveTime = 总时长*0.5(OnEnable 会把 moveTime 复位成 0,必须在激活后设置)。
/// 未拖 summonVortexFXPrefab 时跳过特效,直接放中间(兜底)。
/// </summary>
private void ApplySummonFishCenterSpawn(MonsterContral monster)
{
_summonFish = monster;
if (monster == null || !monster.isMove) return;
if (monster == null) return;
StartCoroutine(SummonVortexRoutine(monster));
}
/// <summary>漩涡出场协程:鱼先隐藏 → 漩涡在路线 0.5 位置播放 → 激活鱼但先禁用 MonsterContral → 原地旋转+放大出场(终点朝向=0.5 位置的正确朝向)→ 再启用 MonsterContral 继续游动</summary>
private IEnumerator SummonVortexRoutine(MonsterContral monster)
{
var moveLine = monster.monsterData?.moveLine;
if (moveLine == null || moveLine.moveTime <= 0f) return;
// ⭐ 开始时间 = 0 + 整体时间的一半 → 直接放在路线 0.5(屏幕中央)
monster.moveTime = moveLine.moveTime * 0.5f;
// 出场位置 = 路线 0.5 位置(屏幕中央);无路线的 Boss 兜底用屏幕正中
Vector3 spawnPos = GetSummonCenterWorld(monster);
if (moveLine != null && moveLine.moveTime > 0f)
{
// 中点世界坐标:路径上 moveTime = 总时长一半的位置(GetBezierPointJob 按秒取点)
Vector2 center2D = moveLine.GetBezierPointJob(moveLine.moveTime * 0.5f);
spawnPos = new Vector3(center2D.x, center2D.y, spawnPos.z);
}
spawnPos += summonEntranceTargetOffset;
// ① 鱼本体先未激活(漩涡期间不显示、不移动、打不到)
if (monster.gameObject.activeSelf)
monster.gameObject.SetActive(false);
// ② 漩涡特效:在路线 0.5 位置播放,旋转期间鱼保持隐藏
if (summonVortexFXPrefab != null)
{
EffectPool.Instance.Get(summonVortexFXPrefab, spawnPos, Quaternion.identity,
CreateFishManager.Instance.transform.parent); // autoReturn=true:粒子播完自动回池
yield return new WaitForSeconds(summonVortexDuration);
}
// ⭐ 漩涡期间这条鱼被击杀/回收(guid 已被复用成别的鱼)→ 中止出场,避免把回池的鱼又拉出来
if (monster == null || monster.isDead || monster.guid != _summonFishGuid) yield break;
// ③ 出场期间先禁用 MonsterContral 再激活预制体(这些预制体默认是未激活状态):
// 组件禁用时 OnEnable 不会跑 → 不会在出场瞬间按路线瞬移/开始游动,
// 也避免了"激活后再禁用"导致 Boss 的 OnEnable 跑两遍(重复进场音效/随机泳姿)
monster.enabled = false;
monster.gameObject.SetActive(true);
monster.transform.position = spawnPos;
// ④ 用鱼自身的朝向逻辑算出"0.5 位置的正确朝向"(含侧倾/镜像补偿),取出被改写的节点;
// 朝向逻辑用的 uirootRect/mainCamera/_cachedChild 是出生时 OnEnable 缓存的(禁用时不会被清)
MonsterContral.RouteFacingResult facing = monster.GetRouteFacingAt(0.5f);
Vector3 scaleTarget = monster.transform.localScale; // 此刻的缩放就是最终缩放(None 分支的左右镜像也在里面)
monster.transform.localScale = Vector3.zero; // 从 0 放大出来
// ⑤ 旋转 + 放大出场:以"正确朝向"为基准倒退若干圈开始转,转到最后一帧正好落在正确朝向
yield return SummonScaleSpinEntrance(monster, facing, scaleTarget);
// ⑥ 精确归位:朝向/缩放/位置写死成目标值,再启用 MonsterContral 从路线 50% 继续游动
// (OnEnable 会把 moveTime 清零,所以 moveTime 必须在启用之后设置 → OnSummonEntranceEnd 内部处理)
if (monster == null || monster.isDead || !monster.gameObject.activeInHierarchy) yield break;
if (facing.valid && facing.node != null) facing.node.rotation = facing.rotation;
monster.transform.localScale = scaleTarget;
monster.transform.position = spawnPos;
monster.enabled = true;
monster.OnSummonEntranceEnd(0.5f);
}
/// <summary>屏幕正中(画布中心)的世界坐标,z 沿用鱼自身深度</summary>
private Vector3 GetSummonCenterWorld(MonsterContral monster)
{
var uiRootRect = UIManager.Instance.uiRoot.GetComponent<RectTransform>();
Vector3 world = CoordinateTransformation.CanvasToWorld(Vector2.zero, uiRootRect, Camera.main);
world.z = monster.transform.position.z;
return world;
}
/// <summary>
/// 召唤出场:原地旋转 + 放大出来。
/// ⭐ 旋转以"0.5 位置的正确朝向"为基准倒退 summonSelfSpinTurns 圈开始,
/// 转到最后一帧正好落在正确朝向(不会有收尾的朝向跳变)。
/// ⭐ 只转朝向逻辑决定的那一个节点(根节点 或 child0):另一个节点保持预制体姿态。
/// </summary>
private IEnumerator SummonScaleSpinEntrance(MonsterContral monster, MonsterContral.RouteFacingResult facing, Vector3 scaleTarget)
{
float duration = Mathf.Max(0.01f, summonEntranceDuration);
float spinTotal = summonSelfSpinTurns * 360f;
float elapsed = 0f;
while (elapsed < duration)
{
// 出场途中被击杀/回收(回池会 SetActive(false))→ 立即中止,交由死亡流程处理
if (monster == null || monster.isDead || !monster.gameObject.activeInHierarchy) yield break;
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float ease = 1f - Mathf.Pow(1f - t, 3f); // OutCubic:先快后慢,收尾自然
// 放大出来:0 → 最终缩放(含左右镜像的 x 符号)
monster.transform.localScale = Vector3.Lerp(Vector3.zero, scaleTarget, ease);
// 旋转出来:从"正确朝向倒退 spinTotal 度"转到"正确朝向"
if (facing.valid && facing.node != null)
{
float deg = spinTotal * (1f - ease); // ease=0 → 倒退 spinTotal;ease=1 → 0(正确朝向)
facing.node.rotation = facing.rotation * Quaternion.AngleAxis(deg, summonSelfSpinAxis);
}
yield return null;
}
// 收尾精确落在正确朝向 + 最终缩放
if (facing.valid && facing.node != null)
facing.node.rotation = facing.rotation;
monster.transform.localScale = scaleTarget;
}
/// <summary>