2026-06-27 12:52:03 -04:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using Sirenix.OdinInspector;
|
|
|
|
|
|
using SLSUtilities.Feedback;
|
|
|
|
|
|
using SLSUtilities.WwiseAssistance;
|
|
|
|
|
|
using Lean.Pool;
|
2026-07-18 03:16:20 -04:00
|
|
|
|
using UnityEngine.Serialization;
|
2026-06-27 12:52:03 -04:00
|
|
|
|
|
|
|
|
|
|
namespace Cielonos.MainGame.Effects.Feedback
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 音频反馈动作,在 Feedback 轨道上播放 Wwise 音频事件。
|
|
|
|
|
|
/// 支持跟随执行者(Play Attached)或在 3D 空间中指定位置播放,并支持打断时平滑淡出。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
2026-07-18 03:16:20 -04:00
|
|
|
|
[FormerlySerializedAs("fadeOutMs")]
|
2026-06-27 12:52:03 -04:00
|
|
|
|
[ShowIf("stopOnInterrupt")]
|
2026-07-18 03:16:20 -04:00
|
|
|
|
[LabelText("淡出时间 (s)")]
|
|
|
|
|
|
[Tooltip("被打断停止时的淡出时长(秒)。")]
|
|
|
|
|
|
public float fadeOut = 0.2f;
|
2026-06-27 12:52:03 -04:00
|
|
|
|
|
|
|
|
|
|
// 运行时状态记录,使用 Dictionary 保证并发/多角色播放时的实例安全
|
|
|
|
|
|
private readonly Dictionary<FeedbackPlayer, uint> _playingIDs = new Dictionary<FeedbackPlayer, uint>();
|
|
|
|
|
|
private readonly Dictionary<FeedbackPlayer, GameObject> _spawnedSoundObjects = new Dictionary<FeedbackPlayer, GameObject>();
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
{
|
2026-07-18 03:16:20 -04:00
|
|
|
|
if (playingID != AkUnitySoundEngine.AK_INVALID_PLAYING_ID)
|
2026-06-27 12:52:03 -04:00
|
|
|
|
{
|
2026-07-18 03:16:20 -04:00
|
|
|
|
AudioManager.Stop(playingID, fadeOut);
|
2026-06-27 12:52:03 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Cleanup(context);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void OnEnd(FeedbackContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
Cleanup(context);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Cleanup(FeedbackContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
_playingIDs.Remove(context.player);
|
|
|
|
|
|
_spawnedSoundObjects.Remove(context.player);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 空间音频的 Wwise 回调,在播放结束时自动回收临时音频点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|