This commit is contained in:
2026-09-28 20:19:27 +08:00
parent d396870d37
commit 106c48cf82
12 changed files with 154 additions and 15 deletions
+42
View File
@@ -126,6 +126,48 @@ public class AudioManager : MonoSingleton<AudioManager>
}
}
private AudioSource _fxLoopSource;
/// <summary>
/// 循环音效专用 AudioSource(懒创建,与 FX 共用混音组)。
/// ⭐ 单独一条 source:Stop 只停循环音效,不会误杀 PlayOneShot 的一次性音效。
/// </summary>
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<AudioSource>();
_fxLoopSource.playOnAwake = false;
_fxLoopSource.loop = true;
if (fx != null) _fxLoopSource.outputAudioMixerGroup = fx.outputAudioMixerGroup;
}
return _fxLoopSource;
}
/// <summary>
/// 循环播放音效(如战神激光长音频)。重复调用同一条 clip 且已在播时不重头播放,只更新播放速度,
/// ⭐ 长音频用它代替逐发 PlayOneShot,避免声音重叠;pitch=2 即 2 倍速(狂暴)。
/// </summary>
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();
}
/// <summary>停止循环音效(不影响 PlayOneShot 的一次性音效)</summary>
public void StopFXLoop()
{
if (_fxLoopSource != null && _fxLoopSource.isPlaying)
_fxLoopSource.Stop();
}
/// <summary>
/// 按音效资源名(#Audio 表 AudioResourcesPath,如 PW_Shark_Show)播放音效。
/// 名字 → ID 走 tbaudio.json 映射,表 ID 调整时无需改代码。
@@ -443,6 +443,9 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
/// <summary>进入房间:初始化生成系统并绑定游戏面板。</summary>
public void EnterRoom(GameTypeEnum gameType)
{
// ⭐ 复位残留冰冻状态:冰冻中切房时旧面板倒计时未走完就销毁,
// 不会触发 UnfreezeAllFish → isFrozen 残留 true → 新房间 OnFishSpawn 全部被拦,不出鱼
ResetFreezeState();
InitSpawnSystem(gameType);
BindCurrentGamePanel();
EnsureBJEffectRoot();
@@ -1344,6 +1347,9 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
{
//await Task.Delay(2000);
// ⭐ 退房复位冰冻状态:冰冻中退房时旧面板倒计时未走完就销毁,
// 不会触发 UnfreezeAllFish → isFrozen 残留 true → 污染下一个房间不出鱼
ResetFreezeState();
spawnList = null;
spawnDic = null;
// ⭐ 退房清理排队中的出鱼事件,防止旧房间残留鱼泄进下一个房间
@@ -2333,6 +2339,23 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
}
#region 冰冻技能
/// <summary>
/// 复位冰冻状态(退房/进房时调用):
/// 清除 isFrozen 与冰冻记录,并停掉所有出鱼协程。
/// ⭐ 若不复位:冰冻中切换房间时,旧面板的 15s 解冻倒计时随面板一起销毁,
/// 永远不会调用 UnfreezeAllFish → isFrozen 残留 true →
/// 新房间 OnFishSpawn 第一行 `if (isFrozen) return;` 把服务端所有出鱼全部拦掉 → 新房间不出鱼。
/// ⭐ StopAllCoroutines:分帧出鱼协程里的 `while (isFrozen) yield return null;` 会一直挂起,
/// 解冻复位后会继续把旧房间排队的鱼生成到新房间,必须一并停掉。
/// </summary>
public void ResetFreezeState()
{
isFrozen = false;
frozenFishStates.Clear();
frozenFishIsMoves.Clear();
StopAllCoroutines();
}
/// <summary>
/// 冰冻:记录所有鱼当前状态并强制切到 Idle(暂停移动+动画)
/// </summary>
@@ -158,6 +158,10 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
private const float LaserFXWaitFallback = 2f;
/// <summary>上一帧是否有锁定点(检测锁定建立沿,触发准备等待)</summary>
private bool _laserLockActive;
/// <summary>激光循环音效是否正在播放(跟随激光实际显示状态起停,避免每帧重复触发)</summary>
private bool _laserSoundOn;
/// <summary>激光循环音效当前播放速度(1=正常,2=狂暴2倍速),用于边沿比较避免每帧重复调用</summary>
private float _laserSoundPitch = 1f;
/// <summary>预制体形态激光的基准缩放(重建时取自 laserPrefab.transform.localScale)</summary>
private Vector3 _laserFxBaseScale = Vector3.one;
/// <summary>预制体形态激光的角度偏移:激光预制体自带的 Z 轴旋转(重建时缓存,绕世界 Z 叠加在计算朝向上)</summary>
@@ -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;
}
/// <summary>
/// 激光循环音效:跟随"激光实际显示"状态起停(仅本地座位,且战神配置了 laserAudio 才播)。
/// ⭐ 用 AudioManager.PlayFXLoop 走专用循环 AudioSource——激光音效很长,逐发 PlayOneShot 会重叠;
/// 起停走状态边沿,锁定期间不重复触发;激光消失/清战神/座位禁用时由 DestroyZhanShenLaser 停止。
/// ⭐ 狂暴(暴走道具,开火速度翻倍)时同步把激光音效提到 2 倍速。
/// </summary>
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); // 已在播同一条时只变速,不重头播
}
/// <summary>本地座位当前是否狂暴(开火速度翻倍):狂暴标志由面板 SetFrenzy 维护在炮台/战神数据上</summary>
private bool IsFrenzyNow()
{
if (currentZhanShenData != null && currentZhanShenData.isFrenzy) return true;
return currentTurret != null && currentTurret.isFrenzy;
}
/// <summary>隐藏全部战神激光(两种形态都处理)</summary>
private void HideZhanShenLasers()
{
@@ -670,6 +714,12 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
// 准备动画等待状态复位(下次锁定重新计时)
_laserPrepWait = 0f;
_laserLockActive = false;
// 激光循环音效停止(仅本座位在播时才停,避免误停他人座位)
if (_laserSoundOn)
{
_laserSoundOn = false;
AudioManager.Instance.StopFXLoop();
}
}
// ==================== 激光起点/命中特效(三发+锁定) ====================
@@ -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}
@@ -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}
@@ -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}
@@ -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,
@@ -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,
@@ -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}
@@ -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;
}
@@ -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);
}
}
}