├── CHANGELOG.md ├── .gitreview ├── CHANGELOG.md.meta ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Runtime.meta ├── Editor ├── Samples.meta ├── AK.Wwise.Unity.Addressables.Editor.asmdef.meta ├── SceneUpdater.cs.meta ├── AddressableMetadata.cs.meta ├── WwiseBankImporter.cs.meta ├── AddressableSymbolDefiner.cs.meta ├── BuildScriptWwisePacked.cs.meta ├── AkAddressablesEditorSettings.cs.meta ├── AkAddressablesEditorUtilities.cs.meta ├── WwiseAssetImportProcessors.cs.meta ├── WwiseStreamingAssetImporter.cs.meta ├── Samples │ ├── WwiseAssetMetadataPreserver.cs.meta │ └── WwiseAssetMetadataPreserver.cs ├── AK.Wwise.Unity.Addressables.Editor.asmdef ├── AddressableSymbolDefiner.cs ├── AddressableMetadata.cs ├── WwiseStreamingAssetImporter.cs ├── WwiseBankImporter.cs ├── BuildScriptWwisePacked.cs ├── SceneUpdater.cs ├── AkAddressablesEditorSettings.cs ├── AkAddressablesEditorUtilities.cs └── WwiseAssetImportProcessors.cs ├── Runtime ├── AK.Wwise.Unity.Addressables.asmdef.meta ├── WwiseAsset.cs.meta ├── AkAssetUtilities.cs.meta ├── InitBankHolder.cs.meta ├── WwiseInitBankReference.cs.meta ├── WwiseSoundBankAsset.cs.meta ├── AkAddressableBankManager.cs.meta ├── WwiseAddressableSoundBank.cs.meta ├── WwiseStreamingMediaAsset.cs.meta ├── AkAddressablesSoundEngineInitialization.cs.meta ├── AkWwiseAddressablesInitializationSettings.cs.meta ├── AK.Wwise.Unity.Addressables.asmdef ├── WwiseStreamingMediaAsset.cs ├── WwiseAsset.cs ├── WwiseSoundBankAsset.cs ├── InitBankHolder.cs ├── WwiseInitBankReference.cs ├── AkAddressablesSoundEngineInitialization.cs ├── AkWwiseAddressablesInitializationSettings.cs ├── AkAssetUtilities.cs ├── WwiseAddressableSoundBank.cs └── AkAddressableBankManager.cs ├── README.md ├── LICENSE.md └── package.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=git.hq.audiokinetic.com 3 | project=WwiseUnityAddressables 4 | defaultremote=origin -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e25cefb3100359247b9f58e8d43410a1 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dd480b869eef8ff4b84c0381ec17e4e6 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c0adf57793c6104588feb44ef4ef219 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82c5872aa1354d24da44c647accf9891 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75de3229038472248a29fa473c63184a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8c71676ebe776247a0ed5d05ca67b73 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Samples.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d966cb49e043f6542a8c28bc530750bf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AK.Wwise.Unity.Addressables.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ed608e52440be241a83e7b07717a618 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/AK.Wwise.Unity.Addressables.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 005b486a0b1ff814abc66a82ac38965e 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/SceneUpdater.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66504e0bed1a72b4f9db701abd4f1b76 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WwiseAsset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd8810706e62a9e41ab234519642db6d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AddressableMetadata.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 613af5da8070b134ca7e9841a4c5eac2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/WwiseBankImporter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 010ff325a4f1dc3468c3376f72e23da1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AkAssetUtilities.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e2b2e6f2063987d40adbe971ab82393f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/InitBankHolder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed39f2b237e573f4f85990bb5d1440ac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AddressableSymbolDefiner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8b83d0297902d246963546fadf43bf6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BuildScriptWwisePacked.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 85d735f7d0ef4d5499a3be7ccdde6e01 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WwiseInitBankReference.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9289c9e765809ec408877f7c7b8d296d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WwiseSoundBankAsset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15fd9cd4296478441b466948a802a886 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AkAddressablesEditorSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c878185e4dc06f9419335005707a63a6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AkAddressablesEditorUtilities.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5bae130b19e387942ada5e56b59d5fe0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/WwiseAssetImportProcessors.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e47a253c01102734b9eded5034bda5d1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/WwiseStreamingAssetImporter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8f9069cf83cff243b0eb88f830dbdbf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AkAddressableBankManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 409951b9c1e10ac43b2c01e7e97702bb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WwiseAddressableSoundBank.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3213ed949058a0d47826c5f3bd819e6d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WwiseStreamingMediaAsset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 649a74834ac549240bc460ca08ac79ca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Samples/WwiseAssetMetadataPreserver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7abb5df6682f6ef4da86a53699655c1c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AkAddressablesSoundEngineInitialization.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff4f8dbbc8dc4644fbe58cf88e80cf59 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AkWwiseAddressablesInitializationSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6f444e2f1a81dc54cab58569a7ca372c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Wwise Addressables 2 | 3 | This package adds support for distributing and loading Wwise assets using the Unity Addressables System. 4 | 5 | For more information about installing and using this package, please consult the [official documentation page](https://www.audiokinetic.com/library/edge/?source=Unity&id=pg_addressables.html). 6 | 7 | For beta users, refer to the documentation provided with the software. 8 | 9 | ## Legal 10 | 11 | Copyright © 2024 Audiokinetic Inc. All rights reserved. 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | The content of the files in this repository include portions of the AUDIOKINETIC 3 | Wwise Technology released in source code form as part of the SDK package. 4 | 5 | Commercial License Usage 6 | 7 | Licensees holding valid commercial licenses to the AUDIOKINETIC Wwise Technology 8 | may use these files in accordance with the end user license agreement provided 9 | with the software or, alternatively, in accordance with the terms contained in a 10 | written agreement between you and Audiokinetic Inc. 11 | 12 | Copyright (c) 2025 Audiokinetic Inc. 13 | ******************************************************************************** 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.audiokinetic.wwise.addressables", 3 | "version": "2024.1.10", 4 | "displayName": "Wwise Unity Addressables", 5 | "description": "Wwise Unity Addressables. We recommend that you obtain the package through GitHub. Append the version number of your Wwise Unity Integration version, in #XX.X.X format, to the URL. For example, https://github.com/audiokinetic/WwiseUnityAddressables.git#v24.1.0. To update the package, we recommend that you use the Wwise Addressables Updater.", 6 | "unity": "2021.3", 7 | "unityRelease": "32f1", 8 | "dependencies": { 9 | "com.unity.addressables": "2.4.6" 10 | }, 11 | "keywords": [ 12 | "Wwise", 13 | "wwise", 14 | "audiokinetic", 15 | "ak", 16 | "Audiokinetic" 17 | ], 18 | "author": { 19 | "name": "Audiokinetic Inc.", 20 | "email": "info@audiokinetic.com", 21 | "url": "https://www.audiokinetic.com" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Runtime/AK.Wwise.Unity.Addressables.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AK.Wwise.Unity.Addressables", 3 | "rootNamespace": "", 4 | "references": [ 5 | "Unity.Addressables", 6 | "Unity.ResourceManager", 7 | "AK.Wwise.Unity.API", 8 | "AK.Wwise.Unity.ProjectDB", 9 | "AK.Wwise.Unity.Utilities" 10 | ], 11 | "includePlatforms": [], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": false, 14 | "overrideReferences": false, 15 | "precompiledReferences": [], 16 | "autoReferenced": true, 17 | "defineConstraints": [ "!UNITY_SERVER" ], 18 | "versionDefines": [ 19 | { 20 | "name": "com.unity.addressables", 21 | "expression": "1.8", 22 | "define": "UNITY_ADDRESSABLES" 23 | }, 24 | { 25 | "name": "com.audiokinetic.wwise.addressables", 26 | "expression": "1.0.0", 27 | "define": "AK_WWISE_ADDRESSABLES" 28 | } 29 | ], 30 | "noEngineReferences": false 31 | } -------------------------------------------------------------------------------- /Editor/AK.Wwise.Unity.Addressables.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AK.Wwise.Unity.Addressables.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "AK.Wwise.Unity.MonoBehaviour.Editor", 6 | "Unity.Addressables.Editor", 7 | "Unity.Addressables", 8 | "Unity.ScriptableBuildPipeline.Editor", 9 | "Unity.ResourceManager", 10 | "Unity.ScriptableBuildPipeline", 11 | "AK.Wwise.Unity.API", 12 | "AK.Wwise.Unity.Addressables", 13 | "AK.Wwise.Unity.API.WwiseTypes", 14 | "AK.Wwise.Unity.ProjectDatabase", 15 | "AK.Wwise.Unity.Utilities" 16 | ], 17 | "includePlatforms": [ 18 | "Editor" 19 | ], 20 | "excludePlatforms": [], 21 | "allowUnsafeCode": false, 22 | "overrideReferences": false, 23 | "precompiledReferences": [], 24 | "autoReferenced": true, 25 | "defineConstraints": [ "!UNITY_SERVER" ], 26 | "versionDefines": [ 27 | { 28 | "name": "com.unity.addressables", 29 | "expression": "1.8", 30 | "define": "UNITY_ADDRESSABLES" 31 | }, 32 | { 33 | "name": "com.audiokinetic.wwise.addressables", 34 | "expression": "1.0.0", 35 | "define": "AK_WWISE_ADDRESSABLES" 36 | } 37 | ], 38 | "noEngineReferences": false 39 | } -------------------------------------------------------------------------------- /Runtime/WwiseStreamingMediaAsset.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | using System.IO; 19 | using UnityEngine; 20 | 21 | namespace AK.Wwise.Unity.WwiseAddressables 22 | { 23 | [PreferBinarySerialization] 24 | public class WwiseStreamingMediaAsset : WwiseAsset 25 | { 26 | public override string GetRelativeFilePath() 27 | { 28 | return language == "SFX" ? name + ".wem" : Path.Combine(language, name + ".wem"); 29 | } 30 | 31 | public string GetName() 32 | { 33 | return name; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Editor/AddressableSymbolDefiner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEditor; 4 | 5 | [InitializeOnLoad] 6 | public static class AddressableSymbolDefiner 7 | { 8 | private const string CurrentVersion = "WWISE_ADDRESSABLES_24_1_OR_LATER"; 9 | 10 | static AddressableSymbolDefiner() 11 | { 12 | 13 | if (PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone).Contains(CurrentVersion)) 14 | { 15 | return; 16 | } 17 | #if !WWISE_2024_OR_LATER 18 | return; 19 | #endif 20 | foreach (BuildTargetGroup targetGroup in Enum.GetValues(typeof(BuildTargetGroup))) 21 | { 22 | if (targetGroup != BuildTargetGroup.Unknown) 23 | { 24 | AddDefineSymbols(targetGroup); 25 | } 26 | } 27 | } 28 | 29 | private static void AddDefineSymbols(BuildTargetGroup targetGroup) 30 | { 31 | string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroup); 32 | var currentDefineList = currentDefines.Split(';').ToList(); 33 | 34 | if (!currentDefineList.Contains(CurrentVersion)) 35 | { 36 | currentDefineList.Add(CurrentVersion); 37 | string updatedDefines = string.Join(";", currentDefineList); 38 | PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, updatedDefines); 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Runtime/WwiseAsset.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using UnityEngine; 21 | 22 | [PreferBinarySerialization] 23 | public abstract class WwiseAsset : ScriptableObject 24 | { 25 | [SerializeField] 26 | [HideInInspector] 27 | public byte[] RawData; 28 | 29 | [SerializeField] 30 | [HideInInspector] 31 | public byte[] hash; 32 | 33 | [SerializeField] 34 | [HideInInspector] 35 | public string language; 36 | 37 | abstract public string GetRelativeFilePath(); 38 | 39 | public int AssetSize 40 | { 41 | get 42 | { 43 | return RawData.Length; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Runtime/WwiseSoundBankAsset.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | using System.Collections.Generic; 19 | using System.IO; 20 | using UnityEngine; 21 | 22 | namespace AK.Wwise.Unity.WwiseAddressables 23 | { 24 | [PreferBinarySerialization] 25 | public class WwiseSoundBankAsset: WwiseAsset 26 | { 27 | [SerializeField] 28 | public List eventNames; 29 | 30 | [SerializeField] public bool isAutoBank; 31 | 32 | public override string GetRelativeFilePath() 33 | { 34 | return language == "SFX" ? name + ".bnk" : Path.Combine(language, name + ".bnk"); 35 | } 36 | 37 | public string GetName() 38 | { 39 | return name; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Editor/AddressableMetadata.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | using System.Collections.Generic; 19 | using UnityEngine; 20 | using UnityEditor; 21 | 22 | 23 | namespace AK.Wwise.Unity.WwiseAddressables 24 | { 25 | public class AddressableMetadata : UnityEngine.ScriptableObject 26 | { 27 | public string groupName; 28 | public List labels = new List(); 29 | 30 | public AddressableMetadata() 31 | { 32 | labels = new List(labels); 33 | } 34 | 35 | public AddressableMetadata(List inLabels, string inGroupName) 36 | { 37 | labels = inLabels; 38 | groupName = inGroupName; 39 | } 40 | 41 | public bool IsDifferent(AddressableMetadata other) 42 | { 43 | return other.groupName != groupName || other.labels != labels; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Runtime/InitBankHolder.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using UnityEngine; 21 | 22 | namespace AK.Wwise.Unity.WwiseAddressables 23 | { 24 | public class InitBankHolder : MonoBehaviour 25 | { 26 | public WwiseInitBankReference InitBank; 27 | 28 | public WwiseAddressableSoundBank GetAddressableInitBank() 29 | { 30 | if (InitBank == null) 31 | { 32 | #if UNITY_EDITOR 33 | var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(WwiseInitBankReference).Name, 34 | new string[] {AkWwiseEditorSettings.WwiseScriptableObjectRelativePath}); 35 | if (guids.Length >0) 36 | { 37 | var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[0]); 38 | InitBank = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); 39 | if (InitBank) 40 | { 41 | return InitBank.AddressableBank; 42 | } 43 | } 44 | #endif 45 | return null; 46 | } 47 | 48 | return InitBank.AddressableBank; 49 | } 50 | } 51 | } 52 | 53 | #endif //AK_WWISE_ADDRESSABLES -------------------------------------------------------------------------------- /Editor/WwiseStreamingAssetImporter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System.Collections; 21 | using System.Collections.Generic; 22 | 23 | using UnityEngine; 24 | using System.IO; 25 | using System.Security.Cryptography; 26 | 27 | using UnityEditor.AssetImporters; 28 | 29 | namespace AK.Wwise.Unity.WwiseAddressables 30 | { 31 | [ScriptedImporter(1, "wem")] 32 | public class WwiseStreamingAssetImporter : ScriptedImporter 33 | { 34 | public override void OnImportAsset(AssetImportContext ctx) 35 | { 36 | string assetName = Path.GetFileNameWithoutExtension(ctx.assetPath); 37 | 38 | string platform; 39 | string language; 40 | string type; 41 | AkAddressablesEditorUtilities.ParseAssetPath(ctx.assetPath, out platform, out language, out type); 42 | 43 | if (platform == null) 44 | { 45 | return; 46 | } 47 | 48 | WwiseStreamingMediaAsset dataAsset = ScriptableObject.CreateInstance(); 49 | dataAsset.RawData = File.ReadAllBytes(Path.GetFullPath(ctx.assetPath)); 50 | byte[] hash = MD5.Create().ComputeHash(dataAsset.RawData); 51 | dataAsset.hash = hash; 52 | dataAsset.language = language; 53 | 54 | ctx.AddObjectToAsset(string.Format("WwiseSteamingMedia_{0}{1}_{2}", platform, language, assetName), dataAsset); 55 | ctx.SetMainObject(dataAsset); 56 | } 57 | } 58 | } 59 | #endif -------------------------------------------------------------------------------- /Runtime/WwiseInitBankReference.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System.Collections; 21 | using System.Collections.Generic; 22 | using AK.Wwise.Unity.WwiseAddressables; 23 | using UnityEditor; 24 | using UnityEngine; 25 | 26 | namespace AK.Wwise.Unity.WwiseAddressables 27 | { 28 | public class WwiseInitBankReference : ScriptableObject 29 | { 30 | public WwiseAddressableSoundBank AddressableBank; 31 | public const string InitBankName = "Init"; 32 | 33 | #if UNITY_EDITOR 34 | 35 | public void OnEnable() 36 | { 37 | AkAssetUtilities.AddressableBankUpdated += UpdateAddressableBankReference; 38 | } 39 | 40 | public void SetAddressableBank(WwiseAddressableSoundBank asset) 41 | { 42 | if (asset != null) 43 | { 44 | AddressableBank = asset; 45 | EditorUtility.SetDirty(this); 46 | AssetDatabase.SaveAssets(); 47 | AssetDatabase.Refresh(); 48 | } 49 | } 50 | 51 | public bool UpdateAddressableBankReference(WwiseAddressableSoundBank asset, string name) 52 | { 53 | if (this == null) 54 | { 55 | return false; 56 | } 57 | if (name == InitBankName) 58 | { 59 | SetAddressableBank(asset); 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | 67 | public static bool FindInitBankReferenceAndSetAddressableBank(WwiseAddressableSoundBank addressableAsset, 68 | string name) 69 | { 70 | WwiseInitBankReference asset; 71 | var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(WwiseInitBankReference).Name, 72 | new string[] {AkWwiseEditorSettings.WwiseScriptableObjectRelativePath}); 73 | foreach (var assetGuid in guids) 74 | { 75 | var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assetGuid); 76 | asset = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); 77 | if (asset) 78 | { 79 | asset.SetAddressableBank(addressableAsset); 80 | } 81 | } 82 | 83 | return guids.Length > 0; 84 | } 85 | 86 | public void OnDestroy() 87 | { 88 | AkAssetUtilities.AddressableBankUpdated -= UpdateAddressableBankReference; 89 | } 90 | #endif 91 | } 92 | } 93 | #endif -------------------------------------------------------------------------------- /Runtime/AkAddressablesSoundEngineInitialization.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using UnityEngine; 21 | 22 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES && (WWISE_ADDRESSABLES_23_1_OR_LATER || WWISE_ADDRESSABLES_POST_2023) 23 | #if WWISE_2024_OR_LATER 24 | public class AkUnityAddressablesSoundEngineInitialization : AkUnitySoundEngineInitialization 25 | #else 26 | public class AkAddressablesSoundEngineInitialization : AkSoundEngineInitialization 27 | #endif 28 | { 29 | public static void ResetInstance() 30 | { 31 | if(m_Instance != null) 32 | { 33 | System.Action copyInitialize = m_Instance.initializationDelegate; 34 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 35 | System.Action copyReInitialize = m_Instance.reInitializationDelegate; 36 | #endif 37 | System.Action copyTerminate = m_Instance.terminationDelegate; 38 | #if WWISE_2024_OR_LATER 39 | m_Instance = new AkUnityAddressablesSoundEngineInitialization(); 40 | #else 41 | m_Instance = new AkAddressablesSoundEngineInitialization(); 42 | #endif 43 | m_Instance.initializationDelegate = copyInitialize; 44 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 45 | m_Instance.reInitializationDelegate = copyReInitialize; 46 | #endif 47 | m_Instance.terminationDelegate = copyTerminate; 48 | } 49 | else 50 | { 51 | #if WWISE_2024_OR_LATER 52 | m_Instance = new AkUnityAddressablesSoundEngineInitialization(); 53 | #else 54 | m_Instance = new AkAddressablesSoundEngineInitialization(); 55 | #endif 56 | } 57 | } 58 | 59 | protected override void LoadInitBank() 60 | { 61 | AK.Wwise.Unity.WwiseAddressables.AkAddressableBankManager.Instance.LoadInitBank(AkWwiseInitializationSettings.Instance.LoadBanksAsynchronously); 62 | } 63 | 64 | protected override void ClearBanks() 65 | { 66 | AK.Wwise.Unity.WwiseAddressables.AkAddressableBankManager.Instance.UnloadAllBanks(clearBankDictionary: false); 67 | AK.Wwise.Unity.WwiseAddressables.AkAddressableBankManager.Instance.UnloadInitBank(); 68 | } 69 | 70 | protected override void ResetBanks() 71 | { 72 | AK.Wwise.Unity.WwiseAddressables.AkAddressableBankManager.Instance.UnloadAllBanks(clearBankDictionary: false); 73 | } 74 | } 75 | #endif -------------------------------------------------------------------------------- /Editor/WwiseBankImporter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System.Collections.Generic; 21 | using System.Security.Cryptography; 22 | using System.IO; 23 | using System.Threading.Tasks; 24 | using UnityEngine; 25 | 26 | using UnityEditor.AssetImporters; 27 | 28 | namespace AK.Wwise.Unity.WwiseAddressables 29 | { 30 | [ScriptedImporter(1, "bnk")] 31 | public class WwiseBankImporter : ScriptedImporter 32 | { 33 | public override void OnImportAsset(AssetImportContext ctx) 34 | { 35 | ImportAssetAsync(ctx); 36 | } 37 | 38 | private async Task ImportAssetAsync(AssetImportContext ctx) 39 | { 40 | string assetName = Path.GetFileNameWithoutExtension(ctx.assetPath); 41 | 42 | string platform; 43 | string language; 44 | string type; 45 | AkAddressablesEditorUtilities.ParseAssetPath(ctx.assetPath, out platform, out language, out type); 46 | bool isAutoBank = type != "User"; 47 | 48 | if (platform == null) 49 | { 50 | Debug.LogWarning($"Skipping {ctx.assetPath} as its platform couldn't be determined. Make sure it is placed in the appropriate platform folder."); 51 | return; 52 | } 53 | 54 | var soundbankInfos = await AkAddressablesEditorUtilities.ParsePlatformSoundbanks(platform, assetName, language, type); 55 | 56 | if (soundbankInfos == null) 57 | { 58 | Debug.LogWarning($"Skipping {ctx.assetPath}. SoundbanksInfo.xml could not be parsed."); 59 | return; 60 | } 61 | 62 | if (!soundbankInfos.ContainsKey((assetName,type))) 63 | { 64 | Debug.LogWarning($"Skipping {ctx.assetPath} as it was not parsed in SoundbanksInfo.xml. Perhaps this bank no longer exists in the wwise project?"); 65 | return; 66 | } 67 | WwiseSoundBankAsset dataAsset = ScriptableObject.CreateInstance(); 68 | dataAsset.RawData = File.ReadAllBytes(Path.GetFullPath(ctx.assetPath)); 69 | dataAsset.language = language; 70 | dataAsset.isAutoBank = isAutoBank; 71 | var eventNames = soundbankInfos[(assetName,type)][language].events; 72 | if (language !="SFX" && soundbankInfos[(assetName,type)].ContainsKey("SFX")) 73 | { 74 | eventNames.AddRange(soundbankInfos[(assetName,type)]["SFX"].events); 75 | } 76 | dataAsset.eventNames = eventNames; 77 | byte[] hash = MD5.Create().ComputeHash(dataAsset.RawData); 78 | dataAsset.hash = hash; 79 | ctx.AddObjectToAsset(string.Format("WwiseBank_{0}{1}_{2}", platform, language, assetName), dataAsset); 80 | ctx.SetMainObject(dataAsset); 81 | } 82 | } 83 | } 84 | #endif -------------------------------------------------------------------------------- /Editor/BuildScriptWwisePacked.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2024 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System; 21 | using UnityEditor; 22 | using UnityEditor.AddressableAssets; 23 | using UnityEditor.AddressableAssets.Build.DataBuilders; 24 | using UnityEditor.AddressableAssets.Settings; 25 | using UnityEditor.AddressableAssets.Settings.GroupSchemas; 26 | using UnityEngine; 27 | 28 | namespace AK.Wwise.Unity.WwiseAddressables 29 | { 30 | /* 31 | * The wwise build script ONLY builds the bundles automatically generated groupes (e.g. WwiseData_[platform] and WwiseData_[platform]_InitBank) for the target build platform. 32 | */ 33 | [CreateAssetMenu(fileName = "BuildScriptWwisePacked.asset", menuName = "Addressables/Content Builders/Wwise Build Script")] 34 | public class BuildScriptWwisePacked : BuildScriptPackedMode 35 | { 36 | /// 37 | public override string Name 38 | { 39 | get 40 | { 41 | return "Wwise Build Script"; 42 | } 43 | } 44 | /// 45 | /// 46 | protected override string ProcessGroup(AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) 47 | { 48 | if (assetGroup == null) 49 | return string.Empty; 50 | 51 | var buildTarget = (BuildTarget) Enum.Parse(typeof(BuildTarget), aaContext.runtimeData.BuildTarget); 52 | IncludePlatformSpecificBundles(buildTarget); 53 | 54 | foreach (var schema in assetGroup.Schemas) 55 | { 56 | var errorString = ProcessGroupSchema(schema, assetGroup, aaContext); 57 | if (!string.IsNullOrEmpty(errorString)) 58 | return errorString; 59 | } 60 | 61 | return string.Empty; 62 | } 63 | 64 | private void IncludePlatformSpecificBundles(UnityEditor.BuildTarget target) 65 | { 66 | var wwisePlatform = AkAddressablesEditorUtilities.GetWwisePlatformNameFromBuildTarget(target); 67 | 68 | var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; 69 | 70 | if (addressableSettings == null) 71 | { 72 | UnityEngine.Debug.LogWarningFormat("[Addressables] settings file not found.\nPlease go to Menu/Window/Asset Management/Addressables/Groups, then click 'Create Addressables Settings' button."); 73 | return; 74 | } 75 | 76 | foreach (var group in addressableSettings.groups) 77 | { 78 | var include = false; 79 | 80 | if (group.Name.Contains("WwiseData")) 81 | { 82 | if (group.Name.Contains(wwisePlatform)) 83 | { 84 | include = true; 85 | } 86 | 87 | var bundleSchema = group.GetSchema(); 88 | if (bundleSchema != null) 89 | bundleSchema.IncludeInBuild = include; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | #endif -------------------------------------------------------------------------------- /Editor/SceneUpdater.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2024 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | #if UNITY_EDITOR 18 | using UnityEditor; 19 | using UnityEngine; 20 | using UnityEditor.SceneManagement; 21 | using UnityEngine.SceneManagement; 22 | using System.Collections.Generic; 23 | using System.Linq; 24 | public class SceneUpdater 25 | { 26 | private static bool hierarchyChanged = false; 27 | [UnityEditor.MenuItem("Tools/Reload All Scenes")] 28 | public static void ReloadAllScenes() 29 | { 30 | EditorApplication.hierarchyChanged += OnHierarchyChanged; 31 | EditorApplication.update += Update; 32 | // Start the process 33 | ReloadNextScene(); 34 | } 35 | 36 | private static void OnHierarchyChanged() 37 | { 38 | hierarchyChanged = true; 39 | } 40 | 41 | public static bool SceneIsInPackage(string scenePath) 42 | { 43 | if (string.IsNullOrEmpty(scenePath)) 44 | return false; 45 | 46 | return scenePath.StartsWith("Packages/"); 47 | } 48 | 49 | private static List scenePaths; 50 | private static int currentSceneIndex = 0; 51 | private static bool lastSceneWasInPackage = false; 52 | 53 | private static void ReloadNextScene() 54 | { 55 | if (scenePaths == null) 56 | { 57 | string[] sceneGuids = AssetDatabase.FindAssets("t:Scene"); 58 | scenePaths = sceneGuids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToList(); 59 | } 60 | if (currentSceneIndex < scenePaths.Count) 61 | { 62 | string scenePath = scenePaths[currentSceneIndex]; 63 | if (SceneIsInPackage(scenePath)) 64 | { 65 | lastSceneWasInPackage = true; 66 | } 67 | else 68 | { 69 | Scene scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); 70 | lastSceneWasInPackage = false; 71 | hierarchyChanged = false; 72 | } 73 | } 74 | else 75 | { 76 | EditorApplication.hierarchyChanged -= OnHierarchyChanged; 77 | EditorApplication.update -= Update; 78 | if (UnityEditorInternal.InternalEditorUtility.inBatchMode) 79 | { 80 | EditorApplication.Exit(0); 81 | } 82 | else 83 | { 84 | EditorUtility.DisplayDialog("Reload All Scenes", "All scenes have been reloaded.", "OK"); 85 | } 86 | } 87 | } 88 | 89 | private static void Update() 90 | { 91 | if (hierarchyChanged || lastSceneWasInPackage) 92 | { 93 | if (hierarchyChanged) 94 | { 95 | if (UnityEditorInternal.InternalEditorUtility.inBatchMode) 96 | { 97 | AkWwisePostImportCallbackSetup.CheckWwiseGlobalExistance(); 98 | } 99 | Scene scene = SceneManager.GetActiveScene(); 100 | EditorSceneManager.SaveScene(scene); 101 | } 102 | currentSceneIndex++; 103 | ReloadNextScene(); 104 | } 105 | } 106 | } 107 | #endif -------------------------------------------------------------------------------- /Runtime/AkWwiseAddressablesInitializationSettings.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | namespace AK.Wwise.Unity.WwiseAddressables 21 | { 22 | public class AkWwiseAddressablesInitializationSettings : AkWwiseInitializationSettings 23 | { 24 | private static AkWwiseAddressablesInitializationSettings m_Instance = null; 25 | 26 | public new static AkWwiseAddressablesInitializationSettings Instance 27 | { 28 | get 29 | { 30 | if (m_Instance == null) 31 | { 32 | #if WWISE_ADDRESSABLES_POST_2023 || WWISE_ADDRESSABLES_23_1_OR_LATER 33 | #if WWISE_2024_OR_LATER 34 | AkUnityAddressablesSoundEngineInitialization.ResetInstance(); 35 | #else 36 | AkAddressablesSoundEngineInitialization.ResetInstance(); 37 | #endif //WWISE_2024_OR_LATER 38 | #endif //WWISE_ADDRESSABLES_POST_2023 || WWISE_ADDRESSABLES_23_1_OR_LATER 39 | #if UNITY_EDITOR 40 | var name = typeof(AkWwiseInitializationSettings).Name; 41 | var className = typeof(AkWwiseAddressablesInitializationSettings).Name; 42 | m_Instance = ReplaceOrCreateAsset(className, name); 43 | #else 44 | m_Instance = (AkWwiseAddressablesInitializationSettings) CreateInstance(); 45 | UnityEngine.Debug.LogWarning("WwiseUnity: No platform specific settings were created. Default initialization settings will be used."); 46 | #endif 47 | } 48 | 49 | return m_Instance; 50 | } 51 | } 52 | 53 | #if !(WWISE_ADDRESSABLES_POST_2023 || WWISE_ADDRESSABLES_23_1_OR_LATER) 54 | protected override void LoadInitBank() 55 | { 56 | AkAddressableBankManager.Instance.LoadInitBank(); 57 | } 58 | 59 | protected override void ClearBanks() 60 | { 61 | AkAddressableBankManager.Instance.UnloadAllBanks(clearBankDictionary: false); 62 | AkAddressableBankManager.Instance.UnloadInitBank(); 63 | } 64 | #endif 65 | 66 | 67 | #if UNITY_EDITOR 68 | public static AkWwiseAddressablesInitializationSettings ReplaceOrCreateAsset(string className, string fileName) 69 | { 70 | var path = System.IO.Path.Combine(AkWwiseEditorSettings.WwiseScriptableObjectRelativePath, fileName + ".asset"); 71 | var assetExists = string.IsNullOrEmpty(UnityEditor.AssetDatabase.AssetPathToGUID(path)); 72 | if (assetExists) 73 | { 74 | var loadedAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); 75 | if (loadedAsset != null) 76 | { 77 | return loadedAsset; 78 | } 79 | else //overwrite current InitializationSettings asset with the addressables one 80 | { 81 | UnityEditor.AssetDatabase.DeleteAsset(path); 82 | var newAsset = CreateInstance(); 83 | UnityEditor.AssetDatabase.CreateAsset(newAsset, path); 84 | return newAsset; 85 | } 86 | } 87 | 88 | var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(AkWwiseAddressablesInitializationSettings).Name); 89 | foreach (var assetGuid in guids) 90 | { 91 | var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assetGuid); 92 | var foundAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); 93 | if (foundAsset) 94 | return foundAsset; 95 | } 96 | 97 | var createdAsset = CreateInstance(); 98 | AkUtilities.CreateFolder(AkWwiseEditorSettings.WwiseScriptableObjectRelativePath); 99 | UnityEditor.AssetDatabase.CreateAsset(createdAsset, path); 100 | return createdAsset; 101 | } 102 | #endif 103 | } 104 | } 105 | #endif // AK_WWISE_ADDRESSABLES 106 | -------------------------------------------------------------------------------- /Runtime/AkAssetUtilities.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System.IO; 21 | #if UNITY_EDITOR 22 | using UnityEditor; 23 | using UnityEngine; 24 | using System.Reflection; 25 | #endif 26 | 27 | namespace AK.Wwise.Unity.WwiseAddressables 28 | { 29 | public static class AkAssetUtilities 30 | { 31 | 32 | #if UNITY_EDITOR 33 | public delegate bool AddressableBankCreatedDelegate(WwiseAddressableSoundBank assetRef, string name); 34 | public static AddressableBankCreatedDelegate AddressableBankUpdated; 35 | 36 | public static string GetSoundbanksPath() 37 | { 38 | if (AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath == null) 39 | { 40 | UnityEngine.Debug.LogError("Wwise Addressables: You need to set the GeneratedSoundbankPath in the Wwise Editor settings or assets will not be properly imported."); 41 | return string.Empty; 42 | } 43 | var path = Path.Combine("Assets", AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath); 44 | return path.Replace("\\", "/"); 45 | } 46 | 47 | public static AssetReferenceWwiseAddressableBank GetAddressableBankAssetReference(string name) 48 | { 49 | var assetPath = System.IO.Path.Combine(GetSoundbanksPath(), name + ".asset"); 50 | return new AssetReferenceWwiseAddressableBank(AssetDatabase.AssetPathToGUID(assetPath)); 51 | } 52 | #if !WWISE_ADDRESSABLES_24_1_OR_LATER 53 | public static WwiseAddressableSoundBank GetAddressableBankAsset(string name) 54 | { 55 | //Unity Integration 2023.1 does not support Auto-Banks 56 | return GetAddressableBankAsset(name, false); 57 | } 58 | #endif 59 | 60 | public static WwiseAddressableSoundBank GetAddressableBankAsset(string name, bool IsLookingForAutoBank) 61 | { 62 | var assetPath = System.IO.Path.Combine(GetSoundbanksPath(), name + ".asset"); 63 | if (IsLookingForAutoBank) 64 | { 65 | assetPath = System.IO.Path.Combine(GetSoundbanksPath(), "Event", name + ".asset"); 66 | } 67 | 68 | var asset = AssetDatabase.LoadAssetAtPath(assetPath); 69 | if (asset == null) 70 | { 71 | if (IsLookingForAutoBank) 72 | { 73 | Debug.LogWarning($"Could not find addressable bank asset : {assetPath}. If the event is in an User Defined Soundbank, make sure" + 74 | " to check the \"Is In User Define SoundBank\" box in the editor."); 75 | } 76 | else 77 | { 78 | Debug.LogError($"Could not find addressable bank asset : {assetPath}"); 79 | } 80 | } 81 | 82 | return asset; 83 | } 84 | #endif 85 | public static bool AreHashesEqual(byte[] existingHash, byte[] newHash) 86 | { 87 | if (existingHash == null || newHash == null) 88 | { 89 | return false; 90 | } 91 | 92 | if (existingHash.Length != newHash.Length) 93 | { 94 | return false; 95 | } 96 | 97 | for (int i = 0; i < newHash.Length; i++) 98 | { 99 | if (existingHash[i] != newHash[i]) 100 | { 101 | return false; 102 | } 103 | } 104 | 105 | return true; 106 | } 107 | 108 | public static bool UpdateStreamedFileIfNecessary(string wwiseFolder, WwiseAsset asset) 109 | { 110 | var filePath = Path.Combine(wwiseFolder, asset.GetRelativeFilePath()); 111 | var hashPath = filePath + ".md5"; 112 | if (File.Exists(hashPath)) 113 | { 114 | var existingHash = File.ReadAllBytes(hashPath); 115 | 116 | if (!AreHashesEqual(existingHash, asset.hash)) 117 | { 118 | // Different hash means file content has changed and needs to be updated 119 | WriteFile(filePath, hashPath, asset); 120 | return true; 121 | } 122 | } 123 | else 124 | { 125 | // No hash means we are downloading the file for the first time 126 | WriteFile(filePath, hashPath, asset); 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | private static void WriteFile(string filePath, string hashPath, WwiseAsset asset) 133 | { 134 | var destinationDir = Path.GetDirectoryName(filePath); 135 | if (!Directory.Exists(destinationDir)) 136 | { 137 | Directory.CreateDirectory(destinationDir); 138 | } 139 | File.WriteAllBytes(filePath, asset.RawData); 140 | File.WriteAllBytes(hashPath, asset.hash); 141 | } 142 | } 143 | } 144 | #endif // AK_WWISE_ADDRESSABLES 145 | -------------------------------------------------------------------------------- /Editor/Samples/WwiseAssetMetadataPreserver.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using UnityEditor.AddressableAssets; 21 | using UnityEditor.AddressableAssets.Settings; 22 | using System.Collections.Generic; 23 | using UnityEditor; 24 | using UnityEngine; 25 | using System.IO; 26 | 27 | namespace AK.Wwise.Unity.WwiseAddressables 28 | { 29 | /* 30 | This class provides the functionality preserve and restore Wwise addressable asset group and label metadata. 31 | This information is lost when the assets are deleted, and clearing the Wwise addressable asset folder is 32 | the simplest way to repair broken WwiseAddressableSoundBanks. 33 | This class should be seen as an example of how to implement this functionality and not a complete solution. 34 | */ 35 | [InitializeOnLoad] 36 | class WwiseAddressableAssetMetadataPreserver 37 | { 38 | static WwiseAddressableAssetMetadataPreserver() 39 | { 40 | if (AkAddressablesEditorSettings.Instance.UseSampleMetadataPreserver) 41 | { 42 | BindMetadataDelegate(); 43 | } 44 | } 45 | 46 | //Sets the GetAssetMetadataDelegate on the WwiseBankPostProcess AssetPostProcessor (which handles importing of .bnk and .wem files) 47 | public static void BindMetadataDelegate() 48 | { 49 | WwiseBankPostProcess.GetAssetMetadataDelegate += GetMetadata; 50 | } 51 | 52 | //Unset the delegate (when the feature is disabled) 53 | public static void UnbindMetaDataDelegate() 54 | { 55 | WwiseBankPostProcess.GetAssetMetadataDelegate -= GetMetadata; 56 | } 57 | 58 | //Creates a AddressableMetadata asset for each Addressable wwise asset that keeps track of groups and labels. 59 | //Metadata assets are created in their own folder in a hierarchy matching the wwise assets 60 | //The folder hierarchy and asset name are used to match the assets on import 61 | [UnityEditor.MenuItem("Assets/Wwise/Addressables/Serialize addressable asset metadata")] 62 | public static void PreserveAllWwiseAssetMetadata() 63 | { 64 | AddressableAssetSettings addressableSettings = AddressableAssetSettingsDefaultObject.Settings; 65 | foreach (AddressableAssetGroup group in addressableSettings.groups) 66 | { 67 | foreach (AddressableAssetEntry assetEntry in group.entries) 68 | { 69 | if (assetEntry.MainAsset) 70 | { 71 | if (assetEntry.MainAsset.GetType().IsSubclassOf(typeof(WwiseAsset))) 72 | { 73 | CreateMetadataAsset(assetEntry.AssetPath, assetEntry.labels, group.name); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | [UnityEditor.MenuItem("Assets/Wwise/Addressables/Preserve addressable asset metadata", true)] 81 | public static bool ValidatePreserveAllWwiseAssetMetadata() 82 | { 83 | return AkAddressablesEditorSettings.Instance.UseSampleMetadataPreserver; 84 | } 85 | 86 | //Create the necessary subfolders and the metadata asset 87 | public static void CreateMetadataAsset(string assetPath, HashSet assetLabels, string groupName) 88 | { 89 | string soundbankPath = assetPath.Replace(AkAssetUtilities.GetSoundbanksPath(), ""); 90 | var splitBankPath = soundbankPath.Split('/'); 91 | AddressableMetadata metaDataAsset = ScriptableObject.CreateInstance(); 92 | metaDataAsset.labels = new List(assetLabels); 93 | metaDataAsset.groupName = groupName; 94 | 95 | var rootPath = AkAddressablesEditorSettings.Instance.MetadataPath; 96 | 97 | if (!Directory.Exists(Path.Combine(Application.dataPath, rootPath))) 98 | { 99 | UnityEditor.AssetDatabase.CreateFolder("Assets", rootPath); 100 | } 101 | 102 | for (int i = 1; i < splitBankPath.Length - 1; i++) 103 | { 104 | if (!Directory.Exists(Path.Combine(Application.dataPath, Path.Combine(rootPath, splitBankPath[i])))) 105 | { 106 | AssetDatabase.CreateFolder(Path.Combine("Assets", rootPath), splitBankPath[i]); 107 | } 108 | rootPath = Path.Combine(rootPath, splitBankPath[i]); 109 | } 110 | 111 | string assetMetadataPath = Path.Combine("Assets", rootPath, Path.GetFileNameWithoutExtension(assetPath) + ".asset"); 112 | AddressableMetadata oldAsset = AssetDatabase.LoadAssetAtPath(assetMetadataPath); 113 | if (oldAsset) 114 | { 115 | if (!metaDataAsset.IsDifferent(oldAsset)) 116 | { 117 | return; 118 | } 119 | } 120 | 121 | UnityEditor.AssetDatabase.CreateAsset(metaDataAsset, assetMetadataPath); 122 | } 123 | 124 | //We know where the metadata asset should be located based on its platfrom and language. 125 | public static AddressableMetadata FindMetadataAsset(string assetName, string platformName, string languageName) 126 | { 127 | string MetadataAssetPath; 128 | if (languageName !="SFX") 129 | { 130 | MetadataAssetPath = Path.Combine("Assets", AkAddressablesEditorSettings.Instance.MetadataPath, platformName, languageName, Path.GetFileNameWithoutExtension(assetName) + ".asset"); 131 | } 132 | else 133 | { 134 | MetadataAssetPath = Path.Combine("Assets", AkAddressablesEditorSettings.Instance.MetadataPath, platformName, Path.GetFileNameWithoutExtension(assetName) + ".asset"); 135 | } 136 | return AssetDatabase.LoadAssetAtPath(MetadataAssetPath); 137 | } 138 | 139 | //Called when improting .bnk and .wem files. Will attempt to find an existing metadata object in the project and load it. 140 | public static bool GetMetadata(string assetName, string platformName, string languageName, ref AddressableMetadata metaData ) 141 | { 142 | AddressableMetadata asset = FindMetadataAsset(assetName, platformName, languageName); 143 | if ( asset ) 144 | { 145 | metaData = asset; 146 | return true; 147 | } 148 | return false; 149 | } 150 | } 151 | } 152 | 153 | #endif -------------------------------------------------------------------------------- /Editor/AkAddressablesEditorSettings.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if UNITY_EDITOR 19 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 20 | using UnityEngine; 21 | using System.Xml; 22 | 23 | namespace AK.Wwise.Unity.WwiseAddressables 24 | { 25 | [System.Serializable] 26 | public class AkAddressablesSettings 27 | { 28 | public const string Filename = "WwiseAddressablesSettings.xml"; 29 | 30 | public static string Path 31 | { 32 | get { return System.IO.Path.Combine(UnityEngine.Application.dataPath, Filename); } 33 | } 34 | 35 | public static bool Exists { get { return System.IO.File.Exists(Path); } } 36 | 37 | public bool UseSampleMetadataPreserver; 38 | public string MetadataPath; 39 | 40 | 41 | internal static AkAddressablesSettings LoadSettings() 42 | { 43 | var settings = new AkAddressablesSettings(); 44 | 45 | try 46 | { 47 | var path = Path; 48 | if (System.IO.File.Exists(path)) 49 | { 50 | var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(AkAddressablesSettings)); 51 | using (var xmlFileStream = new System.IO.FileStream(path, System.IO.FileMode.Open, System.IO.FileAccess.Read)) 52 | settings = xmlSerializer.Deserialize(xmlFileStream) as AkAddressablesSettings; 53 | } 54 | else 55 | { 56 | var projectDir = System.IO.Path.GetDirectoryName(UnityEngine.Application.dataPath); 57 | var foundWwiseProjects = System.IO.Directory.GetFiles(projectDir, "*.wproj", System.IO.SearchOption.AllDirectories); 58 | 59 | //WG-61124 In order to avoid addressables not being updated when removing the streaming option, enable the RemoveUnusedGeneratedFiles feature 60 | foreach (var projects in foundWwiseProjects) 61 | { 62 | var doc = new System.Xml.XmlDocument(); 63 | doc.Load(projects); 64 | var RemoveUnusedGeneratedFilesNode = doc.SelectSingleNode("//Property[@Name='RemoveUnusedGeneratedFiles']"); 65 | if (RemoveUnusedGeneratedFilesNode != null) 66 | { 67 | XmlAttribute valueAttribute = RemoveUnusedGeneratedFilesNode.Attributes["Value"]; 68 | if (valueAttribute != null && valueAttribute.Value != null && valueAttribute.Value != "True") 69 | { 70 | valueAttribute.Value = "True"; 71 | doc.Save(projects); 72 | } 73 | } 74 | 75 | } 76 | 77 | 78 | 79 | settings.MetadataPath = "WwiseAddressablesMetadata"; 80 | settings.UseSampleMetadataPreserver = true; 81 | } 82 | } 83 | catch (System.Exception exception) 84 | { 85 | Debug.LogWarning("Could not load Wwise Addressables settings"); 86 | Debug.LogWarning(exception); 87 | } 88 | 89 | if (string.IsNullOrEmpty(settings.MetadataPath)) 90 | { 91 | settings.MetadataPath = "WwiseAddressablesMetadata"; 92 | } 93 | return settings; 94 | } 95 | 96 | public void SaveSettings() 97 | { 98 | try 99 | { 100 | var xmlDoc = new System.Xml.XmlDocument(); 101 | var xmlSerializer = new System.Xml.Serialization.XmlSerializer(GetType()); 102 | using (var xmlStream = new System.IO.MemoryStream()) 103 | { 104 | var streamWriter = new System.IO.StreamWriter(xmlStream, System.Text.Encoding.UTF8); 105 | xmlSerializer.Serialize(streamWriter, this); 106 | xmlStream.Position = 0; 107 | xmlDoc.Load(xmlStream); 108 | xmlDoc.Save(Path); 109 | } 110 | } 111 | catch 112 | { 113 | UnityEngine.Debug.LogErrorFormat("WwiseUnity: Unable to save addressables settings to file <{0}>. Please ensure that this file path can be written to.", Path); 114 | } 115 | } 116 | } 117 | 118 | public class AkAddressablesEditorSettings 119 | { 120 | private static AkAddressablesSettings s_Instance; 121 | 122 | public static AkAddressablesSettings Instance 123 | { 124 | get 125 | { 126 | if (s_Instance == null) 127 | s_Instance = AkAddressablesSettings.LoadSettings(); 128 | return s_Instance; 129 | } 130 | } 131 | 132 | public static void Reload() 133 | { 134 | s_Instance = AkAddressablesSettings.LoadSettings(); 135 | } 136 | 137 | #region GUI 138 | class SettingsProvider : UnityEditor.SettingsProvider 139 | { 140 | class Styles 141 | { 142 | public static string AddressablesSettings = "Asset metadata preservation"; 143 | 144 | public static UnityEngine.GUIContent UseSampleMetadataPreserver = new UnityEngine.GUIContent("Use Sample Metadata Preserver", "Use the sample metadata preserver to preserve Wwise addressable asset groups and labels when they are deleted."); 145 | 146 | public static UnityEngine.GUIContent MetadataPath = new UnityEngine.GUIContent("Wwise Asset Metadata Path", "Location to create the assets that will contain the addressable asset metadata."); 147 | 148 | 149 | private static UnityEngine.GUIStyle textField; 150 | public static UnityEngine.GUIStyle TextField 151 | { 152 | get 153 | { 154 | if (textField == null) 155 | textField = new UnityEngine.GUIStyle("textfield"); 156 | return textField; 157 | } 158 | } 159 | } 160 | 161 | private static bool Ellipsis() 162 | { 163 | return UnityEngine.GUILayout.Button("...", UnityEngine.GUILayout.Width(30)); 164 | } 165 | 166 | private SettingsProvider(string path) : base(path, UnityEditor.SettingsScope.Project) { } 167 | 168 | [UnityEditor.SettingsProvider] 169 | public static UnityEditor.SettingsProvider CreateMyCustomSettingsProvider() 170 | { 171 | return new SettingsProvider("Project/Wwise Addressables") { keywords = GetSearchKeywordsFromGUIContentProperties() }; 172 | } 173 | 174 | public override void OnGUI(string searchContext) 175 | 176 | { 177 | bool changed = false; 178 | 179 | var labelWidth = UnityEditor.EditorGUIUtility.labelWidth; 180 | UnityEditor.EditorGUIUtility.labelWidth += 100; 181 | 182 | var settings = Instance; 183 | 184 | UnityEngine.GUILayout.Space(UnityEditor.EditorGUIUtility.standardVerticalSpacing); 185 | UnityEngine.GUILayout.Label(Styles.AddressablesSettings, UnityEditor.EditorStyles.boldLabel); 186 | UnityEngine.GUILayout.Space(UnityEditor.EditorGUIUtility.standardVerticalSpacing); 187 | 188 | UnityEditor.EditorGUI.BeginChangeCheck(); 189 | 190 | using (new UnityEngine.GUILayout.VerticalScope("box")) 191 | { 192 | bool newValue = UnityEditor.EditorGUILayout.Toggle(Styles.UseSampleMetadataPreserver, settings.UseSampleMetadataPreserver); 193 | if (settings.UseSampleMetadataPreserver != newValue) 194 | { 195 | settings.UseSampleMetadataPreserver = newValue; 196 | if (settings.UseSampleMetadataPreserver) 197 | { 198 | WwiseAddressableAssetMetadataPreserver.BindMetadataDelegate(); 199 | } 200 | else 201 | { 202 | WwiseAddressableAssetMetadataPreserver.UnbindMetaDataDelegate(); 203 | } 204 | } 205 | UnityEngine.GUILayout.Space(UnityEditor.EditorGUIUtility.standardVerticalSpacing); 206 | 207 | using (new UnityEngine.GUILayout.HorizontalScope()) 208 | { 209 | 210 | 211 | if (settings.UseSampleMetadataPreserver) 212 | { 213 | UnityEditor.EditorGUILayout.PrefixLabel(Styles.MetadataPath); 214 | UnityEditor.EditorGUILayout.SelectableLabel(settings.MetadataPath, Styles.TextField, UnityEngine.GUILayout.Height(17)); 215 | if (Ellipsis()) 216 | { 217 | var OpenInPath = System.IO.Path.GetDirectoryName(AkUtilities.GetFullPath(UnityEngine.Application.dataPath, settings.MetadataPath)); 218 | var MetadataPathNew = UnityEditor.EditorUtility.OpenFolderPanel("Select your metadata Project", OpenInPath, "WwiseAddressableMetadata"); 219 | if (MetadataPathNew.Length != 0) 220 | { 221 | settings.MetadataPath = AkUtilities.MakeRelativePath(UnityEngine.Application.dataPath, MetadataPathNew); 222 | changed = true; 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | 230 | if (UnityEditor.EditorGUI.EndChangeCheck()) 231 | changed = true; 232 | 233 | UnityEngine.GUILayout.Space(UnityEditor.EditorGUIUtility.standardVerticalSpacing); 234 | 235 | UnityEditor.EditorGUIUtility.labelWidth = labelWidth; 236 | 237 | if (changed) 238 | settings.SaveSettings(); 239 | } 240 | #endregion 241 | } 242 | } 243 | } 244 | #endif //Addressables 245 | #endif // UNITY_EDITOR 246 | -------------------------------------------------------------------------------- /Runtime/WwiseAddressableSoundBank.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Linq; 23 | using System.Runtime.InteropServices; 24 | using UnityEditor; 25 | using UnityEngine; 26 | using UnityEngine.AddressableAssets; 27 | 28 | namespace AK.Wwise.Unity.WwiseAddressables 29 | { 30 | [Serializable] 31 | public class WwiseAddressableSoundBank : ScriptableObject, ISerializationCallbackReceiver 32 | { 33 | [SerializeField] 34 | internal WwiseBankPerPlatformEntry[] m_dataPerPlatformList; 35 | 36 | [SerializeField] 37 | internal WwiseBankPerPlatformEntry currentPlatformAssets; 38 | 39 | [System.NonSerialized] 40 | internal BankLoadState loadState = BankLoadState.Unloaded; 41 | 42 | [System.NonSerialized] 43 | internal string currentLanguage; 44 | 45 | [System.NonSerialized] 46 | internal uint soundbankId = AkAddressableBankManager.INVALID_SOUND_BANK_ID; 47 | 48 | [System.NonSerialized] 49 | internal GCHandle GCHandle; 50 | 51 | [System.NonSerialized] 52 | internal bool decodeBank; 53 | 54 | [System.NonSerialized] 55 | internal bool saveDecodedBank; 56 | 57 | [System.NonSerialized] 58 | internal HashSet eventNames; 59 | 60 | [System.NonSerialized] 61 | internal int refCount; 62 | 63 | [System.NonSerialized] 64 | internal bool isAutoBank; 65 | 66 | [System.NonSerialized] 67 | internal uint bankType; 68 | 69 | #if UNITY_EDITOR 70 | public delegate string GetWwisePlatformNameDelegate(BuildTarget target); 71 | public static GetWwisePlatformNameDelegate GetWwisePlatformNameFromBuildTarget; 72 | #endif 73 | 74 | public WwiseBankPerPlatformEntry CurrentPlatformAssets 75 | { 76 | get 77 | { 78 | #if UNITY_EDITOR 79 | string wwisePlatform = GetWwisePlatformNameFromBuildTarget(EditorUserBuildSettings.activeBuildTarget); 80 | if (m_dataPerPlatformList != null) 81 | { 82 | foreach (var entry in m_dataPerPlatformList) 83 | { 84 | if (entry.WwisePlatform == wwisePlatform) 85 | { 86 | return entry; 87 | } 88 | } 89 | } 90 | return null; 91 | #else 92 | return currentPlatformAssets; 93 | #endif 94 | } 95 | } 96 | 97 | public uint SoundbankId 98 | { 99 | get { return soundbankId; } 100 | } 101 | 102 | public BankLoadState LoadState 103 | 104 | { 105 | get { return loadState; } 106 | } 107 | 108 | public bool IsAutoBank 109 | { 110 | get { return isAutoBank; } 111 | set 112 | { 113 | isAutoBank = value; 114 | } 115 | } 116 | 117 | public string CurrentLanguage 118 | { 119 | get { return currentLanguage; } 120 | set 121 | { 122 | currentLanguage = value; 123 | } 124 | } 125 | 126 | public Dictionary Data 127 | { 128 | get 129 | { 130 | #if UNITY_EDITOR 131 | string wwisePlatform = AkBasePathGetter.GetPlatformName(); 132 | return m_dataPerPlatformList.Where(x => x.WwisePlatform == wwisePlatform).Select(x => x.LocalizedBanks).FirstOrDefault(); 133 | #else 134 | return currentPlatformAssets.LocalizedBanks; 135 | #endif 136 | } 137 | } 138 | 139 | public Dictionary StreamingMedia 140 | { 141 | get 142 | { 143 | #if UNITY_EDITOR 144 | string wwisePlatform = AkBasePathGetter.GetPlatformName(); 145 | return m_dataPerPlatformList.Where(x => x.WwisePlatform == wwisePlatform).Select(x => x.LocalizedStreamingMedia).FirstOrDefault(); 146 | #else 147 | return currentPlatformAssets.LocalizedStreamingMedia; 148 | #endif 149 | } 150 | } 151 | 152 | public delegate void BankLoadHandler(); 153 | 154 | public event BankLoadHandler OnBankLoaded; 155 | 156 | public void BroadcastBankLoaded() 157 | { 158 | if (OnBankLoaded != null) 159 | { 160 | OnBankLoaded(); 161 | } 162 | } 163 | 164 | public void OnAfterDeserialize() 165 | { 166 | #if UNITY_EDITOR 167 | if (m_dataPerPlatformList != null) 168 | { 169 | foreach (var entry in m_dataPerPlatformList) 170 | { 171 | entry.Deserialize(); 172 | } 173 | } 174 | #else 175 | currentPlatformAssets.Deserialize(); 176 | #endif 177 | } 178 | 179 | public void OnBeforeSerialize() 180 | { 181 | #if UNITY_EDITOR 182 | 183 | string wwisePlatform = GetWwisePlatformNameFromBuildTarget(EditorUserBuildSettings.activeBuildTarget); 184 | if (m_dataPerPlatformList != null) 185 | { 186 | foreach (var entry in m_dataPerPlatformList) 187 | { 188 | entry.LocalizedBankKeys = entry.LocalizedBanks.Keys.ToArray(); 189 | entry.LocalizedBanksValues = entry.LocalizedBanks.Values.ToArray(); 190 | entry.LocalizedStreamingMediaKeys = entry.LocalizedStreamingMedia.Keys.ToArray(); 191 | entry.LocalizedStreamingMediaValues = new LocalizedStreamingMediaList(); 192 | entry.LocalizedStreamingMediaValues.Add(entry.LocalizedStreamingMedia.Values.ToList()); 193 | 194 | if (entry.WwisePlatform == wwisePlatform) 195 | { 196 | currentPlatformAssets = entry; 197 | } 198 | } 199 | } 200 | #endif 201 | loadState = BankLoadState.Unloaded; 202 | } 203 | 204 | #if UNITY_EDITOR 205 | public void AddOrUpdate(string wwisePlatform, string language, AssetReferenceWwiseBankData bankRef) 206 | { 207 | if (m_dataPerPlatformList == null) 208 | { 209 | m_dataPerPlatformList = Array.Empty(); 210 | } 211 | 212 | WwiseBankPerPlatformEntry foundEntry = null; 213 | foreach (var entry in m_dataPerPlatformList) 214 | { 215 | if (entry.WwisePlatform == wwisePlatform) 216 | { 217 | if (entry.WwisePlatform == wwisePlatform) 218 | { 219 | foundEntry = entry; 220 | break; 221 | } 222 | } 223 | } 224 | 225 | if (foundEntry != null) 226 | { 227 | foundEntry.LocalizedBanks[language] = bankRef; 228 | } 229 | else 230 | { 231 | foundEntry = new WwiseBankPerPlatformEntry { WwisePlatform = wwisePlatform }; 232 | foundEntry.LocalizedBanks[language] = bankRef; 233 | ArrayUtility.Add(ref m_dataPerPlatformList, foundEntry); 234 | } 235 | } 236 | 237 | public void SetStreamingMedia(string wwisePlatform, string language, string platformDir, List streamingMediaIds) 238 | { 239 | List uniqueList = streamingMediaIds.Distinct().ToList(); 240 | foreach (var entry in m_dataPerPlatformList) 241 | { 242 | if (entry.WwisePlatform == wwisePlatform) 243 | { 244 | 245 | entry.LocalizedStreamingMedia[language] = new StreamingMediaList(); 246 | 247 | foreach (var Id in uniqueList) 248 | { 249 | var mediaPath = System.IO.Path.Combine(platformDir, Id + ".wem"); 250 | entry.LocalizedStreamingMedia[language].Add(new AssetReferenceStreamingMedia(AssetDatabase.AssetPathToGUID(mediaPath), Id)); 251 | } 252 | break; 253 | } 254 | } 255 | } 256 | 257 | public void UpdateLocalizationLanguages(string wwisePlatform, List parsedLanguages) 258 | { 259 | if (m_dataPerPlatformList == null) return; 260 | foreach (var entry in m_dataPerPlatformList) 261 | { 262 | if (entry.WwisePlatform == wwisePlatform) 263 | { 264 | var toRemove = new List(); 265 | foreach (var lang in entry.LocalizedStreamingMedia.Keys) 266 | { 267 | if (!parsedLanguages.Contains(lang)) 268 | { 269 | toRemove.Add(lang); 270 | } 271 | } 272 | 273 | foreach (var lang in toRemove) 274 | { 275 | entry.LocalizedStreamingMedia.Remove(lang); 276 | } 277 | 278 | toRemove.Clear(); 279 | foreach (var lang in entry.LocalizedBanks.Keys) 280 | { 281 | if (!parsedLanguages.Contains(lang)) 282 | { 283 | toRemove.Add(lang); 284 | } 285 | } 286 | 287 | foreach (var lang in toRemove) 288 | { 289 | entry.LocalizedBanks.Remove(lang); 290 | } 291 | } 292 | } 293 | } 294 | 295 | public bool TryRemoveMedia(string wwisePlatform, string language, string mediaGuid) 296 | { 297 | foreach (var entry in m_dataPerPlatformList) 298 | { 299 | if (entry.WwisePlatform == wwisePlatform) 300 | { 301 | if (!entry.LocalizedStreamingMedia.ContainsKey(language)) 302 | { 303 | break; 304 | } 305 | AssetReferenceStreamingMedia mediaToRemove = null; 306 | 307 | foreach (var media in entry.LocalizedStreamingMedia[language].media) 308 | { 309 | if (media.AssetGUID == mediaGuid) 310 | { 311 | mediaToRemove = media; 312 | break; 313 | } 314 | } 315 | if (mediaToRemove != null) 316 | { 317 | entry.LocalizedStreamingMedia[language].media.Remove(mediaToRemove); 318 | return true; 319 | } 320 | } 321 | } 322 | return false; 323 | } 324 | 325 | public bool TryRemoveBank(string wwisePlatform, string language, string bankGuid) 326 | { 327 | foreach (var entry in m_dataPerPlatformList) 328 | { 329 | if (entry.WwisePlatform == wwisePlatform) 330 | { 331 | if (!entry.LocalizedBanks.ContainsKey(language)) 332 | { 333 | break; 334 | } 335 | if (entry.LocalizedBanks[language].AssetGUID == bankGuid) 336 | { 337 | entry.LocalizedBanks.Remove(language); 338 | return true; 339 | 340 | } 341 | } 342 | } 343 | return false; 344 | } 345 | #endif 346 | } 347 | 348 | [Serializable] 349 | public class AssetReferenceWwiseAddressableBank : AssetReferenceT 350 | { 351 | public AssetReferenceWwiseAddressableBank(string guid) 352 | : base(guid) 353 | { 354 | } 355 | } 356 | 357 | [Serializable] 358 | public class AssetReferenceWwiseBankData : AssetReferenceT 359 | { 360 | public AssetReferenceWwiseBankData(string guid) 361 | : base(guid) 362 | { 363 | } 364 | } 365 | 366 | [Serializable] 367 | public class AssetReferenceStreamingMedia : AssetReferenceT 368 | { 369 | public string id; 370 | 371 | public AssetReferenceStreamingMedia(string guid, string id) 372 | : base(guid) 373 | { 374 | this.id = id; 375 | } 376 | } 377 | 378 | [Serializable] 379 | public class StreamingMediaList 380 | { 381 | public List media = new List(); 382 | public void Add(AssetReferenceStreamingMedia m) 383 | { 384 | media.Add(m); 385 | } 386 | 387 | 388 | public AssetReferenceStreamingMedia this[int key] 389 | { 390 | get 391 | { 392 | return media[key]; 393 | } 394 | set 395 | { 396 | media[key] = value; 397 | } 398 | } 399 | } 400 | 401 | [Serializable] 402 | public class LocalizedStreamingMediaList 403 | { 404 | public List localizedMediaList = new List(); 405 | public void Add(StreamingMediaList mediaList) 406 | { 407 | localizedMediaList.Add(mediaList); 408 | } 409 | 410 | public void Add(List mediaList) 411 | { 412 | foreach (var l in mediaList) 413 | { 414 | localizedMediaList.Add(l); 415 | } 416 | } 417 | 418 | public StreamingMediaList this[int key] 419 | { 420 | get 421 | { 422 | return localizedMediaList[key]; 423 | } 424 | set 425 | { 426 | localizedMediaList[key] = value; 427 | } 428 | } 429 | } 430 | 431 | [Serializable] 432 | public class WwiseBankPerPlatformEntry 433 | { 434 | public string WwisePlatform; 435 | [NonSerialized] 436 | public Dictionary LocalizedBanks = new Dictionary(); 437 | [NonSerialized] 438 | public Dictionary LocalizedStreamingMedia = new Dictionary(); 439 | 440 | public string[] LocalizedBankKeys; 441 | public AssetReferenceWwiseBankData[] LocalizedBanksValues; 442 | 443 | public string[] LocalizedStreamingMediaKeys; 444 | public LocalizedStreamingMediaList LocalizedStreamingMediaValues; 445 | 446 | public void Deserialize() 447 | { 448 | int idx = 0; 449 | foreach (var key in LocalizedBankKeys) 450 | { 451 | LocalizedBanks.Add(key, LocalizedBanksValues[idx]); 452 | idx++; 453 | } 454 | idx = 0; 455 | foreach (var key in LocalizedStreamingMediaKeys) 456 | { 457 | LocalizedStreamingMedia.Add(key, LocalizedStreamingMediaValues[idx]); 458 | idx++; 459 | } 460 | } 461 | } 462 | 463 | public enum BankLoadState 464 | { 465 | Unloaded, 466 | WaitingForInitBankToLoad, 467 | Loading, 468 | Loaded, 469 | LoadFailed, 470 | TimedOut, 471 | WaitingForPrepareEvent 472 | } 473 | } 474 | #endif // AK_WWISE_ADDRESSABLES 475 | -------------------------------------------------------------------------------- /Editor/AkAddressablesEditorUtilities.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES && UNITY_EDITOR 19 | 20 | using UnityEngine; 21 | using System; 22 | using System.Collections.Generic; 23 | using System.IO; 24 | using System.Linq; 25 | using System.Reflection; 26 | using System.Text.RegularExpressions; 27 | using System.Threading.Tasks; 28 | using UnityEditor; 29 | using System.Xml; 30 | 31 | namespace AK.Wwise.Unity.WwiseAddressables 32 | { 33 | [InitializeOnLoad] 34 | public class AkAddressablesEditorUtilities : MonoBehaviour 35 | { 36 | static AkAddressablesEditorUtilities() 37 | { 38 | WwiseAddressableSoundBank.GetWwisePlatformNameFromBuildTarget = GetWwisePlatformNameFromBuildTarget; 39 | } 40 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 41 | static void RefreshIsJsonFileMissing() 42 | { 43 | WwiseProjectDatabase.SoundBankDirectoryUpdated -= RefreshIsJsonFileMissing; 44 | isJsonFileMissing = false; 45 | } 46 | private static bool isJsonFileMissing = false; 47 | #endif 48 | 49 | public static Dictionary SoundbanksInfo = new Dictionary(); 50 | 51 | public class SoundBankInfo 52 | { 53 | public List streamedFileIds = new List(); 54 | public List events = new List(); 55 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 56 | public bool isUserBank = true; 57 | #endif 58 | } 59 | 60 | public class SoundBankEntry : Dictionary 61 | { 62 | } 63 | 64 | public class PlatformEntry : Dictionary<(string,string), SoundBankEntry> 65 | { 66 | public long lastParseTime; 67 | public Dictionary> eventToSoundBankMap = new Dictionary>(); 68 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 69 | public bool containsInvalidEntry = false; 70 | 71 | public PlatformEntry() 72 | { 73 | WwiseProjectDatabase.SoundBankDirectoryUpdated += ResetInvalidEntry; 74 | } 75 | 76 | private void ResetInvalidEntry() 77 | { 78 | containsInvalidEntry = false; 79 | } 80 | #endif 81 | } 82 | 83 | public static string GetWwisePlatformNameFromBuildTarget(BuildTarget platform) 84 | { 85 | return AkBuildPreprocessor.GetPlatformName(platform); 86 | } 87 | 88 | public static bool IsAutoBank(string assetPath) 89 | { 90 | var banksPath = GetFullSoundbanksPath() + Path.DirectorySeparatorChar; 91 | var assetsFullPath = Path.GetFullPath(assetPath); 92 | 93 | string directoryName = Path.GetDirectoryName(assetsFullPath); 94 | var pathContent = directoryName.Split(Path.DirectorySeparatorChar).ToList(); 95 | return pathContent.Contains("Event"); 96 | } 97 | 98 | public static void ParseAssetPath(string assetPath, out string platform, out string language, out string type) 99 | { 100 | platform = string.Empty; 101 | language = "SFX"; 102 | type = "User"; 103 | 104 | var banksPath = GetFullSoundbanksPath(); 105 | if (!banksPath.EndsWith(Path.DirectorySeparatorChar)) 106 | { 107 | banksPath += Path.DirectorySeparatorChar; 108 | } 109 | var assetsFullPath = Path.GetFullPath(assetPath); 110 | 111 | // Remove the temp folder from the path when the asset import happens during a migration 112 | var r = new Regex(Regex.Escape("_WwiseIntegrationTemp")); 113 | assetsFullPath = r.Replace(assetsFullPath, "", 1); 114 | 115 | // TODO Use Path.RelativePath as soon as Unity uses a .NET version that includes it (i.e 2021.3) 116 | var assetRelPath = assetsFullPath.Replace(banksPath, ""); 117 | 118 | string[] parts = assetRelPath.Split(Path.DirectorySeparatorChar); 119 | platform = parts[0]; 120 | 121 | if (parts.Length > 2) 122 | { 123 | // Asset is stored in a sub-folder; we must identify the purpose of the sub-folder. 124 | if (parts[1] == "Media" || parts[1] == "Bus" || parts[1] == "Event") 125 | { 126 | type = parts[1]; 127 | // Starting with Wwise 2022.1, loose media files are stored in a sub-directory named "Media". 128 | // These themselves can be in localized sub-folders. 129 | if (parts.Length > 3) 130 | { 131 | // The sub-sub folder name is the locale string 132 | language = parts[2]; 133 | } 134 | } 135 | else 136 | { 137 | // Localized bank file; the sub-folder name is the locale string 138 | language = parts[1]; 139 | } 140 | } 141 | } 142 | 143 | public static string GetSoundbanksPath() 144 | { 145 | if (AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath == null) 146 | { 147 | UnityEngine.Debug.LogError("Wwise Addressables: You need to set the GeneratedSoundbankPath in the Wwise Editor settings or assets will not be properly imported."); 148 | return string.Empty; 149 | } 150 | var path = Path.Combine("Assets", AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath); 151 | return path.Replace("\\", "/"); 152 | } 153 | 154 | private static string GetFullSoundbanksPath() 155 | { 156 | if (AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath == null) 157 | { 158 | UnityEngine.Debug.LogError("Wwise Addressables: You need to set the GeneratedSoundbankPath in the Wwise Editor settings or assets will not be properly imported."); 159 | return string.Empty; 160 | } 161 | var path = Path.Combine("Assets", AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath); 162 | return Path.GetFullPath(path); 163 | } 164 | 165 | 166 | public static void ClearSoundbankInfo() 167 | { 168 | SoundbanksInfo.Clear(); 169 | } 170 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 171 | public static void AddSoundBank(string bankName, string bankLanguage, string type, ref PlatformEntry soundBankDict, WwiseSoundBankRef sbInfo) 172 | { 173 | bool isAutoBank = !sbInfo.IsUserBank; 174 | soundBankDict.TryAdd((bankName,type), new SoundBankEntry()); 175 | soundBankDict[(bankName,type)][bankLanguage] = new SoundBankInfo(); 176 | for (int i = 0; i < sbInfo.MediasCount; ++i) 177 | { 178 | if (!soundBankDict[(bankName, type)].ContainsKey(sbInfo.Medias[i].Language)) 179 | { 180 | soundBankDict[(bankName,type)][sbInfo.Medias[i].Language] = new SoundBankInfo(); 181 | } 182 | RecordMediaFile(soundBankDict, (bankName, type), sbInfo.Medias[i].ShortId.ToString(), sbInfo.Medias[i].Language); 183 | 184 | } 185 | for (int i = 0; i < sbInfo.EventsCount; ++i) 186 | { 187 | RecordEvent(soundBankDict, (bankName,type), sbInfo.Language, sbInfo.Events[i].Name); 188 | } 189 | soundBankDict[(bankName,type)][sbInfo.Language].isUserBank = sbInfo.IsUserBank; 190 | } 191 | public static async Task ExecuteUpdate(string platformName, string newBankName, string language, string type) 192 | { 193 | WwiseProjectDatabase.SetCurrentPlatform(platformName); 194 | WwiseProjectDatabase.SetCurrentLanguage(language); 195 | 196 | bool doUpdate = true; 197 | if (!SoundbanksInfo.ContainsKey(platformName)) 198 | { 199 | SoundbanksInfo[platformName] = new PlatformEntry(); 200 | WwisePlatformRef platformInfo = new WwisePlatformRef(platformName); 201 | if (platformInfo.Name == null) 202 | { 203 | WwiseProjectDatabase.Init(AkBasePathGetter.GetWwiseRootOutputPath(), platformName, language); 204 | } 205 | } 206 | if (SoundbanksInfo.ContainsKey(platformName) && SoundbanksInfo[platformName].containsInvalidEntry) 207 | { 208 | doUpdate = false; 209 | } 210 | if (doUpdate) 211 | { 212 | await UpdatePlatformEntry(SoundbanksInfo[platformName], newBankName, platformName, language, type); 213 | } 214 | 215 | return SoundbanksInfo[platformName]; 216 | } 217 | 218 | public static async Task UpdatePlatformEntry(PlatformEntry soundBanks, string newBankName, string platformName, string language, string type) 219 | { 220 | WwiseSoundBankRef sbInfo = new WwiseSoundBankRef(newBankName, type); 221 | if (!sbInfo.IsValid) 222 | { 223 | WwiseProjectDatabase.Init(AkBasePathGetter.GetWwiseRootOutputPath(), platformName, language); 224 | sbInfo = new WwiseSoundBankRef(newBankName, type); 225 | } 226 | if (sbInfo.IsValid) 227 | { 228 | AddSoundBank(newBankName, language, type, ref soundBanks, sbInfo); 229 | } 230 | else 231 | { 232 | soundBanks.containsInvalidEntry = true; 233 | } 234 | soundBanks.lastParseTime = DateTime.Now.Ticks; 235 | } 236 | #endif 237 | 238 | public static void AddSoundBank(string bankName, string bankLanguage, ref PlatformEntry soundBankDict) 239 | { 240 | if (!soundBankDict.ContainsKey((bankName, "User"))) 241 | { 242 | soundBankDict.Add((bankName,"User"), new SoundBankEntry()); 243 | } 244 | soundBankDict[(bankName,"User")][bankLanguage] = new SoundBankInfo(); 245 | } 246 | public static PlatformEntry ExecuteParse(string platformName, string newBankName, string xmlFilename) 247 | { 248 | bool doParse = false; 249 | if (!SoundbanksInfo.ContainsKey(platformName)) 250 | { 251 | doParse = true; 252 | } 253 | else if (SoundbanksInfo.ContainsKey(platformName) && !SoundbanksInfo[platformName].ContainsKey((newBankName,"User"))) 254 | { 255 | doParse = true; 256 | } 257 | else 258 | { 259 | var fileModifiedTime = System.IO.File.GetLastWriteTime(xmlFilename); 260 | if (fileModifiedTime.Ticks > SoundbanksInfo[platformName].lastParseTime) 261 | { 262 | doParse = true; 263 | } 264 | } 265 | 266 | if (doParse) 267 | { 268 | var doc = new System.Xml.XmlDocument(); 269 | PlatformEntry soundBanks; 270 | 271 | try 272 | { 273 | doc.Load(xmlFilename); 274 | } 275 | catch (XmlException e) 276 | { 277 | UnityEngine.Debug.LogError("Exception occurred while parsing SoundBanksInfo.xml. Cannot update project SoundBanks info: " + e); 278 | return null; 279 | } 280 | 281 | XmlElement root = doc.DocumentElement; 282 | if (!Int32.TryParse(root.GetAttribute("SchemaVersion"), out int schemaVersion)) 283 | { 284 | Debug.LogError($"Could not parse SoundbanksInfo.xml for {platformName}. Check {xmlFilename} for possible corruption."); 285 | return null; 286 | } 287 | 288 | if (schemaVersion >= 16) 289 | { 290 | soundBanks = ParseSoundBanksInfoXmlv16(doc); 291 | } 292 | else 293 | { 294 | soundBanks = ParseSoundBanksInfoXmlv15(doc); 295 | } 296 | soundBanks.lastParseTime = DateTime.Now.Ticks; 297 | SoundbanksInfo[platformName] = soundBanks; 298 | } 299 | 300 | if (SoundbanksInfo[platformName].eventToSoundBankMap.Count == 0) 301 | { 302 | Debug.LogWarning($"Could not retrieve event data for {platformName} from SoundbanksInfo.xml. Check {xmlFilename} for possible corruption."); 303 | } 304 | 305 | return SoundbanksInfo[platformName]; 306 | } 307 | 308 | private static PlatformEntry ParseSoundBanksInfoXmlv16(XmlDocument doc) 309 | { 310 | var soundBanks = new PlatformEntry(); 311 | var soundBanksRootNode = doc.GetElementsByTagName("SoundBanks"); 312 | for (var i = 0; i < soundBanksRootNode.Count; i++) 313 | { 314 | var soundBankNodes = soundBanksRootNode[i].SelectNodes("SoundBank"); 315 | for (var j = 0; j < soundBankNodes.Count; j++) 316 | { 317 | var bankName = soundBankNodes[j].SelectSingleNode("ShortName").InnerText; 318 | var language = soundBankNodes[j].Attributes.GetNamedItem("Language").Value; 319 | 320 | AddSoundBank(bankName, language, ref soundBanks); 321 | 322 | if (bankName.Equals("Init")) 323 | { 324 | continue; 325 | } 326 | 327 | // First, record all streamed media contained in this bank. 328 | var mediaRootNode = soundBankNodes[j].SelectSingleNode("Media"); 329 | if (mediaRootNode != null) 330 | { 331 | var fileNodes = mediaRootNode.SelectNodes("File"); 332 | foreach (XmlNode fileNode in fileNodes) 333 | { 334 | RecordMediaFile( 335 | soundBanks, 336 | (bankName,"User"), 337 | fileNode.Attributes["Id"].Value, 338 | fileNode.Attributes["Language"].Value); 339 | } 340 | } 341 | 342 | // Then, record all events contained in the bank 343 | var includedEventsNode = soundBankNodes[j].SelectSingleNode("Events"); 344 | if (includedEventsNode != null) 345 | { 346 | var eventNodes = includedEventsNode.SelectNodes("Event"); 347 | foreach (XmlNode eventNode in eventNodes) 348 | { 349 | RecordEvent(soundBanks, (bankName,"User"), language, eventNode.Attributes["Name"].Value); 350 | } 351 | } 352 | } 353 | } 354 | 355 | return soundBanks; 356 | } 357 | 358 | private static PlatformEntry ParseSoundBanksInfoXmlv15(XmlDocument doc) 359 | { 360 | var soundBanks = new PlatformEntry(); 361 | var soundBanksRootNode = doc.GetElementsByTagName("SoundBanks"); 362 | for (var i = 0; i < soundBanksRootNode.Count; i++) 363 | { 364 | var soundBankNodes = soundBanksRootNode[i].SelectNodes("SoundBank"); 365 | for (var j = 0; j < soundBankNodes.Count; j++) 366 | { 367 | var bankName = soundBankNodes[j].SelectSingleNode("ShortName").InnerText; 368 | var language = soundBankNodes[j].Attributes.GetNamedItem("Language").Value; 369 | 370 | AddSoundBank(bankName, language, ref soundBanks); 371 | 372 | if (bankName.Equals("Init")) 373 | { 374 | continue; 375 | } 376 | 377 | var includedEventsNode = soundBankNodes[j].SelectSingleNode("IncludedEvents"); 378 | if (includedEventsNode != null) 379 | { 380 | var eventNodes = includedEventsNode.SelectNodes("Event"); 381 | for (var e = 0; e < eventNodes.Count; e++) 382 | { 383 | RecordEvent(soundBanks, (bankName,"User"), language, eventNodes[e].Attributes["Name"].Value); 384 | 385 | var streamedFilesRootNode = eventNodes[e].SelectSingleNode("ReferencedStreamedFiles"); 386 | if (streamedFilesRootNode != null) 387 | { 388 | var streamedFileNodes = streamedFilesRootNode.SelectNodes("File"); 389 | if (streamedFileNodes.Count > 0) 390 | { 391 | for (var s = 0; s < streamedFileNodes.Count; s++) 392 | { 393 | RecordMediaFile( 394 | soundBanks, 395 | (bankName,"User"), 396 | streamedFileNodes[s].Attributes["Id"].Value, 397 | streamedFileNodes[s].Attributes.GetNamedItem("Language").Value); 398 | } 399 | } 400 | } 401 | } 402 | } 403 | } 404 | } 405 | 406 | return soundBanks; 407 | } 408 | 409 | public static PlatformEntry GetPlatformSoundbanks(string platformName) 410 | { 411 | return SoundbanksInfo[platformName]; 412 | } 413 | //Parse soundbank xml file to get a dict of the streaming wem files 414 | public static async Task ParsePlatformSoundbanks(string platformName, string newBankName, string language, string type) 415 | { 416 | if (platformName == null) 417 | { 418 | platformName = AkBasePathGetter.GetPlatformName(); 419 | } 420 | 421 | var sourceFolder = Path.Combine("Assets", AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath, platformName); 422 | 423 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 424 | var jsonFilename = Path.Combine(sourceFolder, "SoundbanksInfo.json"); 425 | if (File.Exists(jsonFilename)) 426 | { 427 | return await ExecuteUpdate(platformName, newBankName, language, type); 428 | } 429 | if (!isJsonFileMissing && AkUtilities.IsAutoBankEnabled()) 430 | { 431 | WwiseProjectDatabase.SoundBankDirectoryUpdated += RefreshIsJsonFileMissing; 432 | isJsonFileMissing = true; 433 | Debug.LogWarning($"Could not find SoundbanksInfo.json, falling back to SoundbanksInfo.xml." + 434 | $"Using the SoundbanksInfo.xml is not the recommended option and it involves a manual support for auto-defined Soundbanks." + 435 | $"To benefit from an automatic support of auto-defined Soundbanks, make sure Object GUID, Object Path and Generate JSON Metadata is checked in the WwiseProject. Then, clear {sourceFolder} and regenerate the Soundbanks."); 436 | } 437 | #endif 438 | var xmlFilename = Path.Combine(sourceFolder, "SoundbanksInfo.xml"); 439 | if (!File.Exists(xmlFilename)) 440 | { 441 | Debug.LogWarning($"Could not find SoundbanksInfo.xml at {Path.Combine(AkWwiseEditorSettings.Instance.GeneratedSoundbanksPath, platformName)}. Check the Generated Soundbanks Path in the Unity Wwise project settings. Using the Wwise Project to find SoundbanksInfo.xml."); 442 | if (!AkBasePathGetter.GetSoundBankPaths(platformName, out sourceFolder, out string destinationFolder)) 443 | { 444 | Debug.LogError($"Failed to import {newBankName}. Could not get SoundBank folder for {platformName} from Wwise Project {AkWwiseEditorSettings.Instance.WwiseProjectPath}."); 445 | return null; 446 | } 447 | 448 | xmlFilename = Path.Combine(sourceFolder, "SoundbanksInfo.xml"); 449 | if(!File.Exists(xmlFilename)) 450 | { 451 | Debug.LogError($"Failed to import {newBankName}. Could not find SoundbanksInfo for {platformName} platform. Make sure your SoundBanks are generated and that the setting \"Generate XML Metadata\" is enabled. Then, clear {sourceFolder} and regenerate the Soundbanks."); 452 | return null; 453 | } 454 | } 455 | return ExecuteParse(platformName, newBankName, xmlFilename); 456 | } 457 | 458 | public static void FindAndSetBankReference(WwiseAddressableSoundBank addressableBankAsset, string name) 459 | { 460 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 461 | if (addressableBankAsset.IsAutoBank) 462 | { 463 | WwiseEventReference.FindEventReferenceAndSetAddressableBank(addressableBankAsset, name); 464 | return; 465 | } 466 | #endif 467 | WwiseBankReference.FindBankReferenceAndSetAddressableBank(addressableBankAsset, name); 468 | } 469 | 470 | public static void EnsureInitBankAssetCreated() 471 | { 472 | var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(WwiseInitBankReference).Name, new string[] { AkWwiseEditorSettings.WwiseScriptableObjectRelativePath }); 473 | var InitBankAssetPath = Path.Combine(AkWwiseEditorSettings.WwiseScriptableObjectRelativePath, "InitBank.asset"); 474 | if (guids.Length == 0) 475 | { 476 | try 477 | { 478 | AssetDatabase.StartAssetEditing(); 479 | WwiseInitBankReference InitBankRef = UnityEngine.ScriptableObject.CreateInstance(); 480 | UnityEditor.AssetDatabase.CreateAsset(InitBankRef, InitBankAssetPath); 481 | } 482 | finally 483 | { 484 | AssetDatabase.StopAssetEditing(); 485 | } 486 | } 487 | } 488 | 489 | private static void RecordEvent(PlatformEntry soundBanks, (string,string) bankKey, string language, string eventName) 490 | { 491 | soundBanks[bankKey][language].events.Add(eventName); 492 | } 493 | 494 | private static void RecordMediaFile(PlatformEntry soundBanks, (string,string) bankKey, string id, string language) 495 | { 496 | #if !WWISE_ADDRESSABLES_24_1_OR_LATER 497 | if (!soundBanks[bankKey].ContainsKey(language)) 498 | { 499 | AddSoundBank(bankKey.Item1, language, ref soundBanks); 500 | } 501 | #endif 502 | // Record that this bank "contains" this streamed media file 503 | soundBanks[bankKey][language].streamedFileIds.Add(id); 504 | 505 | // Record that this streamed media file is "contained" in this bank 506 | if (!soundBanks.eventToSoundBankMap.ContainsKey(id)) 507 | { 508 | soundBanks.eventToSoundBankMap[id] = new List<(string,string)>(); 509 | } 510 | soundBanks.eventToSoundBankMap[id].Add(bankKey); 511 | } 512 | } 513 | } 514 | #endif -------------------------------------------------------------------------------- /Editor/WwiseAssetImportProcessors.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System.Collections.Concurrent; 21 | using System.Collections.Generic; 22 | using System.IO; 23 | using System.Linq; 24 | using System.Text; 25 | using System.Threading.Tasks; 26 | using UnityEditor; 27 | using UnityEngine; 28 | using UnityEditor.AddressableAssets; 29 | using UnityEditor.AddressableAssets.Settings; 30 | using UnityEditor.AddressableAssets.Settings.GroupSchemas; 31 | 32 | namespace AK.Wwise.Unity.WwiseAddressables 33 | { 34 | public class WwiseBankPostSaveProcessor : UnityEditor.AssetModificationProcessor 35 | { 36 | //Clear parsed soundbank info after updating assets (force reparse when new bnk and wem assets are added) 37 | static string[] OnWillSaveAssets(string[] paths) 38 | { 39 | bool wwiseAssetsModified = false; 40 | foreach (var item in paths) 41 | { 42 | if (Path.GetExtension(item) == ".bnk") 43 | { 44 | wwiseAssetsModified = true; 45 | break; 46 | } 47 | else if (Path.GetExtension(item) == ".wem") 48 | { 49 | wwiseAssetsModified = true; 50 | break; 51 | } 52 | } 53 | 54 | if (wwiseAssetsModified) 55 | { 56 | AkAddressablesEditorUtilities.ClearSoundbankInfo(); 57 | } 58 | 59 | return paths; 60 | } 61 | } 62 | 63 | public class WwiseBankPostProcess : AssetPostprocessor 64 | { 65 | public delegate bool GetAssetMetadata(string name, string platform, string language, ref AddressableMetadata addressableMetadata); 66 | 67 | //Bind to this delegate to customize grouping or labeling of Wwise bank and media assets 68 | public static GetAssetMetadata GetAssetMetadataDelegate; 69 | 70 | static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) 71 | { 72 | AssetDatabase.SaveAssets(); 73 | AssetDatabase.Refresh(); 74 | UpdateAssetReferences(importedAssets); 75 | RemoveAssetReferences(deletedAssets); 76 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 77 | WarnForInvalidEntry(); 78 | #endif 79 | } 80 | 81 | public static async Task UpdateAssetReferences(string[] assets) 82 | { 83 | HashSet bankAssetsToProcess = new HashSet(); 84 | HashSet streamingAssetsToProcess = new HashSet(); 85 | 86 | foreach (var item in assets) 87 | { 88 | if (Path.GetExtension(item) == ".bnk") 89 | { 90 | bankAssetsToProcess.Add(item); 91 | } 92 | 93 | if (Path.GetExtension(item) == ".wem") 94 | { 95 | streamingAssetsToProcess.Add(item); 96 | } 97 | } 98 | 99 | if (bankAssetsToProcess.Count > 0) 100 | { 101 | ConcurrentDictionary addressableAssetCache = 102 | new ConcurrentDictionary(); 103 | await AddBankReferenceToAddressableBankAsset(addressableAssetCache, bankAssetsToProcess); 104 | AddAssetsToAddressablesGroup(bankAssetsToProcess); 105 | } 106 | 107 | if (streamingAssetsToProcess.Count > 0) 108 | { 109 | await AddStreamedAssetsToBanks(streamingAssetsToProcess); 110 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 111 | AddAssetsToAddressablesGroup(streamingAssetsToProcess); 112 | #endif 113 | } 114 | } 115 | 116 | internal static async Task AddStreamedAssetsToBanks(HashSet streamingAssetsAdded) 117 | { 118 | try 119 | { 120 | foreach (var assetPath in streamingAssetsAdded) 121 | { 122 | string name = Path.GetFileNameWithoutExtension(assetPath); 123 | 124 | string platform; 125 | string language; 126 | string type; 127 | AkAddressablesEditorUtilities.ParseAssetPath(assetPath, out platform, out language, out type); 128 | bool isAutoBank = type != "User"; 129 | 130 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 131 | var soundbankInfos = AkAddressablesEditorUtilities.GetPlatformSoundbanks(platform); 132 | #else 133 | var soundbankInfos = await AkAddressablesEditorUtilities.ParsePlatformSoundbanks(platform, name, language, type); 134 | #endif 135 | 136 | if (soundbankInfos.eventToSoundBankMap.TryGetValue(name, out var bankInfos)) 137 | { 138 | foreach (var bankInfo in bankInfos) 139 | { 140 | string bankName = bankInfo.Item1; 141 | string bankType = bankInfo.Item2; 142 | string bankAssetPath = bankType == "User" ? "" : bankType+"/"; 143 | bankAssetPath += bankName; 144 | string bankAssetDir = Path.GetDirectoryName(assetPath); 145 | string addressableBankAssetDir = AkAssetUtilities.GetSoundbanksPath(); 146 | string addressableBankAssetPath = 147 | System.IO.Path.Combine(addressableBankAssetDir, bankAssetPath + ".asset"); 148 | var bankAsset = 149 | AssetDatabase.LoadAssetAtPath(addressableBankAssetPath); 150 | 151 | if (bankAsset == null) 152 | { 153 | continue; 154 | } 155 | 156 | if (!string.IsNullOrEmpty(platform)) 157 | { 158 | if (!soundbankInfos[(bankName,bankType)].TryGetValue(language, 159 | out AkAddressablesEditorUtilities.SoundBankInfo sbInfo)) 160 | { 161 | if (int.TryParse(language, out int result)) 162 | UnityEngine.Debug.LogError( 163 | "Wwise Unity Addressables: Sub-folders for generated files currently not supported. Please turn off the option in Wwise under Project Settings -> SoundBanks"); 164 | else 165 | UnityEngine.Debug.LogError( 166 | $"Wwise Unity Addressables: Unable to process asset at path {assetPath}: Unrecognized language {language}"); 167 | continue; 168 | } 169 | 170 | List MediaIds = sbInfo.streamedFileIds; 171 | bankAsset.UpdateLocalizationLanguages(platform, soundbankInfos[(bankName,bankType)].Keys.ToList()); 172 | bankAsset.SetStreamingMedia(platform, language, bankAssetDir, MediaIds); 173 | EditorUtility.SetDirty(bankAsset); 174 | } 175 | } 176 | } 177 | else 178 | { 179 | UnityEngine.Debug.LogWarning( 180 | $"Wwise Unity Addressables: Can't find containing SoundBank(s) for event {name}"); 181 | } 182 | } 183 | } 184 | finally 185 | { 186 | AssetDatabase.SaveAssets(); 187 | AssetDatabase.Refresh(); 188 | } 189 | } 190 | public static void RemoveAssetReferences(string[] deletedAssets) 191 | { 192 | HashSet bankAssetsToProcess = new HashSet(); 193 | HashSet streamingAssetsToProcess = new HashSet(); 194 | 195 | foreach (var item in deletedAssets) 196 | { 197 | if (Path.GetExtension(item) == ".bnk") 198 | { 199 | bankAssetsToProcess.Add(item); 200 | } 201 | 202 | if (Path.GetExtension(item) == ".wem") 203 | { 204 | streamingAssetsToProcess.Add(item); 205 | } 206 | } 207 | 208 | if (streamingAssetsToProcess.Count > 0) 209 | { 210 | RemoveStreamedAssetsFromBanks(streamingAssetsToProcess); 211 | RemoveAssetsFromAddressables(streamingAssetsToProcess); 212 | } 213 | 214 | if (bankAssetsToProcess.Count > 0) 215 | { 216 | RemoveBanksFromAddressableSoundbanks(bankAssetsToProcess); 217 | RemoveAssetsFromAddressables(bankAssetsToProcess); 218 | } 219 | } 220 | #if WWISE_ADDRESSABLES_24_1_OR_LATER 221 | public static void WarnForInvalidEntry() 222 | { 223 | foreach (var soundbankInfo in AkAddressablesEditorUtilities.SoundbanksInfo) 224 | { 225 | if (soundbankInfo.Value.containsInvalidEntry) 226 | { 227 | string platform = soundbankInfo.Key; 228 | string destinationBasePath; 229 | string sourceBasePath; 230 | AkBasePathGetter.GetSoundBankPaths(platform, out sourceBasePath, out destinationBasePath); 231 | Debug.LogError($"Invalid entry detected in the Soundbanks information for Platform: {platform}. Please make sure Object GUID and Object Path are checked in the WwiseProject. Then clear {sourceBasePath} and regenerate the Soundbanks."); 232 | } 233 | } 234 | } 235 | #endif 236 | struct CreateAssetEntry 237 | { 238 | public WwiseAddressableSoundBank Asset; 239 | public string Path; 240 | public string Name; 241 | } 242 | 243 | internal static AddressableAssetGroup GetOrAddGroup(AddressableAssetSettings settings, string groupName) 244 | { 245 | AddressableAssetGroup group = settings.groups.Find(x => x.Name == groupName); 246 | if (group == null) 247 | { 248 | group = settings.CreateGroup(groupName, false, false, false, new List { settings.DefaultGroup.Schemas[0] }, typeof(BundledAssetGroupSchema)); 249 | var bundleSchema = group.GetSchema(); 250 | if (bundleSchema != null) 251 | { 252 | bundleSchema.Compression = BundledAssetGroupSchema.BundleCompressionMode.Uncompressed; 253 | } 254 | } 255 | 256 | return group; 257 | } 258 | 259 | internal static async Task AddBankReferenceToAddressableBankAsset(ConcurrentDictionary addressableAssetCache, HashSet bankAssetsAdded) 260 | { 261 | List itemsToCreate = new List(); 262 | try 263 | { 264 | 265 | foreach (var bankPath in bankAssetsAdded) 266 | { 267 | string name = Path.GetFileNameWithoutExtension(bankPath); 268 | 269 | string platform; 270 | string language; 271 | string type; 272 | AkAddressablesEditorUtilities.ParseAssetPath(bankPath, out platform, out language, out type); 273 | 274 | string noPlatformAndLanguageBankAssetPath = bankPath.Replace(platform + "/", ""); 275 | noPlatformAndLanguageBankAssetPath = noPlatformAndLanguageBankAssetPath.Replace(language + "/", ""); 276 | string addressableBankAssetPath = Path.ChangeExtension(noPlatformAndLanguageBankAssetPath, ".asset"); 277 | string addressableBankAssetDirectory = Path.GetDirectoryName(noPlatformAndLanguageBankAssetPath); 278 | bool isAutoBank = type != "User"; 279 | 280 | // First find or create AddressableBank asset 281 | WwiseAddressableSoundBank addressableBankAsset = null; 282 | if (!addressableAssetCache.TryGetValue(addressableBankAssetPath, out addressableBankAsset)) 283 | { 284 | var results = AssetDatabase.FindAssets(string.Format("{0} t:{1}", name, nameof(WwiseAddressableSoundBank))); 285 | if (results.Length > 0) 286 | { 287 | foreach (var addressableBankGuid in results) 288 | { 289 | string addressableBankPath = AssetDatabase.GUIDToAssetPath(addressableBankGuid); 290 | if (addressableBankPath == addressableBankAssetPath) 291 | { 292 | addressableBankAsset = AssetDatabase.LoadAssetAtPath(addressableBankPath); 293 | } 294 | } 295 | } 296 | 297 | if (name == "Init") 298 | { 299 | AkAddressablesEditorUtilities.EnsureInitBankAssetCreated(); 300 | } 301 | 302 | if (addressableBankAsset == null) 303 | { 304 | if (!AssetDatabase.IsValidFolder(addressableBankAssetDirectory)) 305 | { 306 | StringBuilder currentPathBuilder = new StringBuilder(); 307 | if (addressableBankAssetDirectory != null) 308 | { 309 | var addressableBankAssetParts = addressableBankAssetDirectory.Split(Path.DirectorySeparatorChar); 310 | 311 | currentPathBuilder.Append(addressableBankAssetParts[0]); 312 | 313 | for (int i = 1; i < addressableBankAssetParts.Length; ++i) 314 | { 315 | string previousPath = currentPathBuilder.ToString(); 316 | 317 | currentPathBuilder.AppendFormat("/{0}", addressableBankAssetParts[i]); 318 | 319 | string currentPath = currentPathBuilder.ToString(); 320 | 321 | if (!AssetDatabase.IsValidFolder(currentPath)) 322 | { 323 | AssetDatabase.CreateFolder(previousPath, addressableBankAssetParts[i]); 324 | } 325 | } 326 | } 327 | } 328 | addressableBankAsset = ScriptableObject.CreateInstance(); 329 | addressableBankAsset.IsAutoBank = isAutoBank; 330 | itemsToCreate.Add(new CreateAssetEntry { Asset = addressableBankAsset, Path = addressableBankAssetPath, Name = name }); 331 | } 332 | else 333 | { 334 | UpdateAddressableBankReference(addressableBankAsset, name); 335 | } 336 | 337 | addressableAssetCache.AddOrUpdate(addressableBankAssetPath, addressableBankAsset, (key, oldValue) => addressableBankAsset); 338 | } 339 | 340 | if (!string.IsNullOrEmpty(platform)) 341 | { 342 | var soundbankInfos = await AkAddressablesEditorUtilities.ParsePlatformSoundbanks(platform, name, language, type); 343 | if (soundbankInfos.ContainsKey((name,type))) 344 | { 345 | addressableBankAsset.UpdateLocalizationLanguages(platform, soundbankInfos[(name,type)].Keys.ToList()); 346 | addressableBankAsset.AddOrUpdate(platform, language, new AssetReferenceWwiseBankData(AssetDatabase.AssetPathToGUID(bankPath))); 347 | 348 | EditorUtility.SetDirty(addressableBankAsset); 349 | } 350 | else 351 | { 352 | Debug.LogWarning($"Could not update {addressableBankAsset.name} with bank located at {bankPath}"); 353 | } 354 | } 355 | } 356 | } 357 | finally 358 | { 359 | if (itemsToCreate.Count > 0) 360 | { 361 | try 362 | { 363 | AssetDatabase.StartAssetEditing(); 364 | foreach (var entry in itemsToCreate) 365 | { 366 | AssetDatabase.CreateAsset(entry.Asset, entry.Path); 367 | UpdateAddressableBankReference(entry.Asset, entry.Name); 368 | } 369 | } 370 | finally 371 | { 372 | AssetDatabase.StopAssetEditing(); 373 | } 374 | } 375 | 376 | AssetDatabase.SaveAssets(); 377 | AssetDatabase.Refresh(); 378 | } 379 | } 380 | 381 | internal static void UpdateAddressableBankReference(WwiseAddressableSoundBank asset, string bankName ) 382 | { 383 | bool bankScriptableObjectUpdated = false; 384 | if (AkAssetUtilities.AddressableBankUpdated != null) 385 | { 386 | bankScriptableObjectUpdated = AkAssetUtilities.AddressableBankUpdated.GetInvocationList().Select(x => (bool)x.DynamicInvoke(asset, bankName)).Any(v => v); 387 | } 388 | if (!bankScriptableObjectUpdated) 389 | { 390 | if (bankName == WwiseInitBankReference.InitBankName) 391 | { 392 | WwiseInitBankReference.FindInitBankReferenceAndSetAddressableBank(asset, bankName); 393 | } 394 | else 395 | { 396 | AkAddressablesEditorUtilities.FindAndSetBankReference(asset, bankName); 397 | } 398 | } 399 | } 400 | 401 | internal static void RemoveStreamedAssetsFromBanks(HashSet streamingAssetsToRemove) 402 | { 403 | try 404 | { 405 | var foundBanks = AssetDatabase.FindAssets($"t:{typeof(WwiseAddressableSoundBank).Name}"); 406 | var updatedBanks = new List(); 407 | foreach (var assetPath in streamingAssetsToRemove) 408 | { 409 | string platform; 410 | string language; 411 | string type; 412 | AkAddressablesEditorUtilities.ParseAssetPath(assetPath, out platform, out language, out type); 413 | var assetGuid = AssetDatabase.AssetPathToGUID(assetPath); 414 | 415 | foreach (var bankGuid in foundBanks) 416 | { 417 | var bankPath = AssetDatabase.GUIDToAssetPath(bankGuid); 418 | var bank = AssetDatabase.LoadAssetAtPath(bankPath); 419 | if (bank.TryRemoveMedia(platform, language, assetGuid)) 420 | { 421 | EditorUtility.SetDirty(bank); 422 | } 423 | } 424 | } 425 | } 426 | finally 427 | { 428 | AssetDatabase.SaveAssets(); 429 | AssetDatabase.Refresh(); 430 | } 431 | } 432 | 433 | internal static void RemoveBanksFromAddressableSoundbanks(HashSet bankAssetsToRemove) 434 | { 435 | try 436 | { 437 | var foundBanks = AssetDatabase.FindAssets($"t:{typeof(WwiseAddressableSoundBank).Name}"); 438 | var updatedBanks = new List(); 439 | foreach (var assetPath in bankAssetsToRemove) 440 | { 441 | string platform; 442 | string language; 443 | string type; 444 | AkAddressablesEditorUtilities.ParseAssetPath(assetPath, out platform, out language, out type); 445 | var assetGuid = AssetDatabase.AssetPathToGUID(assetPath); 446 | 447 | foreach (var bankGuid in foundBanks) 448 | { 449 | var bankPath = AssetDatabase.GUIDToAssetPath(bankGuid); 450 | var bank = AssetDatabase.LoadAssetAtPath(bankPath); 451 | if (bank.TryRemoveBank(platform, language, assetGuid)) 452 | { 453 | EditorUtility.SetDirty(bank); 454 | break; 455 | } 456 | } 457 | } 458 | } 459 | finally 460 | { 461 | AssetDatabase.SaveAssets(); 462 | AssetDatabase.Refresh(); 463 | } 464 | } 465 | 466 | internal static void AddAssetsToAddressablesGroup(HashSet assetsAdded, string groupName = "") 467 | { 468 | if (AddressableAssetSettingsDefaultObject.Settings == null) 469 | { 470 | AddressableAssetSettingsDefaultObject.Settings = 471 | AddressableAssetSettings.Create(AddressableAssetSettingsDefaultObject.kDefaultConfigFolder, 472 | AddressableAssetSettingsDefaultObject.kDefaultConfigAssetName, true, true); 473 | } 474 | 475 | var settings = AddressableAssetSettingsDefaultObject.Settings; 476 | if (settings == null) 477 | { 478 | Debug.LogWarningFormat("[Addressables] settings file not found.\nPlease go to Menu/Window/Asset Management/Addressables/Groups, then click 'Create Addressables Settings' button."); 479 | return; 480 | } 481 | List groupEntriesModified = new List(); 482 | var parseGroupNames = string.IsNullOrEmpty(groupName); 483 | foreach (var assetPath in assetsAdded) 484 | { 485 | string guid = AssetDatabase.AssetPathToGUID(assetPath); 486 | 487 | string platform; 488 | string language; 489 | string type; 490 | AkAddressablesEditorUtilities.ParseAssetPath(assetPath, out platform, out language, out type); 491 | AddressableMetadata assetMetadata = ScriptableObject.CreateInstance(); 492 | 493 | if (parseGroupNames) 494 | { 495 | if (string.IsNullOrEmpty(platform)) 496 | { 497 | Debug.LogError($"Wwise Addressables import : could not parse platform for {assetPath}. It will not be made addressable."); 498 | continue; 499 | } 500 | if (GetAssetMetadataDelegate != null) 501 | { 502 | if (!GetAssetMetadataDelegate.Invoke(Path.GetFileName(assetPath), platform, language, ref assetMetadata)) 503 | { 504 | // If we can't find a metadata asset use the default group name 505 | assetMetadata.groupName = GetDefaultAddressableGroup(Path.GetFileName(assetPath), platform); 506 | } 507 | } 508 | else 509 | { 510 | assetMetadata.groupName = GetDefaultAddressableGroup(Path.GetFileName(assetPath), platform); 511 | } 512 | } 513 | else 514 | { 515 | assetMetadata.groupName = groupName; 516 | } 517 | 518 | AddressableAssetGroup group = GetOrAddGroup(settings, assetMetadata.groupName); 519 | var groupEntry = settings.CreateOrMoveEntry(guid, group); 520 | if (groupEntry != null) 521 | { 522 | if (assetMetadata.labels.Count >0) 523 | { 524 | foreach (string label in assetMetadata.labels) 525 | { 526 | groupEntry.labels.Add(label); 527 | } 528 | } 529 | groupEntriesModified.Add(groupEntry); 530 | } 531 | } 532 | if (groupEntriesModified.Count > 0) 533 | { 534 | settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, groupEntriesModified, true); 535 | AssetDatabase.SaveAssets(); 536 | } 537 | } 538 | 539 | static string GetDefaultAddressableGroup(string assetName, string platform) 540 | { 541 | string groupName = $"WwiseData_{platform}"; 542 | if (assetName == "Init.bnk") 543 | { 544 | groupName = $"WwiseData_{platform}_InitBank"; 545 | } 546 | return groupName; 547 | } 548 | 549 | internal static void RemoveAssetsFromAddressables(HashSet assetsToRemove) 550 | { 551 | if (AddressableAssetSettingsDefaultObject.Settings == null) 552 | { 553 | return; 554 | } 555 | 556 | var settings = AddressableAssetSettingsDefaultObject.Settings; 557 | if (settings == null) 558 | { 559 | Debug.LogWarningFormat("[Addressables] settings file not found.\nPlease go to Menu/Window/Asset Management/Addressables/Groups, then click 'Create Addressables Settings' button."); 560 | return; 561 | } 562 | 563 | foreach (var assetPath in assetsToRemove) 564 | { 565 | string guid = AssetDatabase.AssetPathToGUID(assetPath); 566 | var assetEntry = settings.FindAssetEntry(guid); 567 | if (assetEntry == null) 568 | { 569 | return; 570 | } 571 | var parentGroup = assetEntry.parentGroup; 572 | settings.RemoveAssetEntry(guid); 573 | } 574 | } 575 | } 576 | } 577 | #endif // AK_WWISE_ADDRESSABLES 578 | -------------------------------------------------------------------------------- /Runtime/AkAddressableBankManager.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The content of this file includes portions of the proprietary AUDIOKINETIC Wwise 3 | Technology released in source code form as part of the game integration package. 4 | The content of this file may not be used without valid licenses to the 5 | AUDIOKINETIC Wwise Technology. 6 | Note that the use of the game engine is subject to the Unity(R) Terms of 7 | Service at https://unity3d.com/legal/terms-of-service 8 | 9 | License Usage 10 | 11 | Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use 12 | this file in accordance with the end user license agreement provided with the 13 | software or, alternatively, in accordance with the terms contained 14 | in a written agreement between you and Audiokinetic Inc. 15 | Copyright (c) 2025 Audiokinetic Inc. 16 | *******************************************************************************/ 17 | 18 | #if AK_WWISE_ADDRESSABLES && UNITY_ADDRESSABLES 19 | 20 | using System; 21 | using System.Collections.Concurrent; 22 | using System.Collections.Generic; 23 | using System.IO; 24 | using System.Linq; 25 | using System.Reflection; 26 | using System.Runtime.InteropServices; 27 | using System.Threading.Tasks; 28 | using UnityEngine.AddressableAssets; 29 | using UnityEngine.ResourceManagement.AsyncOperations; 30 | 31 | #if UNITY_EDITOR 32 | using UnityEditor; 33 | using UnityEngine; 34 | using UnityEngine.SceneManagement; 35 | #endif 36 | 37 | namespace AK.Wwise.Unity.WwiseAddressables 38 | { 39 | public class AkAddressableBankManager 40 | { 41 | public static ConcurrentDictionary<(string, bool), WwiseAddressableSoundBank> m_AddressableBanks = 42 | new ConcurrentDictionary<(string, bool), WwiseAddressableSoundBank>(); 43 | 44 | 45 | public static ConcurrentDictionary<(string, bool), WwiseAddressableSoundBank> GetValidBanks() 46 | { 47 | bool needsCleanup = false; 48 | 49 | foreach (var kvp in m_AddressableBanks) 50 | { 51 | if (kvp.Value == null) 52 | { 53 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager: An Addressable Bank was invalid. An editor restart may be required. Did you delete the bank or it's asset while it was still loaded?"); 54 | needsCleanup = true; 55 | break; 56 | } 57 | } 58 | 59 | if (needsCleanup) 60 | { 61 | var newDictionary = new ConcurrentDictionary<(string, bool), WwiseAddressableSoundBank>(); 62 | 63 | foreach (var kvp in m_AddressableBanks) 64 | { 65 | if (kvp.Value != null) 66 | { 67 | newDictionary.TryAdd(kvp.Key, kvp.Value); 68 | } 69 | } 70 | 71 | m_AddressableBanks = newDictionary; 72 | } 73 | 74 | return m_AddressableBanks; 75 | } 76 | 77 | public static ConcurrentDictionary m_BanksToUnload = 78 | new ConcurrentDictionary(); 79 | 80 | private static ConcurrentDictionary m_EventsToFireOnBankLoad = 81 | new ConcurrentDictionary(); 82 | 83 | private static ConcurrentDictionary m_BankHandles = 84 | new ConcurrentDictionary(); 85 | 86 | public static ConcurrentDictionary BankHandles 87 | { 88 | get 89 | { 90 | return m_BankHandles; 91 | } 92 | } 93 | 94 | private static readonly System.Collections.Generic.List m_BanksToUnloadHandle = 95 | new System.Collections.Generic.List(); 96 | 97 | public const uint INVALID_SOUND_BANK_ID = 0; 98 | 99 | private static WwiseAddressableSoundBank initBank; 100 | public static WwiseAddressableSoundBank InitBank 101 | { 102 | get 103 | { 104 | if (initBank == null) 105 | { 106 | initBank = FindInitBank(); 107 | } 108 | return initBank; 109 | } 110 | } 111 | private static AkAddressableBankManager instance; 112 | public static AkAddressableBankManager Instance 113 | { 114 | get 115 | { 116 | if (instance == null) 117 | { 118 | instance = new AkAddressableBankManager(); 119 | } 120 | return instance; 121 | } 122 | private set { Instance = value; } 123 | } 124 | 125 | public struct BankHandle : IEquatable 126 | { 127 | public WwiseAddressableSoundBank Bank; 128 | public uint SoundBankId; 129 | public bool IgnoreRefCount; 130 | public bool RemoveFromBankDictionary; 131 | public int RefCount; 132 | 133 | public BankHandle(WwiseAddressableSoundBank bank, bool ignoreRefCount = false, bool removeFromBankDictionary = false) 134 | { 135 | Bank = bank; 136 | SoundBankId = bank.SoundbankId; 137 | IgnoreRefCount = ignoreRefCount; 138 | RemoveFromBankDictionary = removeFromBankDictionary; 139 | RefCount = 0; 140 | } 141 | 142 | public void IncRef() 143 | { 144 | if (RefCount == 0) 145 | { 146 | m_BanksToUnloadHandle.Remove(this); 147 | } 148 | RefCount++; 149 | } 150 | 151 | public void DecRef(bool ignoreRefCount) 152 | { 153 | if (RefCount == 0) 154 | { 155 | return; 156 | } 157 | 158 | if (ignoreRefCount) 159 | { 160 | RefCount = 0; 161 | } 162 | else 163 | { 164 | RefCount--; 165 | } 166 | 167 | if (RefCount == 0) 168 | { 169 | m_BanksToUnloadHandle.Add(this); 170 | } 171 | } 172 | 173 | public void UnloadBank() 174 | { 175 | if (!IgnoreRefCount) 176 | { 177 | Bank.refCount = Math.Max(0, Bank.refCount - 1); 178 | if (Bank.refCount != 0) 179 | { 180 | return; 181 | } 182 | } 183 | 184 | if (Bank.loadState == BankLoadState.Loading || Bank.loadState == BankLoadState.WaitingForPrepareEvent) 185 | { 186 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: {Bank.name} will be unloaded after it is done loading"); 187 | m_BanksToUnload.TryAdd(Bank.name, Bank.name); 188 | return; 189 | } 190 | 191 | if (Bank.loadState == BankLoadState.Unloaded) 192 | { 193 | #if WWISE_2024_OR_LATER 194 | AkUnitySoundEngine.PrepareEvent(AkPreparationType.Preparation_Unload, new string[] { Bank.name }, 1); 195 | #else 196 | AkSoundEngine.PrepareEvent(AkPreparationType.Preparation_Unload, new string[] { Bank.name }, 1); 197 | #endif 198 | UnityEngine.Debug.Log($"Wwise Addressables Bank Manager: {Bank.name} is already unloaded."); 199 | return; 200 | } 201 | 202 | if (Bank.loadState == BankLoadState.Loaded) 203 | { 204 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: Unloading {Bank.name} sound Bank - Bank ID : {Bank.soundbankId}"); 205 | if (Bank.bankType != 0) 206 | { 207 | #if WWISE_2024_OR_LATER 208 | AkUnitySoundEngine.PrepareEvent(AkPreparationType.Preparation_Unload, new string[] { Bank.name }, 1); 209 | AkUnitySoundEngine.UnloadBank(Bank.soundbankId, System.IntPtr.Zero, Bank.bankType); 210 | #else 211 | AkSoundEngine.PrepareEvent(AkPreparationType.Preparation_Unload, new string[] { Bank.name }, 1); 212 | AkSoundEngine.UnloadBank(Bank.soundbankId, System.IntPtr.Zero, Bank.bankType); 213 | #endif 214 | } 215 | else 216 | { 217 | #if WWISE_2024_OR_LATER 218 | AkUnitySoundEngine.UnloadBank(Bank.soundbankId, System.IntPtr.Zero); 219 | #else 220 | AkSoundEngine.UnloadBank(Bank.soundbankId, System.IntPtr.Zero); 221 | #endif 222 | } 223 | } 224 | 225 | m_BanksToUnload.TryRemove(Bank.name, out _); 226 | Bank.soundbankId = 0; 227 | Bank.refCount = 0; 228 | Bank.loadState = BankLoadState.Unloaded; 229 | 230 | if (RemoveFromBankDictionary) 231 | { 232 | if (!m_AddressableBanks.TryRemove((Bank.name, Bank.isAutoBank), out _)) 233 | { 234 | #if UNITY_EDITOR 235 | // Don't unnecessarily log messages when caused by domain reload 236 | if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode && !UnityEditor.EditorApplication.isPlaying) 237 | { 238 | return; 239 | } 240 | #endif 241 | if (InitBank && Bank.name != InitBank.name) 242 | { 243 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager: Unloaded {Bank.name}, but it was not in the list of loaded banks"); 244 | } 245 | } 246 | } 247 | } 248 | 249 | public bool Equals(BankHandle other) 250 | { 251 | return SoundBankId == other.SoundBankId; 252 | } 253 | 254 | public override bool Equals(object obj) 255 | { 256 | return obj is BankHandle other && Equals(other); 257 | } 258 | 259 | public override int GetHashCode() 260 | { 261 | return (int)SoundBankId; 262 | } 263 | } 264 | 265 | private uint? m_wwiseMajorVersion = null; 266 | public uint WwiseMajorVersion 267 | { 268 | get 269 | { 270 | if (m_wwiseMajorVersion == null) 271 | { 272 | #if WWISE_2024_OR_LATER 273 | m_wwiseMajorVersion = AkUnitySoundEngine.GetMajorMinorVersion() >> 16; 274 | #else 275 | m_wwiseMajorVersion = AkSoundEngine.GetMajorMinorVersion() >> 16; 276 | #endif 277 | } 278 | return (uint)m_wwiseMajorVersion; 279 | } 280 | } 281 | 282 | public string WriteableMediaDirectory 283 | { 284 | get 285 | { 286 | return Path.Combine(UnityEngine.Application.persistentDataPath, "Media"); 287 | } 288 | } 289 | 290 | private static WwiseAddressableSoundBank FindInitBank() 291 | { 292 | #if UNITY_EDITOR 293 | //Don't log an error from not finding the initBankHolder, as CreateWwiseGlobal will ensure the initBankHolder is added either on Scene creation or Scene reload from the reload script. 294 | if (!Application.isPlaying && AkWwiseEditorSettings.Instance.CreateWwiseGlobal) 295 | { 296 | return null; 297 | } 298 | #endif 299 | 300 | var foundBank = UnityEngine.MonoBehaviour.FindObjectsOfType(); 301 | if (foundBank.Count() == 0) 302 | { 303 | UnityEngine.Debug.LogError("Wwise Addressables: There is no InitBankHolder in the scene, please add one for Wwise to function properly."); 304 | return null; 305 | } 306 | 307 | if (foundBank.Count() > 1) 308 | { 309 | UnityEngine.Debug.LogError("Wwise Addressables: There is more than one InitBankHolder in the scene, which is not recommended."); 310 | } 311 | 312 | WwiseAddressableSoundBank InitBank = foundBank[0].GetAddressableInitBank(); 313 | if (InitBank == null) 314 | { 315 | UnityEngine.Debug.LogError("Wwise Addressables: The InitBankHolder could not get a valid reference to the Init bank."); 316 | return null; 317 | 318 | } 319 | 320 | return InitBank; 321 | } 322 | 323 | struct EventContainer 324 | { 325 | public string eventName; 326 | public object eventObject; 327 | public string methodName; 328 | public object[] methodArgs; 329 | public Type[] methodArgTypes; 330 | } 331 | 332 | bool InitBankLoaded 333 | { 334 | get { return (InitBank != null && InitBank.loadState == BankLoadState.Loaded); } 335 | } 336 | 337 | public void UnloadAllBanks(bool clearBankDictionary = true) 338 | { 339 | foreach (var bank in GetValidBanks().Values) 340 | { 341 | UnloadBank(bank, ignoreRefCount: true, removeFromBankDictionary: false); ; 342 | } 343 | if (clearBankDictionary) 344 | { 345 | m_AddressableBanks.Clear(); 346 | } 347 | } 348 | 349 | public void ReloadAllBanks() 350 | { 351 | var m_banksToReload = new ConcurrentDictionary<(string, bool), WwiseAddressableSoundBank>(m_AddressableBanks); 352 | UnloadAllBanks(); 353 | UnloadInitBank(); 354 | #if WWISE_ADDRESSABLES_23_1_OR_LATER || WWISE_ADDRESSABLES_POST_2023 355 | LoadInitBank(AkWwiseInitializationSettings.Instance.LoadBanksAsynchronously); 356 | #else 357 | LoadInitBank(); 358 | #endif 359 | 360 | 361 | foreach (var bank in m_banksToReload.Values) 362 | { 363 | LoadBank(bank, bank.decodeBank, bank.saveDecodedBank); 364 | } 365 | } 366 | 367 | public void SetLanguageAndReloadLocalizedBanks(string language, bool parseBanks = true) 368 | { 369 | var banksToReload = new List(); 370 | if (parseBanks) 371 | { 372 | foreach (var bank in GetValidBanks().Values) 373 | { 374 | if (bank.currentLanguage == "SFX" || bank.currentLanguage == language) 375 | continue; 376 | banksToReload.Add(bank); 377 | } 378 | foreach (var bank in banksToReload) 379 | { 380 | UnloadBank(bank, ignoreRefCount: true, removeFromBankDictionary: true); 381 | } 382 | } 383 | DoUnloadBank(); 384 | UnloadInitBank(); 385 | m_EventsToFireOnBankLoad.Clear(); 386 | #if WWISE_2024_OR_LATER 387 | AkUnitySoundEngine.SetCurrentLanguage(language); 388 | AkUnitySoundEngine.RenderAudio(); 389 | #else 390 | AkSoundEngine.SetCurrentLanguage(language); 391 | AkSoundEngine.RenderAudio(); 392 | #endif 393 | #if WWISE_ADDRESSABLES_23_1_OR_LATER || WWISE_ADDRESSABLES_POST_2023 394 | LoadInitBank(AkWwiseInitializationSettings.Instance.LoadBanksAsynchronously); 395 | #else 396 | LoadInitBank(); 397 | #endif 398 | foreach (var bank in banksToReload) 399 | { 400 | LoadBank(bank, bank.decodeBank, bank.saveDecodedBank); 401 | } 402 | } 403 | 404 | public void LoadInitBank(bool loadAsync = true) 405 | { 406 | if (InitBank != null) 407 | { 408 | LoadBank(InitBank, addToBankDictionary: false, loadAsync: loadAsync); 409 | } 410 | } 411 | 412 | public void UnloadInitBank() 413 | { 414 | //Not using the accessor here as if the initBank wasn't set on load, there's no point trying to unload it. 415 | if (initBank != null) 416 | { 417 | BankHandle initBankHandle = new BankHandle(InitBank, ignoreRefCount: true, removeFromBankDictionary: false); 418 | initBankHandle.UnloadBank(); 419 | m_BankHandles.TryRemove(InitBank.name, out var outHandle); 420 | } 421 | } 422 | //Todo : support decoding banks and saving decoded banks 423 | public async Task LoadBank(WwiseAddressableSoundBank bank, bool decodeBank = false, bool saveDecodedBank = false, bool addToBankDictionary = true, bool loadAsync = true) 424 | { 425 | bank.decodeBank = decodeBank; 426 | bank.saveDecodedBank = saveDecodedBank; 427 | if (m_AddressableBanks.ContainsKey((bank.name, bank.isAutoBank))) 428 | { 429 | m_AddressableBanks.TryGetValue((bank.name, bank.isAutoBank), out bank); 430 | } 431 | else if (addToBankDictionary) 432 | { 433 | m_AddressableBanks.TryAdd((bank.name, bank.isAutoBank), bank); 434 | } 435 | 436 | if (bank.loadState == BankLoadState.Unloaded || bank.loadState == BankLoadState.WaitingForInitBankToLoad) 437 | { 438 | if (!InitBankLoaded && bank.name != "Init") 439 | { 440 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: {bank.name} bank will be loaded after the init bank is loaded"); 441 | bank.loadState = BankLoadState.WaitingForInitBankToLoad; 442 | return; 443 | } 444 | } 445 | if (bank.loadState == BankLoadState.Loading) 446 | { 447 | bank.refCount += 1; 448 | return; 449 | } 450 | 451 | if (bank.loadState == BankLoadState.Loaded) 452 | { 453 | bool handleFound = m_BankHandles.TryGetValue(bank.name, out var handle); 454 | if (!handleFound) 455 | { 456 | return; 457 | } 458 | handle.IncRef(); 459 | m_BankHandles.AddOrUpdate( 460 | bank.name, 461 | key => new BankHandle(bank), // Add new instance if key does not exist 462 | (key, existingValue) => 463 | { 464 | existingValue.IncRef(); 465 | return existingValue; // Update the existing instance 466 | } 467 | ); 468 | return; 469 | } 470 | 471 | bank.refCount += 1; 472 | bank.loadState = BankLoadState.Loading; 473 | 474 | if (bank.Data == null) 475 | { 476 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager : {bank.name} could not be loaded - Bank reference not set"); 477 | m_AddressableBanks.TryRemove((bank.name, bank.isAutoBank), out _); 478 | return; 479 | } 480 | 481 | AssetReferenceWwiseBankData bankData; 482 | if (bank.Data.ContainsKey("SFX")) 483 | { 484 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: Loading {bank.name} bank"); 485 | bankData = bank.Data["SFX"]; 486 | bank.currentLanguage = "SFX"; 487 | } 488 | else 489 | { 490 | #if WWISE_2024_OR_LATER 491 | var currentLanguage = AkUnitySoundEngine.GetCurrentLanguage(); 492 | #else 493 | var currentLanguage = AkSoundEngine.GetCurrentLanguage(); 494 | #endif 495 | if (bank.Data.ContainsKey(currentLanguage)) 496 | { 497 | bankData = bank.Data[currentLanguage]; 498 | bank.currentLanguage = currentLanguage; 499 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: Loading {bank.name} - {currentLanguage}"); 500 | } 501 | else 502 | { 503 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager: {bank.name} could not be loaded in {currentLanguage} language "); 504 | m_AddressableBanks.TryRemove((bank.name, bank.isAutoBank), out _); 505 | bank.loadState = BankLoadState.Unloaded; 506 | bank.refCount -= 1; 507 | return; 508 | } 509 | } 510 | 511 | if (loadAsync) 512 | { 513 | await LoadBankAsync(bank, bankData, true); 514 | } 515 | else 516 | { 517 | LoadBankAsync(bank, bankData, false); 518 | } 519 | } 520 | 521 | public async Task LoadBankAsync(WwiseAddressableSoundBank bank, AssetReferenceWwiseBankData bankData, bool loadAsync) 522 | { 523 | AsyncOperationHandle asyncHandle = new AsyncOperationHandle(); 524 | WwiseSoundBankAsset soundBankAsset; 525 | if (bankData.OperationHandle.IsValid()) 526 | { 527 | soundBankAsset = (WwiseSoundBankAsset)bankData.Asset; 528 | asyncHandle = bankData.OperationHandle; 529 | } 530 | else 531 | { 532 | asyncHandle = bankData.LoadAssetAsync(); 533 | } 534 | #if UNITY_WEBGL && !UNITY_EDITOR 535 | // On WebGL, we MUST load asynchronously in order to yield back to the browser. 536 | // Failing to do so will result in the thread blocking forever and the asset will never be loaded. 537 | soundBankAsset = (WwiseSoundBankAsset)await asyncHandle.Task; 538 | #else 539 | if (loadAsync) 540 | { 541 | soundBankAsset = (WwiseSoundBankAsset)await asyncHandle.Task; 542 | } 543 | else 544 | { 545 | soundBankAsset = (WwiseSoundBankAsset)asyncHandle.WaitForCompletion(); 546 | } 547 | #endif 548 | //AsyncHandle gets corrupted in Unity 2021 but properly returns the loaded Asset as expected 549 | #if UNITY_2021_1_OR_NEWER 550 | if (soundBankAsset) 551 | #else 552 | if (asyncHandle.IsValid() && asyncHandle.Status == AsyncOperationStatus.Succeeded) 553 | #endif 554 | { 555 | bank.eventNames = new HashSet(soundBankAsset.eventNames); 556 | var data = soundBankAsset.RawData; 557 | bank.GCHandle = GCHandle.Alloc(data, GCHandleType.Pinned); 558 | 559 | #if WWISE_2024_OR_LATER 560 | var result = AkUnitySoundEngine.LoadBankMemoryCopy(bank.GCHandle.AddrOfPinnedObject(), (uint)data.Length, out uint bankID, out uint bankType); 561 | #else 562 | var result = AkSoundEngine.LoadBankMemoryCopy(bank.GCHandle.AddrOfPinnedObject(), (uint)data.Length, out uint bankID, out uint bankType); 563 | #endif 564 | if (result == AKRESULT.AK_Success) 565 | { 566 | if (m_BankHandles.TryGetValue(bank.name, out var handle)) 567 | { 568 | // Bank already loaded, increment its ref count. 569 | handle.IncRef(); 570 | return; 571 | } 572 | handle = new BankHandle(bank, false, false); 573 | handle.IncRef(); 574 | m_BankHandles.TryAdd(bank.name, handle); 575 | bank.soundbankId = bankID; 576 | bank.bankType = bankType; 577 | //Auto bank will set itself as loaded later 578 | if(!bank.isAutoBank) 579 | { 580 | bank.loadState = BankLoadState.Loaded; 581 | } 582 | else 583 | { 584 | bank.loadState = BankLoadState.WaitingForPrepareEvent; 585 | } 586 | } 587 | else if (result == AKRESULT.AK_BankAlreadyLoaded) 588 | { 589 | bank.loadState = BankLoadState.Loaded; 590 | } 591 | else 592 | { 593 | bank.soundbankId = INVALID_SOUND_BANK_ID; 594 | bank.loadState = BankLoadState.LoadFailed; 595 | if ((int)result == 100) // 100 == AK_InvalidBankType (using the raw int value until this package only supports Wwise 22.1 and up) 596 | { 597 | UnityEngine.Debug.LogWarning($"Wwise Addressable Bank Manager : Bank {bank.name} is an auto-generated bank. The Unity Wwise Addressables package only supports user-defined banks. Avoid using auto-generated banks."); 598 | } 599 | else 600 | { 601 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager : Sound Engine failed to load {bank.name} SoundBank"); 602 | } 603 | } 604 | bank.GCHandle.Free(); 605 | 606 | if (bank.StreamingMedia != null) 607 | { 608 | var assetKeys = new List(); 609 | foreach (var language in bank.StreamingMedia.Keys) 610 | { 611 | foreach (var streamedAsset in bank.StreamingMedia[language].media) 612 | { 613 | if (streamedAsset == null) 614 | { 615 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager: Streaming media asset referenced in {bank.name} SoundBank is null"); 616 | continue; 617 | } 618 | assetKeys.Add(streamedAsset); 619 | } 620 | } 621 | if (assetKeys.Count > 0) 622 | { 623 | #if UNITY_EDITOR 624 | if ((EditorSettings.enterPlayModeOptionsEnabled && (EditorSettings.enterPlayModeOptions & EnterPlayModeOptions.DisableDomainReload) != 0) || EditorApplication.isPlaying) 625 | { 626 | var startingSceneName = SceneManager.GetActiveScene().name; 627 | #endif 628 | 629 | var streamingAssetAsyncHandle = Addressables.LoadAssetsAsync(assetKeys.AsEnumerable(), streamingMedia => 630 | { 631 | AkAssetUtilities.UpdateStreamedFileIfNecessary(WriteableMediaDirectory, streamingMedia); 632 | }, Addressables.MergeMode.Union, false); 633 | 634 | await streamingAssetAsyncHandle.Task; 635 | Addressables.Release(streamingAssetAsyncHandle); 636 | #if UNITY_EDITOR 637 | } 638 | #endif 639 | } 640 | } 641 | } 642 | else 643 | { 644 | UnityEngine.Debug.LogError($"Wwise Addressable Bank Manager : Failed to load {bank.name} SoundBank"); 645 | bank.loadState = BankLoadState.LoadFailed; 646 | } 647 | 648 | // WG-60155 Release the bank asset AFTER streaming media assets are handled, otherwise Unity can churn needlessly if they are all in the same asset bundle! 649 | OnBankLoaded(bank); 650 | if (asyncHandle.IsValid()) 651 | { 652 | Addressables.Release(asyncHandle); 653 | } 654 | } 655 | 656 | public void DoUnloadBank() 657 | { 658 | foreach (var bankToUnload in m_BanksToUnloadHandle) 659 | { 660 | bankToUnload.UnloadBank(); 661 | m_BankHandles.TryRemove(bankToUnload.Bank.name, out var outHandle); 662 | } 663 | 664 | m_BanksToUnloadHandle.Clear(); 665 | } 666 | public void UnloadBank(WwiseAddressableSoundBank bank, bool ignoreRefCount = true, bool removeFromBankDictionary = true) 667 | { 668 | if (m_BankHandles.TryGetValue(bank.name, out var handle)) 669 | { 670 | var handleOriginal = handle; 671 | handle.DecRef(ignoreRefCount); 672 | if (!handle.RemoveFromBankDictionary) 673 | { 674 | handle.RemoveFromBankDictionary = removeFromBankDictionary; 675 | } 676 | m_BankHandles.TryUpdate(bank.name, handle, handleOriginal); 677 | } 678 | } 679 | 680 | public bool LoadedBankContainsEvent(string eventName, uint eventId, object eventObject, string methodName, Type[] methodArgTypes, object[] methodArgs) 681 | { 682 | foreach (var bank in GetValidBanks().Values) 683 | { 684 | if (bank.loadState == BankLoadState.Loaded && bank.eventNames != null && bank.eventNames.Contains(eventName)) 685 | { 686 | return true; 687 | } 688 | } 689 | 690 | if (methodName == "ExecuteAction") 691 | { 692 | UnityEngine.Debug.LogWarning($"Wwise Addressables: Trying to execute action on {eventName} but its soundbank hasn't loaded. Aborting."); 693 | return false; 694 | } 695 | 696 | UnityEngine.Debug.LogWarning($"Wwise Addressables: {eventName} will be delayed, because its soundbank has not been loaded."); 697 | m_EventsToFireOnBankLoad.TryAdd(eventId, new EventContainer { eventName = eventName, eventObject = eventObject, methodName = methodName, methodArgTypes = methodArgTypes, methodArgs = methodArgs }); 698 | return false; 699 | } 700 | 701 | private Type m_AkEventType; 702 | private Type EventType 703 | { 704 | get 705 | { 706 | if (m_AkEventType == null) 707 | { 708 | var assembly = Assembly.Load("AK.Wwise.Unity.API.WwiseTypes"); 709 | m_AkEventType = assembly.GetType("AK.Wwise.Event"); 710 | } 711 | return m_AkEventType; 712 | } 713 | } 714 | 715 | public void OnAutoBankLoaded(WwiseAddressableSoundBank bank) 716 | { 717 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager : Loaded {bank.name} AutoBank - Bank ID : {bank.soundbankId}"); 718 | bank.loadState = BankLoadState.Loaded; 719 | FireEventOnBankLoad(bank, false); 720 | } 721 | 722 | private void FireEventOnBankLoad(WwiseAddressableSoundBank bank, bool skipAutoBank) 723 | { 724 | //Fire any events that were waiting on the bank load 725 | var eventsToRemove = new List(); 726 | foreach (var e in m_EventsToFireOnBankLoad) 727 | { 728 | if (bank.eventNames.Contains(e.Value.eventName)) 729 | { 730 | if (skipAutoBank && bank.isAutoBank) 731 | continue; 732 | 733 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager: Triggering delayed event {e.Value.eventName}"); 734 | MethodInfo handleEvent = EventType.GetMethod(e.Value.methodName, e.Value.methodArgTypes); 735 | handleEvent.Invoke(e.Value.eventObject, e.Value.methodArgs); 736 | eventsToRemove.Add(e.Key); 737 | } 738 | } 739 | 740 | 741 | foreach (var e in eventsToRemove) 742 | { 743 | m_EventsToFireOnBankLoad.TryRemove(e, out _); 744 | } 745 | } 746 | 747 | private void OnBankLoaded(WwiseAddressableSoundBank bank) 748 | { 749 | if (bank.loadState == BankLoadState.Loaded) 750 | { 751 | UnityEngine.Debug.Log($"Wwise Addressable Bank Manager : Loaded {bank.name} bank - Bank ID : {bank.soundbankId}"); 752 | if (InitBankLoaded && bank.name == InitBank.name) 753 | { 754 | foreach (var b in GetValidBanks().Values) 755 | { 756 | if (b.loadState == BankLoadState.WaitingForInitBankToLoad) 757 | { 758 | LoadBank(b, b.decodeBank, b.saveDecodedBank); 759 | } 760 | } 761 | } 762 | 763 | FireEventOnBankLoad(bank, true); 764 | } 765 | 766 | else if (bank.loadState == BankLoadState.WaitingForPrepareEvent) 767 | { 768 | bank.BroadcastBankLoaded(); 769 | } 770 | 771 | //Reset bank state if load failed 772 | if (bank.loadState == BankLoadState.LoadFailed) 773 | { 774 | UnloadBank(bank, ignoreRefCount : true); 775 | } 776 | 777 | if (m_BanksToUnload.Keys.Contains(bank.name)) 778 | { 779 | UnloadBank(bank); 780 | } 781 | } 782 | 783 | ~AkAddressableBankManager() 784 | { 785 | #if WWISE_2024_OR_LATER 786 | AkUnitySoundEngine.ClearBanks(); 787 | #else 788 | AkSoundEngine.ClearBanks(); 789 | #endif 790 | } 791 | } 792 | } 793 | 794 | #endif // AK_WWISE_ADDRESSABLES --------------------------------------------------------------------------------