using System; using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; using SLSUtilities.Feedback; using SLSUtilities.WwiseAssistance; using Lean.Pool; namespace Cielonos.MainGame.Effects.Feedback { /// /// 音频反馈动作,在 Feedback 轨道上播放 Wwise 音频事件。 /// 支持跟随执行者(Play Attached)或在 3D 空间中指定位置播放,并支持打断时平滑淡出。 /// [Serializable] [FeedbackActionColor(0.2f, 0.6f, 0.9f)] public class PostAudioAction : FeedbackActionBase { public override string DisplayName => "Post Audio"; [Required] [Tooltip("要播放的 Wwise 音频事件")] public AK.Wwise.Event wwiseEvent; [LabelText("跟随执行者播放")] [Tooltip("如果为 true,音效将绑定在执行者 GameObject 上移动;如果为 false,将在生成时的坐标固定播放 3D 音效。")] public bool playAttached = true; [LabelText("被打断时停止")] [Tooltip("如果为 true,反馈轨被打断(如受击、格挡)时,此音效会立刻停止/淡出。")] public bool stopOnInterrupt = true; [ShowIf("stopOnInterrupt")] [LabelText("淡出时间 (ms)")] [Tooltip("被打断停止时的淡出时长(毫秒)。")] public int fadeOutMs = 200; // 运行时状态记录,使用 Dictionary 保证并发/多角色播放时的实例安全 private readonly Dictionary _playingIDs = new Dictionary(); private readonly Dictionary _spawnedSoundObjects = new Dictionary(); public override void OnStart(FeedbackContext context) { if (wwiseEvent == null) return; GameObject ownerObj = context.owner != null ? context.owner.gameObject : null; uint playingID = 0; if (playAttached && ownerObj != null) { // 绑定到执行者播放 playingID = wwiseEvent.Post(ownerObj); if (playingID != 0) { _playingIDs[context.player] = playingID; } } else { // 3D 空间固定位置播放 Vector3 position = context.owner != null ? context.owner.position : Vector3.zero; if (AudioManager.Instance != null && AudioManager.Instance.audioPoint != null) { GameObject soundObj = LeanPool.Spawn(AudioManager.Instance.audioPoint, position, Quaternion.identity); _spawnedSoundObjects[context.player] = soundObj; playingID = wwiseEvent.Post(soundObj, (uint)AkCallbackType.AK_EndOfEvent, AutoDespawnCallback, context.player); if (playingID != 0) { _playingIDs[context.player] = playingID; soundObj.name = $"PostAudioAction-{wwiseEvent.Name}-Event{playingID}"; } else { // 播放失败,立即回收对象 LeanPool.Despawn(soundObj); _spawnedSoundObjects.Remove(context.player); } } } } public override void OnInterrupt(FeedbackContext context) { if (stopOnInterrupt) { if (_playingIDs.TryGetValue(context.player, out uint playingID)) { if (playingID != 0 && playingID != AkUnitySoundEngine.AK_INVALID_PLAYING_ID) { AudioManager.Stop(playingID, fadeOutMs); } } } Cleanup(context); } public override void OnEnd(FeedbackContext context) { Cleanup(context); } private void Cleanup(FeedbackContext context) { _playingIDs.Remove(context.player); _spawnedSoundObjects.Remove(context.player); } /// /// 空间音频的 Wwise 回调,在播放结束时自动回收临时音频点。 /// private void AutoDespawnCallback(object in_cookie, AkCallbackType in_type, AkCallbackInfo in_info) { if (in_type == AkCallbackType.AK_EndOfEvent) { FeedbackPlayer player = in_cookie as FeedbackPlayer; if (player != null) { if (_spawnedSoundObjects.TryGetValue(player, out GameObject soundObj)) { if (soundObj != null) { LeanPool.Despawn(soundObj); } _spawnedSoundObjects.Remove(player); } _playingIDs.Remove(player); } } } public override bool Validate(out string error) { if (wwiseEvent == null) { error = "Wwise Event is not assigned."; return false; } error = null; return true; } } }