From 704cf26fa8167c39b13c2a01dff2de8196a21da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=AE2003?= <2384017988@qq.com> Date: Fri, 21 Aug 2026 01:52:14 +0800 Subject: [PATCH] 1 --- .../Game/XJMH/Boss/JiXieXie/JiXieXieEvent.cs | 24 ++- .../Game/XJMH/Boss/JiXieXie/JiXieXie_BOSS.cs | 183 +++++++++++++++++- .../XJMH/Boss/JiXieXie/PW_Enemy_3000.prefab | 16 +- 3 files changed, 213 insertions(+), 10 deletions(-) diff --git a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXieEvent.cs b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXieEvent.cs index 1301fbbac..8be388eb9 100644 --- a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXieEvent.cs +++ b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXieEvent.cs @@ -23,9 +23,10 @@ using Random = UnityEngine.Random; /// num < 炮倍×100 绿,< 炮倍×200 蓝,< 炮倍×500 紫,≥ 炮倍×500 红(金色不用), /// 每张卡下方显示本份金币数量。 /// -/// 流程(用户确认版,全程 UI 层): -/// 死亡位置一次出现 8 颗宝石 → 停留1秒 → 飞到屏幕中心两排(每排4个)排好 +/// 流程(用户确认版,全程 UI 层;lua 对应 PW_Effect.txt DropTenCardEffect): +/// 屏幕靠上位置一次出现 N 颗宝石 → 飞到屏幕中心两排排好 /// → 一张一张翻牌(全是金币)→ 卡牌飞到座位上方 → 显示 BJEndUI + 加分 + 回收 +/// 音效对齐 lua:宝石掉落 FreeCardDrop / 翻牌 FreeCardBomb / 分数滚动 FreeCardEarnRoll(+Enlarge/Reduce) / 领取 FreeGet /// /// ⭐ 资源配置:所有预制体/图片/名称集中在 JiXieXieConfig 资产(ScriptableObject), /// 只需拖一个 config 字段。资产用菜单 Tools → 机械蝎 → 生成八连抽配置资产 一键生成。 @@ -386,8 +387,9 @@ public class JiXieXieEvent : BJEventBase Vector2 baseUi = CoordinateTransformation.ScreenPoint_ToCanvas( new Vector2(Screen.width / 2f, Screen.height * spawnYRatio), _uiCanvas); - // 音效:宝石掉落(PW_FreeCardDrop,只播一次,宝石间隔太密不逐颗播) - AudioManager.Instance.PlayerFXByName("PW_FreeCardDrop"); + // 音效:宝石掉落(lua OnCreate 仅 isSelf 播 FreeCardDrop,只播一次,宝石间隔太密不逐颗播) + if (isSelf) + AudioManager.Instance.PlayerFXByName("PW_FreeCardDrop"); _chipInstances.Clear(); @@ -523,12 +525,19 @@ public class JiXieXieEvent : BJEventBase { Debug.Log($"[JiXieXie] 八连抽完成,显示 BJEndUI 倍数={bJbeishu} 总分={maxScore} isSelf={isSelf}"); + // 音效:分数滚动开始(lua RollScoreAim 用 FreeCardEarnRoll) + AudioManager.Instance.PlayerFXByName("PW_FreeCardEarnRoll"); + Vector3 uiPos = GetSeatPosition() + Vector3.up * bjEndUIOffsetY; if (bjEndUI != null) { var endUI = Instantiate(bjEndUI, uiPos, Quaternion.identity, CreateFishManager.Instance != null ? CreateFishManager.Instance.bjEffectRoot : null); endUI.Init(bJbeishu, maxScore, isSelf); + + // 音效:分数数字放大→还原(lua RollScoreAim 用 FreeCardEarnEnlarge / FreeCardEarnReduce) + AudioManager.Instance.PlayerFXByName("PW_FreeCardEarnEnlarge"); + DOVirtual.DelayedCall(0.4f, () => AudioManager.Instance.PlayerFXByName("PW_FreeCardEarnReduce")); } else { @@ -538,6 +547,9 @@ public class JiXieXieEvent : BJEventBase // 回加金币到玩家 JiaFen?.Invoke(); + // 音效:领取成功(lua MoveEndAim 最后一张卡 i==10 用 FreeGet) + AudioManager.Instance.PlayerFXByName("PW_FreeGet"); + // 回收爆奖预制体 + 销毁 chip die(); } @@ -561,8 +573,8 @@ public class JiXieXieEvent : BJEventBase /// 翻单张卡:隐藏宝石 → 显示卡牌背面 → 翻转显示正面(分级色)→ 特效 private void FlipOneCard(GameObject chip, DropItemInfo info, int index) { - // 音效:打开卡片(PW_OpenCard) - AudioManager.Instance.PlayerFXByName("PW_OpenCard"); + // 音效:翻牌/爆开(lua BombToAmi 用 FreeCardBomb;原 PW_OpenCard 为旧口径) + AudioManager.Instance.PlayerFXByName("PW_FreeCardBomb"); // 隐藏宝石相关子物体 Transform gemstone = chip.transform.Find(gemstoneName); diff --git a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXie_BOSS.cs b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXie_BOSS.cs index 66ce55339..51a9085e3 100644 --- a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXie_BOSS.cs +++ b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/JiXieXie_BOSS.cs @@ -1,28 +1,77 @@ using Assets.MyGame.Scripts.Net.APIResponses.Enum; using Assets.MyGame.Scripts.Net.WebResponses; +using DG.Tweening; using UnityEngine; /// /// 机械蝎 Boss。 /// 死亡流程:正常打死 → 在死亡位置爆炸 → 范围内普通鱼死亡 → 加分滚动。 /// 通用爆奖模式,Boss 本体走标准 MonsterControl_BossBase.DieFish。 +/// +/// lua 对应:pw_enemy/PW_Scorpion.txt(本体生命周期)+ pw_effect/PW_Effect.txt DropTenCardEffect(八连抽)。 +/// 本体 Spine 状态机(PW_Scorpion.txt):in(出生) → stand(站立,2.2s后) ⇄ attack(随机10~25s,需倒计时>5) +/// → out(离场/OnMoveFinish) → 爆炸(OnExplosion)。碰撞体由 MonsterContral 基类驱动。 +/// 八连抽(DropTenCardEffect)见 JiXieXieEvent.cs。 +/// +/// C# 侧已对齐 lua 的 Spine 动画切换(OnMoveEnter 驱动 in→stand + 启动攻击循环 StartAttackCycle;DieFish 驱动 out), +/// 全部经 PlaySpineSafe 守卫:动画名缺失或 isSpine=0 时自动跳过(回退原 stand2),绝不抛异常。 +/// ⚠️ 当前预制体 PW_Enemy_3000 的 isSpine=0,且 Spine 资源需含 in/stand/attack/out 动画名; +/// 美术接入 Spine 骨骼(isSpine=1 + 对应动画名)后此逻辑立即生效。 +/// 音效时序(FreeCardBomb/FreeGet 等)已在 JiXieXieEvent.cs 对齐;死亡音效 isSelf 判定见 DieFish。 /// public class JiXieXie_BOSS : MonsterControl_BossBase { + // ---- Spine 动画名(需与美术 Spine 资源动画名一致;不一致改这里即可)---- + private const string AnimIn = "in"; + private const string AnimStand = "stand"; + private const string AnimAttack = "attack"; + private const string AnimOut = "out"; + + // ---- 攻击节奏(对齐 lua:随机 10~25s 间隔,attack 持续 2s,出生 2.2s 后转 stand)---- + // ⚠️ 攻击循环用 DOTween 递归计时驱动,从 OnMoveEnter 启动(进入 move 状态即触发,不依赖 isMove)。 + // 基类 MonsterContral.OnUpdate 对 isMove=false 的固定 Boss 会在分发处直接 return, + // 导致 OnMoveUpdata 永不被调用——故攻击计时不能放在 OnMoveUpdata 里。 + private Tween _attackTween; + private bool _attackRunning; + + // ---- 自动退出(对齐 lua OnMoveFinish:未被击杀也到点离场)---- + // ⚠️ 机械蝎 isMove=false 固定停在 (0,0,0),不走路线、OnMoveUpdata 永不触发路线终点检测, + // 且服务端 SyncGameData.remainingSeconds 未接上销毁逻辑 → 不被打死就永远不消失。 + // 故用可配置存活计时器兜底:到点播 out(若 Spine 含)后回收。<=0 表示永不离场(纯靠击杀)。 + [Header("自动退出:未被击杀时的存活时长(秒),<=0 永不自动离场")] + public float bossLifeTime = 30f; + private Tween _exitTween; + private bool _isExiting; + private const float AttackIntervalMin = 10f; + private const float AttackIntervalMax = 25f; + private const float AttackDuration = 2f; + private const float BornToStandDelay = 2.2f; + public override void OnEnable() { base.OnEnable(); + _attackRunning = false; // 音效:蝎子语音(PW_TextScorpion) AudioManager.Instance.PlayerFXByName("PW_TextScorpion"); } public override void DieFish(RoomKillNotice obj = null) { + StopAttackCycle(); // 死亡即停止攻击循环,避免对已销毁对象触发动画 + StopExitTimer(); // 取消自动退出计时,避免击杀回收后定时器再触发一次回收 base.DieFish(obj); + // 离场/死亡动画(对齐 lua out;非 Spine 或无 out 动画时自动跳过) + PlaySpineSafe(AnimOut, false); + // 音效:蝎子死亡 + 死亡语音(PW_ScorpionDie / PW_ScorpionDieVoice) - AudioManager.Instance.PlayerFXByName("PW_ScorpionDie"); - AudioManager.Instance.PlayerFXByName("PW_ScorpionDieVoice"); + // lua OnExplosion:仅当击杀者为自己(isSelf)才播;本地用户不可用时回退为必播(保留原行为) + bool isSelfKill = GameManager.Instance != null && obj != null && obj.userId == GameManager.Instance.user.id; + if (isSelfKill || GameManager.Instance == null) + { + AudioManager.Instance.PlayerFXByName("PW_ScorpionDie"); + AudioManager.Instance.PlayerFXByName("PW_ScorpionDieVoice"); + } // 标准爆奖事件初始化 action_effect_DieAward = (o) => @@ -62,4 +111,134 @@ public class JiXieXie_BOSS : MonsterControl_BossBase } }; } + + // ================================================================ + // Spine 动画切换(对齐 lua PW_Scorpion.txt 的 SkeletonAnimation 状态机) + // 全部经 PlaySpineSafe / HasSpineAnim 守卫:动画名缺失或 isSpine=0 时自动跳过 + // (回退原 stand2 或保持现状),绝不抛 Spine ArgumentException。 + // ⚠️ 当前预制体 PW_Enemy_3000 的 isSpine=0 且 Spine 资源需含 in/stand/attack/out 动画名; + // 美术接入 Spine 骨骼(isSpine=1 + 对应动画名)后此逻辑立即生效。 + // ================================================================ + + /// 出生动画序列:in(一次) → 2.2s 后 stand(循环)。无 in 动画时回退基类 stand2。 + /// 同时启动攻击循环(OnMoveEnter 在状态切到 move 时必被调用,不受 isMove 闸门影响)。 + public override void OnMoveEnter() + { + if (HasSpineAnim(AnimIn)) + { + PlaySpineSafe(AnimIn, false); + DOVirtual.DelayedCall(BornToStandDelay, () => PlaySpineSafe(AnimStand, true)); + } + else + { + base.OnMoveEnter(); + } + StartAttackCycle(); + StartExitTimer(); + } + + /// 对象回收/离场时停止攻击与退出计时,避免对已销毁对象触发(DOTween 回调安全守卫)。 + public override void OnDisable() + { + StopAttackCycle(); + StopExitTimer(); + base.OnDisable(); + } + + // ================================================================ + // 自动退出(对齐 lua OnMoveFinish:未被击杀也到点离场) + // ================================================================ + + /// 启动存活计时:到点触发 AutoExit。bossLifeTime<=0 不自动离场(纯靠击杀)。 + private void StartExitTimer() + { + if (bossLifeTime <= 0f) return; + _isExiting = false; + if (_exitTween != null && _exitTween.IsActive()) _exitTween.Kill(); + _exitTween = DOVirtual.DelayedCall(bossLifeTime, AutoExit); + } + + /// 到点离场:停攻击循环 → 播 out(若 Spine 含)→ 延迟回收本体(同 lua OnMoveFinish→DestroyEnemy)。 + private void AutoExit() + { + if (_isExiting) return; + _isExiting = true; + StopAttackCycle(); + + float delay = 0f; + if (HasSpineAnim(AnimOut)) + { + PlaySpineSafe(AnimOut, false); + delay = 2f; // 对齐 lua PW_Effect_Scorpion_Out 的 time=2 + } + DOVirtual.DelayedCall(delay, () => + { + if (this == null) return; + if (CreateFishManager.Instance != null) + CreateFishManager.Instance.Id_DieFish_NotFX(guid); + }); + } + + private void StopExitTimer() + { + _isExiting = false; + if (_exitTween != null && _exitTween.IsActive()) + _exitTween.Kill(); + _exitTween = null; + } + + /// 攻击循环:随机 10~25s 间隔播放 attack(一次,2s) 后回 stand(对齐 lua stand⇄attack)。 + /// 用 DOTween 递归计时,不依赖 OnMoveUpdata / isMove。 + /// lua 原逻辑 attack 还需“出场倒计时>5”才触发,C# 侧无该倒计时阶段,故简化为纯随机间隔。 + private void StartAttackCycle() + { + if (!HasSpineAnim(AnimAttack)) return; + if (_attackRunning) return; // 防止 OnMoveEnter 重复触发导致多套循环 + _attackRunning = true; + ScheduleNextAttack(); + } + + private void ScheduleNextAttack() + { + if (!_attackRunning) return; + float delay = UnityEngine.Random.Range(AttackIntervalMin, AttackIntervalMax); + _attackTween = DOVirtual.DelayedCall(delay, PlayAttackOnce); + } + + private void PlayAttackOnce() + { + if (!_attackRunning) return; + PlaySpineSafe(AnimAttack, false); + _attackTween = DOVirtual.DelayedCall(AttackDuration, () => + { + if (!_attackRunning) return; + PlaySpineSafe(AnimStand, true); + ScheduleNextAttack(); + }); + } + + private void StopAttackCycle() + { + _attackRunning = false; + if (_attackTween != null && _attackTween.IsActive()) + _attackTween.Kill(); + _attackTween = null; + } + + /// Spine 动画是否存在(动画名或 Skeleton 缺失时返回 false)。 + private bool HasSpineAnim(string name) + { + if (skeleton == null) return false; + var data = skeleton.SkeletonDataAsset?.GetSkeletonData(false); + return data != null && data.FindAnimation(name) != null; + } + + /// 安全播放 Spine 动画:动画名不存在 / 非 Spine 时静默跳过,避免 Spine 抛 ArgumentException。 + private void PlaySpineSafe(string name, bool loop) + { + if (skeleton == null) return; + var data = skeleton.SkeletonDataAsset?.GetSkeletonData(false); + if (data == null || data.FindAnimation(name) == null) return; + skeleton.AnimationState.SetAnimation(0, name, loop); + } } diff --git a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/PW_Enemy_3000.prefab b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/PW_Enemy_3000.prefab index bed90506c..dc3161f06 100644 --- a/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/PW_Enemy_3000.prefab +++ b/Assets/MyGame/aa/Game/XJMH/Boss/JiXieXie/PW_Enemy_3000.prefab @@ -125,7 +125,7 @@ MonoBehaviour: moveSpeed: 0 cachedCamera: {fileID: 0} cachedUirootRect: {fileID: 0} - skeleton: {fileID: 0} + skeleton: {fileID: 8745352577998363756} isSpine: 0 fishTypeID: 12028 currState: 0 @@ -137,7 +137,7 @@ MonoBehaviour: isMove: 0 is3D: 0 forwordXYZ: - fishenum: 0 + fishenum: 2 moveTime: 0 isyz: 0 yzID: @@ -167,3 +167,15 @@ MonoBehaviour: v3: {x: 0, y: 0, z: 0} isLocalCoin: 0 killerSitPos: -1 +--- !u!114 &8745352577998363756 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 114517790449158321, guid: 75d387735e7c6cb4d806a8603178a855, + type: 3} + m_PrefabInstance: {fileID: 8704159038579725533} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d247ba06193faa74d9335f5481b2b56c, type: 3} + m_Name: + m_EditorClassIdentifier: