tools-unity-wwise
Wwise audio middleware integration for Unity including events, banks, RTPC, spatial audio, and callback handling.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Wwise audio middleware integration for Unity including events, banks, RTPC, spatial audio, and callback handling.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | tools-unity-wwise |
| description | Wwise audio middleware integration for Unity including events, banks, RTPC, spatial audio, and callback handling. |
Wwise is professional audio middleware providing advanced sound design capabilities. This skill covers Unity integration patterns for events, sound banks, real-time parameters, and spatial audio.
public class AudioBankManager : MonoBehaviour
{
[SerializeField] private AK.Wwise.Bank _initBank;
[SerializeField] private AK.Wwise.Bank[] _coreBanks;
private readonly HashSet<uint> _loadedBanks = new();
public async UniTask InitializeAudio()
{
// Load Init bank first (required)
await LoadBankAsync(_initBank);
// Load core banks
foreach (var bank in _coreBanks)
{
await LoadBankAsync(bank);
}
}
public async UniTask LoadBankAsync(AK.Wwise.Bank bank)
{
if (bank == null || _loadedBanks.Contains(bank.Id))
return;
var tcs = new UniTaskCompletionSource();
bank.Load(decodeBank: false, saveDecodedBank: false);
// Wait for bank to be ready
AKRESULT result = AkSoundEngine.LoadBank(
bank.Name,
out uint bankId
);
if (result == AKRESULT.AK_Success)
{
_loadedBanks.Add(bankId);
Debug.Log($"Loaded bank: {bank.Name}");
}
else
{
Debug.LogError($"Failed to load bank {bank.Name}: {result}");
}
}
public void UnloadBank(AK.Wwise.Bank bank)
{
if (bank == null) return;
bank.Unload();
_loadedBanks.Remove(bank.Id);
}
private void OnDestroy()
{
// Unload all banks on shutdown
foreach (var bank in _coreBanks)
{
UnloadBank(bank);
}
}
}
public class AsyncBankLoader
{
public static async UniTask<bool> LoadBankAsync(string bankName)
{
var tcs = new UniTaskCompletionSource<bool>();
AkSoundEngine.LoadBank(
bankName,
(uint)AkCallbackType.AK_BankLoaded,
(in_cookie, in_type, in_info) =>
{
var loadInfo = (AkBankCallbackInfo)in_info;
tcs.TrySetResult(loadInfo.loadResult == AKRESULT.AK_Success);
},
null,
out uint bankId
);
return await tcs.Task;
}
}
public class AudioEventPlayer : MonoBehaviour
{
[SerializeField] private AK.Wwise.Event _footstepEvent;
[SerializeField] private AK.Wwise.Event _attackEvent;
private uint _playingId;
public void PlayFootstep()
{
_footstepEvent.Post(gameObject);
}
public void PlayAttack()
{
// Store playing ID for potential stop
_playingId = _attackEvent.Post(gameObject);
}
public void StopAttack()
{
if (_playingId != 0)
{
AkSoundEngine.StopPlayingID(_playingId);
_playingId = 0;
}
}
public void StopAll()
{
AkSoundEngine.StopAll(gameObject);
}
}
public class AudioEventWithCallback : MonoBehaviour
{
[SerializeField] private AK.Wwise.Event _voiceEvent;
public event Action OnVoiceComplete;
public void PlayVoice()
{
_voiceEvent.Post(
gameObject,
(uint)(AkCallbackType.AK_EndOfEvent | AkCallbackType.AK_Marker),
OnAudioCallback
);
}
private void OnAudioCallback(
object in_cookie,
AkCallbackType in_type,
AkCallbackInfo in_info)
{
switch (in_type)
{
case AkCallbackType.AK_EndOfEvent:
OnVoiceComplete?.Invoke();
break;
case AkCallbackType.AK_Marker:
var markerInfo = (AkMarkerCallbackInfo)in_info;
HandleMarker(markerInfo.strLabel);
break;
}
}
private void HandleMarker(string label)
{
Debug.Log($"Audio marker: {label}");
// Sync animation, VFX, etc.
}
}
public static class WwiseAsyncExtensions
{
public static async UniTask PlayAndWaitAsync(
this AK.Wwise.Event wwiseEvent,
GameObject gameObject,
CancellationToken ct = default)
{
var tcs = new UniTaskCompletionSource();
uint playingId = wwiseEvent.Post(
gameObject,
(uint)AkCallbackType.AK_EndOfEvent,
(cookie, type, info) => tcs.TrySetResult()
);
if (playingId == 0)
{
Debug.LogWarning($"Failed to post event: {wwiseEvent.Name}");
return;
}
// Handle cancellation
using (ct.Register(() =>
{
AkSoundEngine.StopPlayingID(playingId);
tcs.TrySetCanceled();
}))
{
await tcs.Task;
}
}
}
// Usage
await _chargeSound.PlayAndWaitAsync(gameObject, _cts.Token);
public class AudioParameterController : MonoBehaviour
{
[SerializeField] private AK.Wwise.RTPC _healthRTPC;
[SerializeField] private AK.Wwise.RTPC _speedRTPC;
[SerializeField] private AK.Wwise.RTPC _tensionRTPC;
public void SetHealth(float normalizedHealth)
{
// Value 0-100 for health percentage
_healthRTPC.SetGlobalValue(normalizedHealth * 100f);
}
public void SetSpeed(float speed)
{
// Set on specific game object for 3D sounds
_speedRTPC.SetValue(gameObject, speed);
}
public void SetTension(float tension, float interpolationMs = 100f)
{
// Smooth interpolation
AkSoundEngine.SetRTPCValue(
_tensionRTPC.Id,
tension,
gameObject,
(int)interpolationMs
);
}
}
public class AudioAnimationSync : MonoBehaviour
{
[SerializeField] private AK.Wwise.RTPC _animationProgressRTPC;
private Animator _animator;
private void Update()
{
var stateInfo = _animator.GetCurrentAnimatorStateInfo(0);
float progress = stateInfo.normalizedTime % 1f;
_animationProgressRTPC.SetValue(gameObject, progress * 100f);
}
}
public class AudioStateManager : MonoBehaviour
{
[SerializeField] private AK.Wwise.State _combatState;
[SerializeField] private AK.Wwise.State _explorationState;
[SerializeField] private AK.Wwise.State _menuState;
public void EnterCombat()
{
_combatState.SetValue();
}
public void ExitCombat()
{
_explorationState.SetValue();
}
public void EnterMenu()
{
_menuState.SetValue();
}
}
public class AudioSwitchController : MonoBehaviour
{
[SerializeField] private AK.Wwise.Switch _surfaceSwitch;
// Different surface types
[SerializeField] private AK.Wwise.Switch _concreteSurface;
[SerializeField] private AK.Wwise.Switch _grassSurface;
[SerializeField] private AK.Wwise.Switch _waterSurface;
public void SetSurface(SurfaceType surface)
{
switch (surface)
{
case SurfaceType.Concrete:
_concreteSurface.SetValue(gameObject);
break;
case SurfaceType.Grass:
_grassSurface.SetValue(gameObject);
break;
case SurfaceType.Water:
_waterSurface.SetValue(gameObject);
break;
}
}
}
public class SpatialAudioSetup : MonoBehaviour
{
private AkGameObj _akGameObj;
private void Awake()
{
// Ensure AkGameObj component exists
_akGameObj = GetComponent<AkGameObj>();
if (_akGameObj == null)
{
_akGameObj = gameObject.AddComponent<AkGameObj>();
}
// Register with Wwise
AkSoundEngine.RegisterGameObj(gameObject, gameObject.name);
}
private void OnDestroy()
{
AkSoundEngine.UnregisterGameObj(gameObject);
}
}
public class AudioListenerSetup : MonoBehaviour
{
[SerializeField] private bool _isDefaultListener = true;
private void Awake()
{
// Register as game object
AkSoundEngine.RegisterGameObj(gameObject, "AudioListener");
if (_isDefaultListener)
{
// Set as default listener
ulong[] listeners = { AkSoundEngine.GetAkGameObjectID(gameObject) };
AkSoundEngine.SetDefaultListeners(listeners, 1);
}
}
private void OnDestroy()
{
AkSoundEngine.UnregisterGameObj(gameObject);
}
}
public class AudioRoom : MonoBehaviour
{
[SerializeField] private AkRoom _akRoom;
[SerializeField] private float _reverbLevel = 1f;
[SerializeField] private float _transmissionLoss = 0.5f;
private void Awake()
{
_akRoom = GetComponent<AkRoom>();
if (_akRoom == null)
{
_akRoom = gameObject.AddComponent<AkRoom>();
}
// Configure room
_akRoom.reverbLevel = _reverbLevel;
_akRoom.transmissionLoss = _transmissionLoss;
}
}
public class VolumeController : MonoBehaviour
{
[SerializeField] private AK.Wwise.RTPC _masterVolumeRTPC;
[SerializeField] private AK.Wwise.RTPC _musicVolumeRTPC;
[SerializeField] private AK.Wwise.RTPC _sfxVolumeRTPC;
[SerializeField] private AK.Wwise.RTPC _voiceVolumeRTPC;
public void SetMasterVolume(float volume)
{
_masterVolumeRTPC.SetGlobalValue(VolumeToDb(volume));
}
public void SetMusicVolume(float volume)
{
_musicVolumeRTPC.SetGlobalValue(VolumeToDb(volume));
}
public void SetSFXVolume(float volume)
{
_sfxVolumeRTPC.SetGlobalValue(VolumeToDb(volume));
}
public void SetVoiceVolume(float volume)
{
_voiceVolumeRTPC.SetGlobalValue(VolumeToDb(volume));
}
// Convert linear 0-1 to dB scale
private float VolumeToDb(float linear)
{
if (linear <= 0.0001f) return -96f; // Silence
return 20f * Mathf.Log10(linear);
}
public void Mute()
{
AkSoundEngine.SetRTPCValue("MasterVolume", -96f);
}
public void Unmute()
{
// Restore to saved value
SetMasterVolume(PlayerPrefs.GetFloat("MasterVolume", 1f));
}
}
public class WwiseAnimationEventHandler : MonoBehaviour
{
[SerializeField] private AK.Wwise.Event _footstepEvent;
[SerializeField] private AK.Wwise.Event _whooshEvent;
[SerializeField] private AK.Wwise.Event _impactEvent;
// Called from animation event
public void PlayFootstep()
{
_footstepEvent.Post(gameObject);
}
// Called from animation event with string parameter
public void PlaySoundByName(string eventName)
{
AkSoundEngine.PostEvent(eventName, gameObject);
}
// Called from animation event
public void PlayWhoosh()
{
_whooshEvent.Post(gameObject);
}
// Called from animation event
public void PlayImpact()
{
_impactEvent.Post(gameObject);
}
}
public class PooledAudioEmitter : MonoBehaviour, IPoolable
{
private uint _currentPlayingId;
public void OnSpawn()
{
AkSoundEngine.RegisterGameObj(gameObject);
}
public void OnDespawn()
{
if (_currentPlayingId != 0)
{
AkSoundEngine.StopPlayingID(_currentPlayingId);
_currentPlayingId = 0;
}
AkSoundEngine.UnregisterGameObj(gameObject);
}
public void PlayEvent(AK.Wwise.Event wwiseEvent)
{
_currentPlayingId = wwiseEvent.Post(gameObject);
}
}
public class AudioBatcher
{
private readonly List<(AK.Wwise.Event evt, GameObject go)> _pendingEvents = new();
private const int MaxEventsPerFrame = 10;
public void QueueEvent(AK.Wwise.Event evt, GameObject go)
{
_pendingEvents.Add((evt, go));
}
public void ProcessBatch()
{
int processed = 0;
while (_pendingEvents.Count > 0 && processed < MaxEventsPerFrame)
{
var (evt, go) = _pendingEvents[0];
_pendingEvents.RemoveAt(0);
if (go != null)
{
evt.Post(go);
}
processed++;
}
}
}
public static class WwiseSafeExtensions
{
public static uint PostSafe(this AK.Wwise.Event evt, GameObject go)
{
if (evt == null || !evt.IsValid())
{
Debug.LogWarning("Attempted to post invalid Wwise event");
return 0;
}
if (go == null)
{
Debug.LogWarning($"Attempted to post event {evt.Name} on null GameObject");
return 0;
}
return evt.Post(go);
}
public static void SetValueSafe(this AK.Wwise.RTPC rtpc, GameObject go, float value)
{
if (rtpc == null || !rtpc.IsValid())
{
Debug.LogWarning("Attempted to set invalid RTPC");
return;
}
rtpc.SetValue(go, value);
}
}
| Issue | Solution |
|---|---|
| No sound | Check bank loaded, game object registered |
| Event not found | Verify bank contains event, bank is loaded |
| RTPC not working | Check RTPC name matches Wwise project |
| 3D audio wrong | Verify listener setup, position updates |
| Memory issues | Unload unused banks |
| Clicks/pops | Use fade times, check sample rates |
Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.
Mobile-specific Unity optimization patterns for memory, battery, thermal, and performance.
End-of-story completion review. Reads the story file, verifies each acceptance criterion against the implementation, checks for GDD/ADR deviations, prompts code review, updates story status to Complete, and surfaces the next ready story from the sprint.
Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
Unity animation patterns including Animancer, state machines, and performance optimization.
Behavior Designer patterns for AI behavior trees including task creation, shared variables, conditionals, and debugging.