diff --git a/Assets/AddressableAssetsData/Windows/addressables_content_state.bin b/Assets/AddressableAssetsData/Windows/addressables_content_state.bin index 5b546d273..2acc3fd29 100644 Binary files a/Assets/AddressableAssetsData/Windows/addressables_content_state.bin and b/Assets/AddressableAssetsData/Windows/addressables_content_state.bin differ diff --git a/Assets/Framework/Tool/AudioManager.cs b/Assets/Framework/Tool/AudioManager.cs index 0a0d0715b..5c56176a8 100644 --- a/Assets/Framework/Tool/AudioManager.cs +++ b/Assets/Framework/Tool/AudioManager.cs @@ -126,6 +126,48 @@ public class AudioManager : MonoSingleton } } + private AudioSource _fxLoopSource; + + /// + /// 循环音效专用 AudioSource(懒创建,与 FX 共用混音组)。 + /// ⭐ 单独一条 source:Stop 只停循环音效,不会误杀 PlayOneShot 的一次性音效。 + /// + private AudioSource GetFxLoopSource() + { + if (_fxLoopSource == null) + { + var fx = GetAudioSource(AudioType.FX); + GameObject host = fx != null ? fx.gameObject : UIManager.Instance.transform.GetChild(0).gameObject; + _fxLoopSource = host.AddComponent(); + _fxLoopSource.playOnAwake = false; + _fxLoopSource.loop = true; + if (fx != null) _fxLoopSource.outputAudioMixerGroup = fx.outputAudioMixerGroup; + } + return _fxLoopSource; + } + + /// + /// 循环播放音效(如战神激光长音频)。重复调用同一条 clip 且已在播时不重头播放,只更新播放速度, + /// ⭐ 长音频用它代替逐发 PlayOneShot,避免声音重叠;pitch=2 即 2 倍速(狂暴)。 + /// + public void PlayFXLoop(AudioClip audioClip, float pitch = 1f) + { + if (audioClip == null) return; + var src = GetFxLoopSource(); + src.pitch = pitch > 0f ? pitch : 1f; + if (src.clip == audioClip && src.isPlaying) return; // 同一条音效已在循环 → 只变速,不重播 + src.clip = audioClip; + src.loop = true; + src.Play(); + } + + /// 停止循环音效(不影响 PlayOneShot 的一次性音效) + public void StopFXLoop() + { + if (_fxLoopSource != null && _fxLoopSource.isPlaying) + _fxLoopSource.Stop(); + } + /// /// 按音效资源名(#Audio 表 AudioResourcesPath,如 PW_Shark_Show)播放音效。 /// 名字 → ID 走 tbaudio.json 映射,表 ID 调整时无需改代码。 diff --git a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs index d1ebb49fa..d6b56a6d1 100644 --- a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs +++ b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs @@ -443,6 +443,9 @@ public class CreateFishManager : MonoSingleton /// 进入房间:初始化生成系统并绑定游戏面板。 public void EnterRoom(GameTypeEnum gameType) { + // ⭐ 复位残留冰冻状态:冰冻中切房时旧面板倒计时未走完就销毁, + // 不会触发 UnfreezeAllFish → isFrozen 残留 true → 新房间 OnFishSpawn 全部被拦,不出鱼 + ResetFreezeState(); InitSpawnSystem(gameType); BindCurrentGamePanel(); EnsureBJEffectRoot(); @@ -1344,6 +1347,9 @@ public class CreateFishManager : MonoSingleton { //await Task.Delay(2000); + // ⭐ 退房复位冰冻状态:冰冻中退房时旧面板倒计时未走完就销毁, + // 不会触发 UnfreezeAllFish → isFrozen 残留 true → 污染下一个房间不出鱼 + ResetFreezeState(); spawnList = null; spawnDic = null; // ⭐ 退房清理排队中的出鱼事件,防止旧房间残留鱼泄进下一个房间 @@ -2333,6 +2339,23 @@ public class CreateFishManager : MonoSingleton } #region 冰冻技能 + /// + /// 复位冰冻状态(退房/进房时调用): + /// 清除 isFrozen 与冰冻记录,并停掉所有出鱼协程。 + /// ⭐ 若不复位:冰冻中切换房间时,旧面板的 15s 解冻倒计时随面板一起销毁, + /// 永远不会调用 UnfreezeAllFish → isFrozen 残留 true → + /// 新房间 OnFishSpawn 第一行 `if (isFrozen) return;` 把服务端所有出鱼全部拦掉 → 新房间不出鱼。 + /// ⭐ StopAllCoroutines:分帧出鱼协程里的 `while (isFrozen) yield return null;` 会一直挂起, + /// 解冻复位后会继续把旧房间排队的鱼生成到新房间,必须一并停掉。 + /// + public void ResetFreezeState() + { + isFrozen = false; + frozenFishStates.Clear(); + frozenFishIsMoves.Clear(); + StopAllCoroutines(); + } + /// /// 冰冻:记录所有鱼当前状态并强制切到 Idle(暂停移动+动画) /// diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs index 713322464..d903370e2 100644 --- a/Assets/MyGame/Scripts/Turret/SitPosData.cs +++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs @@ -158,6 +158,10 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler private const float LaserFXWaitFallback = 2f; /// 上一帧是否有锁定点(检测锁定建立沿,触发准备等待) private bool _laserLockActive; + /// 激光循环音效是否正在播放(跟随激光实际显示状态起停,避免每帧重复触发) + private bool _laserSoundOn; + /// 激光循环音效当前播放速度(1=正常,2=狂暴2倍速),用于边沿比较避免每帧重复调用 + private float _laserSoundPitch = 1f; /// 预制体形态激光的基准缩放(重建时取自 laserPrefab.transform.localScale) private Vector3 _laserFxBaseScale = Vector3.one; /// 预制体形态激光的角度偏移:激光预制体自带的 Z 轴旋转(重建时缓存,绕世界 Z 叠加在计算朝向上) @@ -478,6 +482,9 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler // 两个条件满足其一即激活——散射模式开着但目标不足时也要显示起点球 UpdateJGFXParentsActive(lockActive && (_laserTargets.Count > 1 || isScatterLaserOn)); + // ⭐ 激光循环音效:跟随"激光实际显示"状态起停(激光音效很长,逐发播会重叠,改为循环) + UpdateZhanShenLaserSound(lockActive); + #if UNITY_EDITOR DebugJGFXState(); #endif @@ -505,6 +512,43 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler _laserPrepWait = 0f; } + /// + /// 激光循环音效:跟随"激光实际显示"状态起停(仅本地座位,且战神配置了 laserAudio 才播)。 + /// ⭐ 用 AudioManager.PlayFXLoop 走专用循环 AudioSource——激光音效很长,逐发 PlayOneShot 会重叠; + /// 起停走状态边沿,锁定期间不重复触发;激光消失/清战神/座位禁用时由 DestroyZhanShenLaser 停止。 + /// ⭐ 狂暴(暴走道具,开火速度翻倍)时同步把激光音效提到 2 倍速。 + /// + private void UpdateZhanShenLaserSound(bool laserVisible) + { + var zsData = currentZhanShenData as ZhanShenTurretData; + bool shouldPlay = laserVisible && this == localPlayerSit && zsData != null && zsData.laserAudio != null; + + if (!shouldPlay) + { + // 激光消失/非本地/未配置音效:停止循环(仅本座位在播时才停,避免误停他人座位) + if (_laserSoundOn) + { + _laserSoundOn = false; + AudioManager.Instance.StopFXLoop(); + } + return; + } + + // ⭐ 狂暴:激光音效同步 2 倍速(与开火速度翻倍一致) + float pitch = IsFrenzyNow() ? 2f : 1f; + if (_laserSoundOn && Mathf.Approximately(pitch, _laserSoundPitch)) return; // 已在播且速度未变 → 无需处理 + _laserSoundOn = true; + _laserSoundPitch = pitch; + AudioManager.Instance.PlayFXLoop(zsData.laserAudio, pitch); // 已在播同一条时只变速,不重头播 + } + + /// 本地座位当前是否狂暴(开火速度翻倍):狂暴标志由面板 SetFrenzy 维护在炮台/战神数据上 + private bool IsFrenzyNow() + { + if (currentZhanShenData != null && currentZhanShenData.isFrenzy) return true; + return currentTurret != null && currentTurret.isFrenzy; + } + /// 隐藏全部战神激光(两种形态都处理) private void HideZhanShenLasers() { @@ -670,6 +714,12 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler // 准备动画等待状态复位(下次锁定重新计时) _laserPrepWait = 0f; _laserLockActive = false; + // 激光循环音效停止(仅本座位在播时才停,避免误停他人座位) + if (_laserSoundOn) + { + _laserSoundOn = false; + AudioManager.Instance.StopFXLoop(); + } } // ==================== 激光起点/命中特效(三发+锁定) ==================== diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_14080_0_DaTing.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_14080_0_DaTing.prefab index b10a13719..9e7c62dc7 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_14080_0_DaTing.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_14080_0_DaTing.prefab @@ -1878,4 +1878,5 @@ MonoBehaviour: selfOffsetJGStart: {x: -17, y: 130} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -125, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: 3242c49d65ce5ea46a2e08223f9efdbf, type: 3} + laserAudio: {fileID: 8300000, guid: cec3a7ee6689f0447813cc27f500f9cf, type: 3} + bulletAudio: {fileID: 8300000, guid: 3242c49d65ce5ea46a2e08223f9efdbf, type: 3} diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_4022.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_4022.prefab index 6e344690e..d7601fac1 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_4022.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_4022.prefab @@ -643,4 +643,5 @@ MonoBehaviour: selfOffsetJGStart: {x: 0, y: 180} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -100, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: 1f29c336b73e6564894ad13386c25caa, type: 3} + laserAudio: {fileID: 8300000, guid: 9c84d32017426c64b89da338ac6e3267, type: 3} + bulletAudio: {fileID: 8300000, guid: 1f29c336b73e6564894ad13386c25caa, type: 3} diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_6004.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_6004.prefab index 3485136ed..43ec95da7 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_6004.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_FishEntry_6004.prefab @@ -1187,4 +1187,5 @@ MonoBehaviour: selfOffsetJGStart: {x: 13, y: 203} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -125, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: ec13aff0d55799149ad4623aa6a4c8be, type: 3} + laserAudio: {fileID: 8300000, guid: 1c60a8863ed13294c90a5917617006d8, type: 3} + bulletAudio: {fileID: 8300000, guid: ec13aff0d55799149ad4623aa6a4c8be, type: 3} diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab index 76c61ee60..358180723 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab @@ -1562,7 +1562,8 @@ MonoBehaviour: selfOffsetJGStart: {x: 30, y: 170} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -125, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: 67a9f36f11a381e4b8e2f31e62212364, type: 3} + laserAudio: {fileID: 8300000, guid: 3940837f653cae04896b7f69dd9c273e, type: 3} + bulletAudio: {fileID: 8300000, guid: 67a9f36f11a381e4b8e2f31e62212364, type: 3} --- !u!95 &8661363738911940257 stripped Animator: m_CorrespondingSourceObject: {fileID: 8284544360685858346, guid: ffe5b917cd8f21941adf54c437e9a701, diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14902_0_dev.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14902_0_dev.prefab index c592401c9..be7fa3fc6 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14902_0_dev.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14902_0_dev.prefab @@ -3797,7 +3797,8 @@ MonoBehaviour: selfOffsetJGStart: {x: 15, y: 150} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -125, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: 018b5393a4a0d0447af5d15e34d66fab, type: 3} + laserAudio: {fileID: 8300000, guid: f8def02954a080746aaccdc0f6513725, type: 3} + bulletAudio: {fileID: 8300000, guid: 018b5393a4a0d0447af5d15e34d66fab, type: 3} --- !u!95 &3827418405087986554 stripped Animator: m_CorrespondingSourceObject: {fileID: 2725459927999047066, guid: 83f830ab856bfc642b235330651c6513, diff --git a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab index 5c45d68e2..eddf65071 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab +++ b/Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab @@ -973,4 +973,5 @@ MonoBehaviour: selfOffsetJGStart: {x: 40, y: 200} buttomOffsetZSRotateInit: {x: 0, y: 0, z: 0} topOffsetZSRotateInit: {x: -125, y: 0, z: 0} - laserAudio: {fileID: 8300000, guid: 1d9f388857aa12f46b206c224734d00a, type: 3} + laserAudio: {fileID: 8300000, guid: d62315dc349b8a24e9185f0f0c4c5e11, type: 3} + bulletAudio: {fileID: 8300000, guid: 1d9f388857aa12f46b206c224734d00a, type: 3} diff --git a/Assets/MyGame/aa/Game/ZhanShen/ZhanShenTurretData.cs b/Assets/MyGame/aa/Game/ZhanShen/ZhanShenTurretData.cs index 64bd7cc05..04c0504ba 100644 --- a/Assets/MyGame/aa/Game/ZhanShen/ZhanShenTurretData.cs +++ b/Assets/MyGame/aa/Game/ZhanShen/ZhanShenTurretData.cs @@ -19,6 +19,8 @@ public class ZhanShenTurretData : TurretData [Tooltip("上方座位战神初始旋转(欧拉角,直接设置,覆盖该战神预制体原始旋转)。上下视角不一样分开设置。全0=保持默认朝向")] public Vector3 topOffsetZSRotateInit; - [Tooltip("战神激光开火音效:激光声音与普通开火不同,锁定时每发激光用 AudioManager.PlayFX(AudioClip) 直接播放;为空则回退普通开火音效")] + [Tooltip("战神激光音效(锁定中):声音较长,锁定时循环播放,松开锁定/激光消失即停止;为空则逐发回退 bulletAudio/bulletClip")] public AudioClip laserAudio; + [Tooltip("战神普通开火音效(未锁定时):不锁定开炮时逐发播放;为空则回退普通炮台音效 bulletClip")] + public AudioClip bulletAudio; } diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index 61ccf45a7..b77c2e87c 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -939,6 +939,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // ⭐ 清理未完成的 not-self 爆奖 pending(避免悬空引用) BaoJiangNotSelfHandler.ClearAll(); + // ⭐ 清理冰冻特效实例:Instantiate 无父对象 → 挂在场景根,面板销毁不会带走, + // 冰冻中切房会残留一块冰冻蒙皮,必须手动销毁 + if (_bingFengInstance != null) + { + Destroy(_bingFengInstance); + _bingFengInstance = null; + } + _frozenAnimSpeeds.Clear(); + } public virtual void Start() { @@ -3004,8 +3013,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } if (sit.posID==currSitData.posID) { - AudioManager.Instance.PlayFX(bulletClip); - + // ⭐ 战神普通开火音效(未锁定):优先读战神配置的 bulletAudio,未配置回退普通炮台音效 bulletClip + var zsData = sit.currentZhanShenData as ZhanShenTurretData; + AudioClip clip = (zsData != null && zsData.bulletAudio != null) ? zsData.bulletAudio : bulletClip; + if (clip != null) + AudioManager.Instance.PlayFX(clip); } } @@ -3058,12 +3070,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (sit.posID == currSitData.posID) { - // ⭐ 战神激光音效:激光声音与普通开火不同——直接读战神配置的 laserAudio 播放 - // (AudioManager.PlayFX 的 AudioClip 重载);未配置时回退普通开火音效 bulletClip - AudioClip laserClip = (sit.currentZhanShenData as ZhanShenTurretData)?.laserAudio; - if (laserClip == null) laserClip = bulletClip; - if (laserClip != null) - AudioManager.Instance.PlayFX(laserClip); + // ⭐ 战神激光音效:配置了 laserAudio 的战神,激光音效由 SitPosData 用循环 AudioSource 播放 + // (长音频循环,避免逐发播放重叠),此处不再逐发播; + // 未配置 laserAudio 时逐发回退:优先战神 bulletAudio,再退普通炮台 bulletClip + var zsData = sit.currentZhanShenData as ZhanShenTurretData; + if (zsData == null || zsData.laserAudio == null) + { + AudioClip clip = (zsData != null && zsData.bulletAudio != null) ? zsData.bulletAudio : bulletClip; + if (clip != null) + AudioManager.Instance.PlayFX(clip); + } } }