public class AkWwiseInitializationSettings : AkCommonPlatformSettings { [UnityEngine.HideInInspector] public System.Collections.Generic.List PlatformSettingsNameList = new System.Collections.Generic.List(); [UnityEngine.HideInInspector] public System.Collections.Generic.List PlatformSettingsList = new System.Collections.Generic.List(); [UnityEngine.HideInInspector] public System.Collections.Generic.List InvalidReferencePlatforms = new System.Collections.Generic.List(); public bool IsValid { get { return PlatformSettingsNameList.Count == PlatformSettingsList.Count; } } public int Count { get { return PlatformSettingsList.Count; } } [UnityEngine.HideInInspector] public AkCommonUserSettings UserSettings; [UnityEngine.HideInInspector] public AkCommonAdvancedSettings AdvancedSettings; [UnityEngine.HideInInspector] public AkCommonCommSettings CommsSettings; protected override AkCommonUserSettings GetUserSettings() { return UserSettings; } protected override AkCommonAdvancedSettings GetAdvancedSettings() { return AdvancedSettings; } protected override AkCommonCommSettings GetCommsSettings() { return CommsSettings; } private static readonly string[] AllGlobalValues = new[] { "UserSettings.m_BasePath", "UserSettings.m_StartupLanguage", "UserSettings.m_EngineLogging", "UserSettings.m_MaximumNumberOfPositioningPaths", "UserSettings.m_MemoryCutoffThreshold", "UserSettings.m_CommandQueueSize", "UserSettings.m_SamplesPerFrame", "UserSettings.m_MainOutputSettings.m_AudioDeviceShareset", "UserSettings.m_MainOutputSettings.m_DeviceID", "UserSettings.m_MainOutputSettings.m_PanningRule", "UserSettings.m_MainOutputSettings.m_ChannelConfig.m_ChannelConfigType", "UserSettings.m_MainOutputSettings.m_ChannelConfig.m_ChannelMask", "UserSettings.m_MainOutputSettings.m_ChannelConfig.m_NumberOfChannels", "UserSettings.m_StreamingLookAheadRatio", "UserSettings.m_SampleRate", "UserSettings.m_NumberOfRefillsInVoice", "UserSettings.m_SpatialAudioSettings.m_MaxSoundPropagationDepth", "UserSettings.m_SpatialAudioSettings.m_MovementThreshold", "UserSettings.m_SpatialAudioSettings.m_NumberOfPrimaryRays", "UserSettings.m_SpatialAudioSettings.m_MaxReflectionOrder", "UserSettings.m_SpatialAudioSettings.m_MaxPathLength", "UserSettings.m_SpatialAudioSettings.m_CPULimitPercentage", "UserSettings.m_SpatialAudioSettings.m_EnableDiffractionOnReflections", "UserSettings.m_SpatialAudioSettings.m_EnableGeometricDiffractionAndTransmission", "UserSettings.m_SpatialAudioSettings.m_CalcEmitterVirtualPosition", "UserSettings.m_SpatialAudioSettings.m_UseObstruction", "UserSettings.m_SpatialAudioSettings.m_UseOcclusion", "CommsSettings.m_PoolSize", "CommsSettings.m_DiscoveryBroadcastPort", "CommsSettings.m_CommandPort", "CommsSettings.m_NotificationPort", "CommsSettings.m_InitializeSystemComms", "CommsSettings.m_NetworkName", "AdvancedSettings.m_IOMemorySize", "AdvancedSettings.m_TargetAutoStreamBufferLengthMs", "AdvancedSettings.m_UseStreamCache", "AdvancedSettings.m_MaximumPinnedBytesInCache", "AdvancedSettings.m_EnableGameSyncPreparation", "AdvancedSettings.m_ContinuousPlaybackLookAhead", "AdvancedSettings.m_MonitorQueuePoolSize", "AdvancedSettings.m_MaximumHardwareTimeoutMs", "AdvancedSettings.m_SpatialAudioSettings.m_DiffractionShadowAttenuationFactor", "AdvancedSettings.m_SpatialAudioSettings.m_DiffractionShadowDegrees", "AdvancedSettings.m_RenderDuringFocusLoss", "AdvancedSettings.m_UseAsyncOpen", "AdvancedSettings.m_SoundBankPersistentDataPath", "AdvancedSettings.m_DebugOutOfRangeCheckEnabled", "AdvancedSettings.m_DebugOutOfRangeLimit", "AdvancedSettings.m_MemoryAllocationSizeLimit", "AdvancedSettings.m_MemoryDebugLevel" }; public abstract class PlatformSettings : AkCommonPlatformSettings { #region Ignore property list management [UnityEngine.SerializeField] [UnityEngine.HideInInspector] private System.Collections.Generic.List IgnorePropertyNameList = new System.Collections.Generic.List(); public void IgnorePropertyValue(string propertyPath) { if (IsPropertyIgnored(propertyPath)) return; IgnorePropertyNameList.Add(propertyPath); SetUseGlobalPropertyValue(propertyPath, false); } public bool IsPropertyIgnored(string propertyPath) { return IgnorePropertyNameList.Contains(propertyPath); } #endregion #region Global property list management [UnityEngine.SerializeField] [UnityEngine.HideInInspector] private System.Collections.Generic.List GlobalPropertyNameList = new System.Collections.Generic.List(); protected PlatformSettings() { SetGlobalPropertyValues(AllGlobalValues); } public void SetUseGlobalPropertyValue(string propertyPath, bool use) { if (IsUsingGlobalPropertyValue(propertyPath) == use) return; if (use) GlobalPropertyNameList.Add(propertyPath); else GlobalPropertyNameList.Remove(propertyPath); _GlobalPropertyHashSet = null; } public void SetGlobalPropertyValues(System.Collections.IEnumerable enumerable) { foreach (var item in enumerable) { string propertyPath = item as string; if (!IsUsingGlobalPropertyValue(propertyPath)) GlobalPropertyNameList.Add(propertyPath); } } private bool IsUsingGlobalPropertyValue(string propertyPath) { return GlobalPropertyNameList.Contains(propertyPath); } private System.Collections.Generic.HashSet _GlobalPropertyHashSet = null; public System.Collections.Generic.HashSet GlobalPropertyHashSet { get { if (_GlobalPropertyHashSet == null) _GlobalPropertyHashSet = new System.Collections.Generic.HashSet(GlobalPropertyNameList); return _GlobalPropertyHashSet; } set { _GlobalPropertyHashSet = value; } } #endregion #if UNITY_EDITOR protected static void RegisterPlatformSettingsClass(string platformName) where T : PlatformSettings { string className = typeof(T).Name; string currentClassName; if (m_PlatformSettingsClassNames.TryGetValue(platformName, out currentClassName) && currentClassName == className) { UnityEngine.Debug.LogWarning("WwiseUnity: The class <" + currentClassName + "> is being replaced by <" + className + "> for the reference platform: " + platformName); return; } m_PlatformSettingsClassNames[platformName] = className; } public static bool IsDistinctPlatform(string platformName) { return m_PlatformSettingsClassNames.ContainsKey(platformName); } #endif } public class CommonPlatformSettings : PlatformSettings { protected override AkCommonUserSettings GetUserSettings() { return UserSettings; } protected override AkCommonAdvancedSettings GetAdvancedSettings() { return AdvancedSettings; } protected override AkCommonCommSettings GetCommsSettings() { return CommsSettings; } [UnityEngine.HideInInspector] public AkCommonUserSettings UserSettings; [UnityEngine.HideInInspector] public AkCommonAdvancedSettings AdvancedSettings; [UnityEngine.HideInInspector] public AkCommonCommSettings CommsSettings; } #region Singleton management private static AkWwiseInitializationSettings m_Instance = null; private static AkBasePlatformSettings m_ActivePlatformSettings = null; public static AkWwiseInitializationSettings Instance { get { if (m_Instance == null) { #if UNITY_EDITOR var name = typeof(AkWwiseInitializationSettings).Name; m_Instance = GetOrCreateAsset(name, name); #else m_Instance = CreateInstance(); UnityEngine.Debug.LogWarning("WwiseUnity: No platform specific settings were created. Default initialization settings will be used."); #endif } return m_Instance; } } private static AkBasePlatformSettings GetPlatformSettings(string platformName) { var instance = Instance; if (!instance.IsValid) return instance; for (var i = 0; i < instance.Count; ++i) { var platformSettings = instance.PlatformSettingsList[i]; if (platformSettings && (string.Equals(platformName, instance.PlatformSettingsNameList[i], System.StringComparison.OrdinalIgnoreCase))) return platformSettings; } UnityEngine.Debug.LogWarning("WwiseUnity: Platform specific settings cannot be found for <" + platformName + ">. Using global settings."); return instance; } public static AkBasePlatformSettings ActivePlatformSettings { get { if (m_ActivePlatformSettings == null) m_ActivePlatformSettings = GetPlatformSettings(AkBasePathGetter.GetPlatformName()); return m_ActivePlatformSettings; } } private void OnEnable() { if (m_Instance == null) m_Instance = this; else if (m_Instance != this) UnityEngine.Debug.LogWarning("WwiseUnity: There are multiple AkWwiseInitializationSettings objects instantiated; only one will be used."); } #endregion #region Sound Engine Initialization public bool InitializeSoundEngine() { #if UNITY_EDITOR Instance.ActiveSettingsHash = GetHashOfActiveSettings(); Instance.ActiveSettingsHaveChanged = true; #endif UnityEngine.Debug.LogFormat("WwiseUnity: Wwise(R) SDK Version {0}.", AkSoundEngine.WwiseVersion); var initResult = AkSoundEngine.Init(ActivePlatformSettings.AkInitializationSettings); if (initResult != AKRESULT.AK_Success) { UnityEngine.Debug.LogError($"WwiseUnity: Failed to initialize the sound engine. Reason: {initResult}"); AkSoundEngine.Term(); return false; } if (AkSoundEngine.InitSpatialAudio(ActivePlatformSettings.AkSpatialAudioInitSettings) != AKRESULT.AK_Success) { UnityEngine.Debug.LogWarning("WwiseUnity: Failed to initialize spatial audio."); } AkSoundEngine.InitCommunication(ActivePlatformSettings.AkCommunicationSettings); var akBasePathGetterInstance = AkBasePathGetter.Get(); var soundBankBasePath = akBasePathGetterInstance.SoundBankBasePath; if (string.IsNullOrEmpty(soundBankBasePath)) { // this is a nearly impossible situation UnityEngine.Debug.LogError("WwiseUnity: Couldn't find SoundBanks base path. Terminating sound engine."); AkSoundEngine.Term(); return false; } var persistentDataPath = akBasePathGetterInstance.PersistentDataPath; var isBasePathSameAsPersistentPath = soundBankBasePath == persistentDataPath; #if UNITY_ANDROID var canSetBasePath = !isBasePathSameAsPersistentPath; var canSetPersistentDataPath = true; #else var canSetBasePath = true; var canSetPersistentDataPath = !isBasePathSameAsPersistentPath; #endif if (canSetBasePath && AkSoundEngine.SetBasePath(soundBankBasePath) != AKRESULT.AK_Success) { #if UNITY_EDITOR var format = "WwiseUnity: Failed to set SoundBanks base path to <{0}>. Make sure SoundBank path is correctly set under Edit > Project Settings > Wwise > Editor > Asset Management."; #else var format = "WwiseUnity: Failed to set SoundBanks base path to <{0}>. Make sure SoundBank path is correctly set under Edit > Project Settings > Wwise > Initialization."; #endif #if !UNITY_ANDROID || UNITY_EDITOR // It might be normal for SetBasePath to return AK_PathNotFound on Android. Silence the error log to avoid confusion. UnityEngine.Debug.LogErrorFormat(format, soundBankBasePath); #endif } if (canSetPersistentDataPath && !string.IsNullOrEmpty(persistentDataPath)) { AkSoundEngine.AddBasePath(persistentDataPath); } var decodedBankFullPath = akBasePathGetterInstance.DecodedBankFullPath; if (!string.IsNullOrEmpty(decodedBankFullPath)) { // AkSoundEngine.SetDecodedBankPath creates the folders for writing to (if they don't exist) AkSoundEngine.SetDecodedBankPath(decodedBankFullPath); // Adding decoded bank path last to ensure that it is the first one used when writing decoded banks. AkSoundEngine.AddBasePath(decodedBankFullPath); } AkSoundEngine.SetCurrentLanguage(ActivePlatformSettings.InitialLanguage); AkCallbackManager.Init(ActivePlatformSettings.CallbackManagerInitializationSettings); UnityEngine.Debug.Log("WwiseUnity: Sound engine initialized successfully."); LoadInitBank(); return true; } protected virtual void LoadInitBank() { AkBankManager.LoadInitBank(); } protected virtual void ClearBanks() { AkSoundEngine.ClearBanks(); } protected virtual void ResetBanks() { AkBankManager.Reset(); } public bool ResetSoundEngine(bool isPlaying) { if (isPlaying) { ClearBanks(); LoadInitBank(); } AkCallbackManager.Init(ActivePlatformSettings.CallbackManagerInitializationSettings); return true; } public void TerminateSoundEngine() { if (!AkSoundEngine.IsInitialized()) return; AkSoundEngine.SetOfflineRendering(false); // Stop everything, and make sure the callback buffer is empty. We try emptying as much as possible, and wait 10 ms before retrying. // Callbacks can take a long time to be posted after the call to RenderAudio(). AkSoundEngine.StopAll(); ClearBanks(); AkSoundEngine.RenderAudio(); for (var retry = 0; retry < 5;) { if (AkCallbackManager.PostCallbacks() == 0) { SleepForMilliseconds(10); ++retry; } SleepForMilliseconds(1); } AkSoundEngine.Term(); // Make sure we have no callbacks left after Term. Some might be posted during termination. AkCallbackManager.PostCallbacks(); AkCallbackManager.Term(); ResetBanks(); #if UNITY_EDITOR Instance.ActiveSettingsHash = string.Empty; Instance.ActiveSettingsHaveChanged = true; #endif } private static void SleepForMilliseconds(double milliseconds) { using (var tmpEvent = new System.Threading.ManualResetEvent(false)) tmpEvent.WaitOne(System.TimeSpan.FromMilliseconds(milliseconds)); } #endregion #if UNITY_EDITOR public static T GetOrCreateAsset(string className, string fileName) where T : AkCommonPlatformSettings { var path = System.IO.Path.Combine(AkWwiseEditorSettings.WwiseScriptableObjectRelativePath, fileName + ".asset"); var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); if (asset) return asset; var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(T).Name); foreach (var assetGuid in guids) { var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assetGuid); asset = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); if (asset) return asset; } asset = CreateInstance(className) as T; AkUtilities.CreateFolder(AkWwiseEditorSettings.WwiseScriptableObjectRelativePath); UnityEditor.AssetDatabase.CreateAsset(asset, path); return asset; } private static System.Collections.Generic.Dictionary m_PlatformSettingsClassNames = new System.Collections.Generic.Dictionary(); private const System.Reflection.BindingFlags BindingFlags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic; private static string GetHashOfActiveSettingsField(string name, object obj) { var type = obj.GetType(); if (type.IsPrimitive || type == typeof(string)) return name + ": " + obj.ToString() + "\n"; string ret = string.Empty; foreach (var subFieldInfo in type.GetFields(BindingFlags)) { var subObject = subFieldInfo.GetValue(obj); var subType = subFieldInfo.FieldType; var fields = subType.GetFields(BindingFlags); if (fields.Length == 0) ret += subFieldInfo.Name + ": " + subObject.ToString() + "\n"; else ret += GetHashOfActiveSettingsField(subFieldInfo.Name, subObject); } return ret; } public static string GetHashOfActiveSettings() { try { return GetHashOfActiveSettingsField(string.Empty, ActivePlatformSettings); } catch { return string.Empty; } } [UnityEngine.HideInInspector] private bool ActiveSettingsHaveChanged = true; [UnityEngine.HideInInspector] private string ActiveSettingsHash; public static void UpdatePlatforms() { if (!AkUtilities.IsWwiseProjectAvailable) return; var customPlatformSettingsMap = new System.Collections.Generic.Dictionary(); var instance = Instance; if (instance.IsValid) { for (var i = 0; i < instance.Count; ++i) { var settings = instance.PlatformSettingsList[i]; var name = instance.PlatformSettingsNameList[i]; if (settings && !string.IsNullOrEmpty(name)) customPlatformSettingsMap.Add(name, settings); } } var updated = false; var allCustomPlatforms = new System.Collections.Generic.List(); foreach (var pair in AkUtilities.PlatformMapping) { var referencePlatform = pair.Key; var customPlatformList = pair.Value; string className, customClassName; if (!m_PlatformSettingsClassNames.TryGetValue(referencePlatform, out className)) { if (!instance.InvalidReferencePlatforms.Contains(referencePlatform)) { instance.InvalidReferencePlatforms.Add(referencePlatform); UnityEngine.Debug.LogError("WwiseUnity: A class has not been registered for the reference platform: " + referencePlatform); } continue; } foreach (var customWwisePlatform in customPlatformList) { allCustomPlatforms.Add(customWwisePlatform); if (customPlatformSettingsMap.ContainsKey(customWwisePlatform)) continue; PlatformSettings settings; if (m_PlatformSettingsClassNames.TryGetValue(customWwisePlatform, out customClassName)) { settings = GetOrCreateAsset(customClassName, customWwisePlatform); } else { settings = GetOrCreateAsset(className, customWwisePlatform); } customPlatformSettingsMap.Add(customWwisePlatform, settings); updated = true; } } var customPlatformSettingsToRemoveMap = new System.Collections.Generic.Dictionary(); foreach (var pair in customPlatformSettingsMap) { var instantiatedCustomPlatform = pair.Key; if (!allCustomPlatforms.Contains(instantiatedCustomPlatform)) customPlatformSettingsToRemoveMap.Add(instantiatedCustomPlatform, pair.Value); } foreach (var pair in customPlatformSettingsToRemoveMap) { var instantiatedCustomPlatform = pair.Key; customPlatformSettingsMap.Remove(instantiatedCustomPlatform); var parentPath = System.IO.Path.Combine("Assets", AkWwiseEditorSettings.WwiseScriptableObjectRelativePath); var assetPath = System.IO.Path.Combine(parentPath, instantiatedCustomPlatform + ".asset"); UnityEditor.AssetDatabase.DeleteAsset(assetPath); updated = true; } if (updated) { instance.PlatformSettingsNameList.Clear(); instance.PlatformSettingsList.Clear(); AkUtilities.RepaintInspector(); var keys = System.Linq.Enumerable.ToList(customPlatformSettingsMap.Keys); keys.Sort(); foreach (var key in keys) { instance.PlatformSettingsNameList.Add(key); instance.PlatformSettingsList.Add(customPlatformSettingsMap[key]); } UnityEditor.EditorUtility.SetDirty(instance); UnityEditor.AssetDatabase.SaveAssets(); AkUtilities.RepaintInspector(); } } #region Custom Editor #if UNITY_2018_3_OR_NEWER class SettingsProvider : UnityEditor.SettingsProvider #else [UnityEditor.MenuItem("Edit/Wwise Initialization Settings..", false, (int)AkWwiseWindowOrder.WwiseInitializationSettings)] private static void WwiseInitializationSettingsMenuItem() { UnityEditor.Selection.activeObject = Instance; } [UnityEditor.CustomEditor(typeof(AkWwiseInitializationSettings))] public class Editor : UnityEditor.Editor #endif { #if UNITY_2018_3_OR_NEWER private SettingsProvider(string path) : base(path, UnityEditor.SettingsScope.Project) { } [UnityEditor.SettingsProvider] public static UnityEditor.SettingsProvider CreateMyCustomSettingsProvider() { return new SettingsProvider("Project/Wwise Initialization"); } private UnityEditor.SerializedObject serializedObject; #endif private const string UserSettings = "UserSettings"; private const string AdvancedSettings = "AdvancedSettings"; private const string CommsSettings = "CommsSettings"; private System.Collections.Generic.List PreviousPlatformSettingsList = new System.Collections.Generic.List(); private System.Collections.Generic.List GlobalSettingsGroups = new System.Collections.Generic.List(); private System.Collections.Generic.Dictionary> GlobalGroupSettingsMap = new System.Collections.Generic.Dictionary>(); private System.Collections.Generic.List PlatformSpecificSettingsGroups = new System.Collections.Generic.List(); #if UNITY_2019_1_OR_NEWER public override void OnActivate(string searchContext, UnityEngine.UIElements.VisualElement rootElement) #elif UNITY_2018_3_OR_NEWER public override void OnActivate(string searchContext, UnityEngine.Experimental.UIElements.VisualElement rootElement) #else public void OnEnable() #endif { UpdateRequired = true; #if UNITY_2018_3_OR_NEWER serializedObject = new UnityEditor.SerializedObject(Instance); #endif GlobalGroupSettingsMap.Clear(); foreach (var settingsGroup in new[] { UserSettings, AdvancedSettings, CommsSettings }) { var property = serializedObject.FindProperty(settingsGroup); if (property == null) return; var type = System.Type.GetType(property.type); foreach (var field in type.GetFields()) { var childProperty = property.FindPropertyRelative(field.Name); if (childProperty == null) continue; System.Collections.Generic.HashSet hashSet = null; if (!GlobalGroupSettingsMap.TryGetValue(settingsGroup, out hashSet)) { hashSet = new System.Collections.Generic.HashSet(); GlobalGroupSettingsMap.Add(settingsGroup, hashSet); } hashSet.Add(childProperty.propertyPath); } } GlobalSettingsGroups.Clear(); GlobalSettingsGroups.Add(new GlobalSettingsGroupData(UserSettings, serializedObject, "", GlobalGroupSettingsMap[UserSettings])); GlobalSettingsGroups.Add(new GlobalSettingsGroupData(AdvancedSettings, serializedObject, "", GlobalGroupSettingsMap[AdvancedSettings])); GlobalSettingsGroups.Add(new GlobalSettingsGroupData(CommsSettings, serializedObject, "Wwise Communication Settings", GlobalGroupSettingsMap[CommsSettings])); } #if UNITY_2018_3_OR_NEWER public override void OnGUI(string searchContext) #else public override void OnInspectorGUI() #endif { if (!Instance.IsValid) { UnityEditor.EditorGUILayout.HelpBox("Platform names do not correspond with their associated settings data.", UnityEditor.MessageType.Error); return; } var labelWidth = UnityEditor.EditorGUIUtility.labelWidth; UnityEditor.EditorGUIUtility.labelWidth += 100; UpdatePlatformData(); DrawHelpBox(); UnityEditor.EditorGUILayout.Space(); if (PreviousPlatformSettingsList.Count == 0) { if (!AkUtilities.IsWwiseProjectAvailable) { UnityEditor.EditorGUILayout.HelpBox("The Wwise project is not available. Please specify its location within the Wwise Settings.", UnityEditor.MessageType.Warning); return; } UnityEditor.EditorGUILayout.HelpBox("No Wwise platforms have been added. Editing global settings.", UnityEditor.MessageType.Warning); UnityEditor.EditorGUILayout.Space(); } UnityEditor.EditorGUI.BeginChangeCheck(); serializedObject.Update(); foreach (var setting in GlobalSettingsGroups) setting.Draw(); serializedObject.ApplyModifiedProperties(); UnityEditor.EditorGUILayout.Space(); foreach (var setting in PlatformSpecificSettingsGroups) setting.Draw(); if (UnityEditor.EditorGUI.EndChangeCheck()) Instance.ActiveSettingsHaveChanged = true; UnityEditor.EditorGUIUtility.labelWidth = labelWidth; } private bool UpdateRequired = false; private void UpdatePlatformData() { if (!UpdateRequired) { var firstNotSecond = System.Linq.Enumerable.Except(PreviousPlatformSettingsList, Instance.PlatformSettingsList); var secondNotFirst = System.Linq.Enumerable.Except(Instance.PlatformSettingsList, PreviousPlatformSettingsList); var refreshRequired = System.Linq.Enumerable.Any(firstNotSecond) || System.Linq.Enumerable.Any(secondNotFirst); if (!refreshRequired) { foreach (var platformSettings in Instance.PlatformSettingsList) { if (platformSettings == null) { refreshRequired = true; break; } } if (!refreshRequired) return; } } else { UpdateRequired = false; } PreviousPlatformSettingsList.Clear(); PlatformSpecificSettingsGroups.Clear(); var platformNames = new System.Collections.Generic.HashSet(); foreach (var setting in GlobalSettingsGroups) setting.ClearPlatformData(); for (var i = 0; i < Instance.Count; ++i) { var platformSettings = Instance.PlatformSettingsList[i]; if (!platformSettings) continue; var platformName = Instance.PlatformSettingsNameList[i]; if (string.IsNullOrEmpty(platformName)) continue; if (!platformNames.Contains(platformName)) { platformNames.Add(platformName); PreviousPlatformSettingsList.Add(platformSettings); var platform = new PlatformData { Settings = platformSettings, Name = platformName, SerializedObject = new UnityEditor.SerializedObject(platformSettings) }; foreach (var setting in GlobalSettingsGroups) setting.SetupPlatform(platform); foreach (var settingsGroup in new[] { UserSettings, AdvancedSettings, CommsSettings }) PlatformSpecificSettingsGroups.Add(new PlatformSpecificSettingsData(platform, settingsGroup, GlobalGroupSettingsMap[settingsGroup])); } } } private static void DrawHelpBox() { if (Instance.ActiveSettingsHaveChanged) { var hash = GetHashOfActiveSettings(); Instance.ActiveSettingsHaveChanged = string.IsNullOrEmpty(hash) || hash != Instance.ActiveSettingsHash; } var helpBoxText = "No changes have been made. Please be advised that changes will take effect once the Editor exits play mode."; var messageType = UnityEditor.MessageType.Info; if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || UnityEditor.EditorApplication.isPlaying || UnityEditor.BuildPipeline.isBuildingPlayer) { helpBoxText = "Changes will take effect once the Editor exits play mode."; } else if (Instance.ActiveSettingsHaveChanged) { helpBoxText = "Changes have been made and will take effect once the Editor exits play mode."; messageType = UnityEditor.MessageType.Warning; } UnityEditor.EditorGUILayout.HelpBox(helpBoxText, messageType); } private static System.Collections.Generic.IEnumerable GetChildren(UnityEditor.SerializedProperty property) { property = property.Copy(); var nextElement = property.Copy(); bool hasNextElement = nextElement.Next(false); if (!hasNextElement) nextElement = null; if (!property.Next(true)) yield break; while (!UnityEditor.SerializedProperty.EqualContents(property, nextElement)) { yield return property.Copy(); if (!property.Next(false)) break; } } private static bool DrawFoldout(UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, UnityEngine.FontStyle fontStyle) { var settingsFoldoutStyle = new UnityEngine.GUIStyle(UnityEditor.EditorStyles.foldout) { fontStyle = fontStyle }; var value = UnityEditor.EditorGUILayout.Foldout(property.isExpanded, label, true, settingsFoldoutStyle); property.isExpanded = value; return value; } private class PlatformData { public PlatformSettings Settings; public UnityEditor.SerializedObject SerializedObject; public string Name; } private class GlobalSettingsGroupData { string ToolTip; string DisplayName; UnityEditor.SerializedProperty Property; System.Collections.Generic.List SettingsList; public GlobalSettingsGroupData(string settingsGroup, UnityEditor.SerializedObject serializedObject, string displayName, System.Collections.Generic.HashSet propertyHashSet) { Property = serializedObject.FindProperty(settingsGroup); if (Property == null) return; ToolTip = AkUtilities.GetTooltip(Property); DisplayName = string.IsNullOrEmpty(displayName) ? ("Common " + Property.displayName) : displayName; SettingsList = new System.Collections.Generic.List(); foreach (var childPropertyPath in propertyHashSet) { var childProperty = serializedObject.FindProperty(childPropertyPath); if (childProperty == null) continue; SettingsList.Add(new GlobalSettingData(childProperty, childPropertyPath)); } } public void SetupPlatform(PlatformData platform) { if (SettingsList == null || SettingsList.Count == 0) return; foreach (var settings in SettingsList) settings.SetupPlatform(platform); } public void ClearPlatformData() { if (SettingsList != null) foreach (var setting in SettingsList) setting.ClearPlatformData(); } public void Draw() { if (SettingsList == null || SettingsList.Count == 0) return; using (var verticalScope = new UnityEditor.EditorGUILayout.VerticalScope("box")) { #if !UNITY_2018_3_OR_NEWER ++UnityEditor.EditorGUI.indentLevel; #endif var label = new UnityEngine.GUIContent { text = DisplayName, tooltip = ToolTip }; if (DrawFoldout(Property, label, UnityEngine.FontStyle.Bold)) foreach (var settings in SettingsList) settings.Draw(); #if !UNITY_2018_3_OR_NEWER --UnityEditor.EditorGUI.indentLevel; #endif } } public class GlobalSettingData { string ToolTip; readonly string DisplayName; readonly string PropertyPath; readonly bool IsStringValue; UnityEditor.SerializedProperty Property; readonly UnityEditor.SerializedPropertyType PropertyType; System.Collections.Generic.List SettingsList; System.Collections.Generic.List Children; bool HasChildren { get { return Children != null && Children.Count > 0; } } public GlobalSettingData(UnityEditor.SerializedProperty property, string propertyPath) { Property = property; PropertyType = property.propertyType; PropertyPath = propertyPath; DisplayName = property.displayName; ToolTip = AkUtilities.GetTooltip(property); if (property.type == "string") { IsStringValue = true; } else if (property.hasChildren) { Children = new System.Collections.Generic.List(); foreach (var child in GetChildren(property)) Children.Add(new GlobalSettingData(child, child.propertyPath)); } } public void SetupPlatform(PlatformData platform) { if (HasChildren) { foreach (var child in Children) child.SetupPlatform(platform); } else { if (platform.Settings.IsPropertyIgnored(PropertyPath)) return; if (SettingsList == null) SettingsList = new System.Collections.Generic.List(); SettingsList.Add(new PlatformSettingData(platform, PropertyPath, ToolTip)); } } public void ClearPlatformData() { SettingsList = null; if (HasChildren) foreach (var child in Children) child.ClearPlatformData(); } private bool AnyChildUsesGlobalValue { get { if (Children != null && Children.Count > 0) { foreach (var child in Children) if (child.AnyChildUsesGlobalValue) return true; return false; } if (SettingsList == null || SettingsList.Count == 0) return true; foreach (var settings in SettingsList) if (settings.Platform.Settings.GlobalPropertyHashSet.Contains(PropertyPath)) return true; return false; } } private bool AllChildrenAreEqual { get { if (Children != null && Children.Count > 0) { foreach (var child in Children) if (!child.AllChildrenAreEqual) return false; return true; } if (SettingsList == null) return true; switch (PropertyType) { case UnityEditor.SerializedPropertyType.Boolean: var boolValue = Property.boolValue; foreach (var settings in SettingsList) if (boolValue != settings.Property.boolValue) return false; return true; case UnityEditor.SerializedPropertyType.Enum: var enumValueIndex = Property.enumValueIndex; foreach (var settings in SettingsList) if (enumValueIndex != settings.Property.enumValueIndex) return false; return true; case UnityEditor.SerializedPropertyType.Float: var floatValue = Property.floatValue; foreach (var settings in SettingsList) if (floatValue != settings.Property.floatValue) return false; return true; case UnityEditor.SerializedPropertyType.Integer: var longValue = Property.longValue; foreach (var settings in SettingsList) if (longValue != settings.Property.longValue) return false; return true; case UnityEditor.SerializedPropertyType.String: var stringValue = Property.stringValue; foreach (var settings in SettingsList) if (stringValue != settings.Property.stringValue) return false; return true; } return true; } } public void Draw() { var hasChanged = false; var isString = IsStringValue; var hasChildren = HasChildren; if (!hasChildren && (SettingsList == null || SettingsList.Count == 0)) return; using (var verticalScope = new UnityEditor.EditorGUILayout.VerticalScope()) { var indentLevel = UnityEditor.EditorGUI.indentLevel++; var forceExpand = !AnyChildUsesGlobalValue; var label = new UnityEngine.GUIContent(DisplayName, ToolTip); if (hasChildren) { DrawFoldout(Property, label, AllChildrenAreEqual ? UnityEngine.FontStyle.Normal : UnityEngine.FontStyle.Italic); } else if (!forceExpand) { DrawFoldout(Property, label, AllChildrenAreEqual ? UnityEngine.FontStyle.Normal : UnityEngine.FontStyle.Italic); UnityEditor.EditorGUI.BeginChangeCheck(); var labelWithTooltipOnly = new UnityEngine.GUIContent { tooltip = ToolTip }; if (isString) UnityEditor.EditorGUILayout.DelayedTextField(Property, labelWithTooltipOnly); else UnityEditor.EditorGUILayout.PropertyField(Property, labelWithTooltipOnly, false); hasChanged = UnityEditor.EditorGUI.EndChangeCheck(); } else { UnityEditor.EditorGUILayout.LabelField(label); } if (hasChildren) { if (Property.isExpanded) foreach (var child in Children) child.Draw(); } else if (forceExpand || Property.isExpanded) { foreach (var settings in SettingsList) settings.Draw(Property, PropertyPath, forceExpand, ToolTip); } else if (hasChanged) { foreach (var settings in SettingsList) if (settings.Platform.Settings.GlobalPropertyHashSet.Contains(PropertyPath)) settings.UpdateValue(Property); } UnityEditor.EditorGUI.indentLevel = indentLevel; } } public class PlatformSettingData { public UnityEditor.SerializedProperty Property; public PlatformData Platform; public PlatformSettingData(PlatformData platform, string propertyPath, string tooltip) { Platform = platform; Property = Platform.SerializedObject.FindProperty(propertyPath); } public void UpdateValue(UnityEditor.SerializedProperty globalProperty) { if (Property == null) return; Platform.SerializedObject.Update(); PropagateValue(Property, globalProperty); Platform.SerializedObject.ApplyModifiedProperties(); } public void Draw(UnityEditor.SerializedProperty globalProperty, string propertyPath, bool forceExpand, string tooltip) { if (Property == null) return; var indentLevel = UnityEditor.EditorGUI.indentLevel++; var position = UnityEngine.GUILayoutUtility.GetRect(UnityEngine.GUIContent.none, UnityEngine.GUIStyle.none, UnityEngine.GUILayout.Height(UnityEditor.EditorGUIUtility.singleLineHeight)); var wasUsingGlobalValue = Platform.Settings.GlobalPropertyHashSet.Contains(propertyPath); var width = position.width; if (!wasUsingGlobalValue) position.width = UnityEditor.EditorGUIUtility.labelWidth; var isUsingGlobalValue = UnityEditor.EditorGUI.ToggleLeft(position, new UnityEngine.GUIContent(Platform.Name, tooltip), wasUsingGlobalValue); position.width = width; if (wasUsingGlobalValue != isUsingGlobalValue) Platform.Settings.SetUseGlobalPropertyValue(propertyPath, isUsingGlobalValue); if (!isUsingGlobalValue) { position.x += UnityEditor.EditorGUIUtility.labelWidth; position.width -= UnityEditor.EditorGUIUtility.labelWidth; UnityEditor.EditorGUI.indentLevel = 1; // Not zero, so that a control handle is available Platform.SerializedObject.Update(); UnityEditor.EditorGUI.PropertyField(position, Property, new UnityEngine.GUIContent { tooltip = tooltip }); Platform.SerializedObject.ApplyModifiedProperties(); } else if (forceExpand) PropagateValue(globalProperty, Property); else UpdateValue(globalProperty); UnityEditor.EditorGUI.indentLevel = indentLevel; } private static void PropagateValue(UnityEditor.SerializedProperty x, UnityEditor.SerializedProperty y) { //if (x.propertyType != y.propertyType) // return; switch (x.propertyType) { case UnityEditor.SerializedPropertyType.Boolean: x.boolValue = y.boolValue; break; case UnityEditor.SerializedPropertyType.Enum: x.longValue = y.longValue; break; case UnityEditor.SerializedPropertyType.Float: x.floatValue = y.floatValue; break; case UnityEditor.SerializedPropertyType.Integer: x.longValue = y.longValue; break; case UnityEditor.SerializedPropertyType.String: x.stringValue = y.stringValue; break; case UnityEditor.SerializedPropertyType.Generic: if (x.type == y.type) { var XProperty = x.Copy(); var YProperty = y.Copy(); var XEndProperty = x.Copy(); var YEndProperty = y.Copy(); XEndProperty = XEndProperty.Next(false) ? XEndProperty : null; YEndProperty = YEndProperty.Next(false) ? YEndProperty : null; while (XProperty.Next(true) && YProperty.Next(true) && !UnityEditor.SerializedProperty.EqualContents(XProperty, XEndProperty) && !UnityEditor.SerializedProperty.EqualContents(YProperty, YEndProperty)) PropagateValue(XProperty, YProperty); } break; } } } } } private class PlatformSpecificSettingsData { public string ToolTip; public UnityEditor.SerializedProperty Property; public System.Collections.Generic.List SettingsList; public PlatformData Platform; public PlatformSpecificSettingsData(PlatformData platform, string propertyPath, System.Collections.Generic.HashSet globalPropertyHashSet) { Platform = platform; Property = Platform.SerializedObject.FindProperty(propertyPath); if (Property == null) return; ToolTip = AkUtilities.GetTooltip(Property); System.Collections.Generic.HashSet hashSet = new System.Collections.Generic.HashSet(); foreach (var childProperty in GetChildren(Property)) hashSet.Add(childProperty.propertyPath); var remainder = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Except(hashSet, globalPropertyHashSet)); if (remainder.Length > 0) SettingsList = new System.Collections.Generic.List(); foreach (var childPropertyPath in remainder) { var childProperty = Platform.SerializedObject.FindProperty(childPropertyPath); if (childProperty != null) SettingsList.Add(childProperty); } } public void Draw() { if (SettingsList == null || SettingsList.Count == 0) return; Platform.SerializedObject.Update(); using (new UnityEditor.EditorGUILayout.VerticalScope("box")) { #if !UNITY_2018_3_OR_NEWER ++UnityEditor.EditorGUI.indentLevel; #endif var label = new UnityEngine.GUIContent(Platform.Name + " Specific " + Property.displayName, ToolTip); if (DrawFoldout(Property, label, UnityEngine.FontStyle.Bold)) { ++UnityEditor.EditorGUI.indentLevel; foreach (var child in SettingsList) UnityEditor.EditorGUILayout.PropertyField(child, true); --UnityEditor.EditorGUI.indentLevel; } #if !UNITY_2018_3_OR_NEWER --UnityEditor.EditorGUI.indentLevel; #endif } Platform.SerializedObject.ApplyModifiedProperties(); } } } #endregion #endif }