Search Unity

Showcase Sound CSS (Harmony patch)

Discussion in 'UI Toolkit' started by Guedez, Jun 17, 2021.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    So I've made a CSS to play sounds, it automatically hooks the
    CustomStyleResolvedEvent
    callback to all
    VisualElements
    on their constructor, I've added an IF to ensure it only adds to buttons, but you can remove it.

    Code (CSharp):
    1.  
    2. using HarmonyLib;
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.Reflection;
    7. using UnityEditor;
    8. using UnityEngine;
    9. using UnityEngine.UIElements;
    10.  
    11. public class VisualElementHook {
    12.     public class OutAudio {
    13.         public string value;
    14.     }
    15.     public class InAudio {
    16.         public string value;
    17.     }
    18.     public class ClickAudio {
    19.         public string value;
    20.     }
    21.  
    22.     [HarmonyPatch(typeof(VisualElement), MethodType.Constructor, new Type[] { })]
    23.     class Patch {
    24.         static void Postfix(VisualElement __instance) {
    25.             if (typeof(Button).IsAssignableFrom(__instance.GetType())) {
    26.                 __instance.RegisterCallback<CustomStyleResolvedEvent>((CustomStyleResolvedEvent) => {
    27.                     CustomStyleProperty<string> property = new CustomStyleProperty<string>("--OutAudio");
    28.                     OutAudio OutAudio = Utils.UIElementUserData<OutAudio>(__instance);
    29.                     if (CustomStyleResolvedEvent.customStyle.TryGetValue(property, out string value)) {
    30.                         if (OutAudio != null && OutAudio.value != value) {
    31.                             SoundProcessor.PlaySound(OutAudio.value);
    32.                         }
    33.                         Utils.UIElementUserData(__instance, new OutAudio() { value = value });
    34.                     } else {
    35.                         if (OutAudio != null) {
    36.                             SoundProcessor.PlaySound(OutAudio.value);
    37.                         }
    38.                         Utils.UIElementUserData<OutAudio>(__instance, null);
    39.                     }
    40.                     InAudio InAudio = Utils.UIElementUserData<InAudio>(__instance);
    41.                     property = new CustomStyleProperty<string>("--InAudio");
    42.                     if (CustomStyleResolvedEvent.customStyle.TryGetValue(property, out value)) {
    43.                         if (InAudio == null || InAudio.value != value) {
    44.                             SoundProcessor.PlaySound(value);
    45.                         }
    46.                         Utils.UIElementUserData(__instance, new InAudio() { value = value });
    47.                     } else {
    48.                         Utils.UIElementUserData<InAudio>(__instance, null);
    49.                     }
    50.                     property = new CustomStyleProperty<string>("--ClickAudio");
    51.                     if (CustomStyleResolvedEvent.customStyle.TryGetValue(property, out value)) {
    52.                         Utils.UIElementUserData(__instance, new ClickAudio() { value = value });
    53.                     } else {
    54.                         Utils.UIElementUserData<ClickAudio>(__instance, null);
    55.                     }
    56.                 });
    57.                 __instance.RegisterCallback<ClickEvent>((CustomStyleResolvedEvent) => {
    58.                     ClickAudio ClickAudio = Utils.UIElementUserData<ClickAudio>(__instance);
    59.                     if (ClickAudio != null) {
    60.                         SoundProcessor.PlaySound(ClickAudio.value);
    61.                     }
    62.                 });
    63.             }
    64.         }
    65.     }
    66.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    67.     static void OnBeforeSceneLoadRuntimeMethod() {
    68.         var harmony = new Harmony("UnityEngine.UIElements.Extensions");
    69.         harmony.PatchAll(Assembly.GetExecutingAssembly());
    70.     }
    71. }
    72.  
    Code (CSharp):
    1. public static T UIElementUserData<T>(VisualElement target) where T : class {
    2.         Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
    3.         if (data == null) {
    4.             return (T)null;
    5.         }
    6.         if (data.TryGetValue(typeof(T), out object val)) {
    7.             return (T)val;
    8.         }
    9.         return (T)null;
    10.     }
    11.     public static void UIElementUserData<T>(VisualElement target, T val) where T : class {
    12.         Dictionary<Type, object> data = target.userData as Dictionary<Type, object>;
    13.         if (data == null) {
    14.             target.userData = data = new Dictionary<Type, object>();
    15.         }
    16.         data[typeof(T)] = val;
    17.         target.SendEvent(new ChangeDataEvent<T>() { data = val });
    18.     }
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEngine;
    5. using UnityEngine.Audio;
    6.  
    7. [ProcessorFor("SoundPlay")]
    8. public class SoundProcessor : AutoProcessorNonNullable<SoundProcessor.SoundEffect> {
    9.     public SoundProcessor(bool isContainer, bool isPartial) : base(isContainer, isPartial) {
    10.     }
    11.     public struct SoundEffect {
    12.         public float Volume;
    13.         public string[] PlayOn;
    14.         public string[] Sound;
    15.     }
    16.  
    17.     private static Dictionary<string, AudioClip[]> loadedSounds = new Dictionary<string, AudioClip[]>();
    18.     private static Dictionary<string, AudioSource> singleSound = new Dictionary<string, AudioSource>();
    19.     private static Dictionary<string, SoundEffect> unloadedSounds = new Dictionary<string, SoundEffect>();
    20.     private static Dictionary<string, int> LastPlayed = new Dictionary<string, int>();
    21.  
    22.     public static string RegisterSound(AudioClip audioClip, float volume = 1) {
    23.         if (audioClip == null) {
    24.             return null;
    25.         }
    26.         string ID = audioClip.GetInstanceID().ToString();
    27.         unloadedSounds[ID] = new SoundEffect() { Volume = volume };
    28.         loadedSounds[ID] = new AudioClip[1] { audioClip };
    29.         return ID;
    30.     }
    31.  
    32.     private static List<AudioSource> SoundPlayers = new List<AudioSource>();
    33.     private static AudioMixerGroup SoundsGroup;
    34.  
    35.     public override void AfterProcess(ref SoundEffect nuts) {
    36.         nuts.Volume = nuts.Volume == 0 ? 1 : nuts.Volume;
    37.     }
    38.  
    39.     protected override bool entityless() {
    40.         return true;
    41.     }
    42.  
    43.     public override void OnEndDatabaseLoadAfterWorldInitialization() {
    44.         foreach (SoundEffect effect in dictionary.Values) {
    45.             foreach (string s in effect.PlayOn) {
    46.                 unloadedSounds[s.ToLower()] = effect;
    47.             }
    48.         }
    49.         AudioMixer AM = Resources.Load<AudioMixer>("Audio/Master");
    50.         SoundsGroup = AM.FindMatchingGroups("Master/Sounds")[0];
    51.     }
    52.  
    53.     public static bool PlaySound(string sound, bool protect = false, float volume = 1, float pitch = 1) {
    54.         sound = sound.ToLower();
    55.         if (LastPlayed.GetOrDefault(sound, -1) == Time.renderedFrameCount && protect) {
    56.             return false;
    57.         }
    58.         if (!unloadedSounds.ContainsKey(sound)) {
    59.             Debug.LogWarning("There is no definition for: " + sound);
    60.             return false;
    61.         }
    62.         SoundEffect soundEffect = unloadedSounds[sound];
    63.         if (loadedSounds.TryGetValue(sound, out AudioClip[] clip)) {
    64.             AudioSource sp = null;
    65.             foreach (AudioSource source in SoundPlayers) {
    66.                 if (!source.isPlaying) {
    67.                     sp = source;
    68.                     break;
    69.                 }
    70.             }
    71.             sp = EnsureSoundPlayer(sp);
    72.             sp.outputAudioMixerGroup = SoundsGroup;
    73.             sp.pitch = pitch;
    74.             sp.volume = soundEffect.Volume * volume;
    75.             AudioClip clip1 = clip[UnityEngine.Random.Range(0, clip.Length)];
    76.             sp.PlayOneShot(clip1);
    77.             LastPlayed[sound] = Time.renderedFrameCount;
    78.             return true;
    79.         } else {
    80.             AudioClip[] foundClip = soundEffect.Sound.Cast(T => Resources.Load(T) as AudioClip).ToArray();
    81.             if (foundClip.All(T => T != null)) {
    82.                 loadedSounds[sound] = foundClip;
    83.                 PlaySound(sound);
    84.                 return true;
    85.             } else {
    86.                 Debug.LogError("One or more AudioClips could not be found in paths: " + string.Join(", ", soundEffect.Sound));
    87.             }
    88.         }
    89.         return false;
    90.     }
    91.  
    92.     private static AudioSource EnsureSoundPlayer(AudioSource sp) {
    93.         if (sp == null) {
    94.             GameObject gameObject = new GameObject();
    95.             gameObject.hideFlags = HideFlags.HideAndDontSave;
    96.             sp = gameObject.AddComponent<AudioSource>();
    97.             SoundPlayers.Add(sp);
    98.         }
    99.  
    100.         return sp;
    101.     }
    102.  
    103.     public static void PlaySoundContinuous(string sound, float volume) {
    104.         if (!unloadedSounds.ContainsKey(sound)) {
    105.             Debug.LogError("There is no definition for: " + sound);
    106.             return;
    107.         }
    108.         SoundEffect soundEffect = unloadedSounds[sound];
    109.         if (!loadedSounds.TryGetValue(sound, out AudioClip[] clip)) {
    110.             clip = soundEffect.Sound.Cast(T => Resources.Load(T) as AudioClip).ToArray();
    111.             if (clip != null) {
    112.                 loadedSounds[sound] = clip;
    113.             } else {
    114.                 Debug.LogError("Could not find AudioClip in path: " + sound);
    115.                 return;
    116.             }
    117.         }
    118.         if (!singleSound.TryGetValue(sound, out AudioSource source)) {
    119.             GameObject gameObject = new GameObject();
    120.             gameObject.hideFlags = HideFlags.HideAndDontSave;
    121.             singleSound[sound] = source = gameObject.AddComponent<AudioSource>();
    122.             source.clip = clip[UnityEngine.Random.Range(0, clip.Length)];
    123.             source.loop = true;
    124.             source.Play();
    125.         }
    126.         if (volume == 0) {
    127.             source.Pause();
    128.         } else {
    129.             source.volume = volume * soundEffect.Volume;
    130.             if (!source.isPlaying) {
    131.                 source.UnPause();
    132.             }
    133.         }
    134.     }
    135.  
    136.     public static AudioSource RequestAudioSource() {
    137.         AudioSource sp = null;
    138.         foreach (AudioSource source in SoundPlayers) {
    139.             if (!source.isPlaying) {
    140.                 sp = source;
    141.                 break;
    142.             }
    143.         }
    144.         sp = EnsureSoundPlayer(sp);
    145.         return sp;
    146.     }
    147. }
    148.  
    Code (CSS):
    1. .LargeButton{
    2. }
    3. .LargeButton:hover{
    4.     --InAudio:closeWindow;
    5.     --OutAudio:closeWindow;
    6.     --ClickAudio:closeWindow;
    7. }
     
    Last edited: Jun 18, 2021