Système audio spatial dynamique pour une expérience immersive en réalité virtuelle
Contexte et problématique
Dans le cadre d’un projet de recherche immersif en réalité virtuelle, j’ai développé une architecture audio temps-réel visant à reproduire des ambiances naturelles complexes, interactives, tout en maintenant une gestion optimale des ressources. L’intégration conjointe de Unity et de FMOD permet un contrôle fin des paramètres sonores en fonction de paramètres externes, indispensable à l’adaptation contextuelle des signaux audio dans une simulation.
Approche et méthodologie
L’implémentation repose sur un ensemble modulaire d’une vingtaine de scripts C#, répartis en sous-systèmes (ambiances environnementales, occlusion dynamique, interactions utilisateurs et routage audio multi-périphériques). Chaque module manipule en temps réel les instances FMOD et leurs paramètres associés (snapshots, occlusion, depth-based attenuation, randomisation), permettant la variation contextuelle automatique des évènements sonores et la cohérence spatiale lors des déplacements de l’utilisateur en VR.
Résultats et perspectives
Les expérimentations démontrent un rendu sonore cohérent et réaliste, éliminant les discontinuités et assurant une immersion tout au long de l’expérience de jeu. La conception modulaire facilite l’intégration future de paramètres externes (par exemple les données météorologiques en temps réel ou d’autres acteurs), l’extension du système d’occlusion, ainsi que l’exploration de routages audio multi-sorties distincts (PC vs casque VR) pour une expérience multi-utilisateur avancée.
Introduction
Ce travail s’inscrit dans le cadre d’un projet de restitution immersive de la vie préhistorique autour de l’Homme de Tautavel. L’objectif est de proposer une expérience pédagogique et scientifique en réalité virtuelle, permettant aux utilisateurs – étudiants, chercheurs ou grand public de s’immerger dans un environnement fidèle à la période paléolithique. La reconstitution sonore n’est pas un simple habillage : elle participe à la compréhension du contexte écologique, des interactions avec l’environnement et des comportements humains du Paléolithique. La dimension audio devient ainsi un vecteur de recherche (test de scénarios sonores plausibles) et un levier pédagogique (renforcement de la présence et de la mémoire spatiale).
Dans ce projet, nous présentons une architecture audio temps-réel pensée pour un environnement VR représentant les paysages, les climats et les ambiances de la préhistoire. Notre système se compose d’une vingtaine de scripts C# regroupés en quatre grands ensembles :
I. Ambiances environnementales (sons atmosphériques, gestion des zones naturelles, animaux, effets d’eau)
II. Interactions joueur / environnement (footsteps, splashes, transitions sous-marines, adaptation aux paramètres contextuels)
III. Occlusion et propagation (raycasting dynamique et filtrage adaptatif)
IV. Routage et multi-périphériques (tentative de séparation PC/VR pour une spatialisation distincte)
Cette modularité permet d’orchestrer l’intégralité de la scène sonore avec une granularité élevée : chaque script pilote directement des paramètres FMOD, synchronise les événements, et ajuste les instances audios en fonction des positions, des collisions, et des conditions contextuelles. L’approche est pensée pour minimiser les ruptures perceptibles (crossfade entre zones, limitation du nombre d’instances, randomisation spatiale) et pour ouvrir la voie à des contrôles automatisés (ex. adaptation aux données météo en temps réel).
En articulant recherche scientifique, enregistrements de terrain, restitution immersive et pédagogie, cette approche audio contribue à renforcer la valeur scientifique et éducative de l’expérience VR sur l’Homme de Tautavel.
II – Interactions joueur / environnement
Le système d’interaction sonore repose sur plusieurs modules indépendants mais interconnectés, chacun chargé de traduire une classe d’événements physiques ou comportementaux en signaux audio. Parmi ceux-ci, trois scripts se distinguent : ImpactManagerSound.cs, SplashSound.cs et FoliageSound.cs, qui assurent respectivement la gestion des impacts, des interactions avec l’eau et des déplacements dans la végétation.
Ces scripts exploitent les capacités événementielles de FMOD et les fonctionnalités physiques de Unity (collisions, triggers, raycasts) pour générer des sons en fonction de la position, de la vitesse et du type d’objet en interaction. Cette approche modulaire et temps réel garantit une adaptation continue du paysage sonore à l’action du joueur.
ImpactManagerSound
Ce script constitue le noyau du système de gestion des impacts physiques dans la scène. Son rôle est de détecter, classer et déclencher les événements sonores associés à toute collision entre objets dynamiques.
Le script s’appuie sur un système d’événements centralisé : lorsqu’une collision est détectée par le composant CollisionEvent.cs, celui-ci envoie une requête à l’instance unique d’ImpactManagerSound. Ce dernier :
– Analyse les propriétés physiques de la collision (matériaux, vitesse d’impact, intensité).
– Sélectionne le type d’événement FMOD approprié.
– Calcule les paramètres audio dynamiques, notamment l’intensité ou la réverbération en fonction de la force d’impact.
– Déclenche le son à la position exacte de la collision, garantissant une spatialisation correcte dans l’environnement 3D.
Un singleton ImpactManagerSound.Instance permet une gestion optimisée, évitant la duplication d’instances FMOD et assurant la cohérence sonore sur l’ensemble du projet.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
using UnityEngine; using FMODUnity; using FMOD.Studio; using static SoundEvents; public class ImpactManagerSound : MonoBehaviour { public Transform playerTransform; public static ImpactManagerSound Instance; private Terrain terrain; private Texture2D sandSplat, stoneSplat; private Rigidbody rb; private EventInstance instImpact; private void Awake() { if (Instance == null) Instance = this; else Destroy(gameObject); rb = GetComponent<Rigidbody>(); } private void Update() { if (instImpact.isValid()) { instImpact.set3DAttributes(RuntimeUtils.To3DAttributes(playerTransform.position)); } } void Start() { terrain = Terrain.activeTerrain; instImpact = RuntimeManager.CreateInstance("event:/TautavelMan/TautavelMan_Impact/EV_TautavelMan_Impact"); sandSplat = (Texture2D)terrain.materialTemplate.GetTexture("_Texture_Splat_1"); stoneSplat = (Texture2D)terrain.materialTemplate.GetTexture("_Texture_Splat_2"); } public void HandleImpact(Collision collision) { Vector3 previousVelocity = collision.relativeVelocity; float fallSpeed = previousVelocity.magnitude; fallSpeed = Mathf.Clamp(fallSpeed, 0f, 10f); if (fallSpeed > 1f) { Vector3 mapPosition = ConvertPosition(collision.contacts[0].point); float sandValue = GetSandValue(mapPosition); float stoneValue = GetStoneValue(mapPosition); float grassValue = 1 - (sandValue + stoneValue); instImpact.set3DAttributes(RuntimeUtils.To3DAttributes(playerTransform.position)); instImpact.setParameterByName("Parameter_VelocityFall", fallSpeed); instImpact.setParameterByName("Parameter_FTPS_Sand", sandValue); instImpact.setParameterByName("Parameter_FTPS_Stone", stoneValue); instImpact.setParameterByName("Parameter_FTPS_Grass", grassValue); instImpact.start(); //instImpact.release(); //Debug.Log($"Impact! Speed: {fallSpeed}"); SignalTautavelManEvent(); } } Vector3 ConvertPosition(Vector3 position) { Vector3 terrainPosition = position - terrain.transform.position; return new Vector3(terrainPosition.x / terrain.terrainData.size.x, 0, terrainPosition.z / terrain.terrainData.size.z); } float GetSandValue(Vector3 pos) { if (sandSplat == null) return 0f; return sandSplat.GetPixelBilinear(pos.x, pos.z).r; } float GetStoneValue(Vector3 pos) { if (stoneSplat == null) return 0f; return stoneSplat.GetPixelBilinear(pos.x, pos.z).g; } } |
De plus, l’intensité sonore et la nature du son dépendent directement de la vitesse relative des objets au moment de l’impact, ce qui permet de reproduire aussi bien de légères collisions que des chocs puissants.
Le système communique avec d’autres scripts d’environnement (comme les collisions liées à l’eau dans SplashSound) pour garantir la continuité sonore entre matériaux (pierre, terre, eau, végétation).
WaterSound
Le script WaterSound constitue le cœur du système acoustique lié à l’eau. Il assure à la fois la détection d’immersion du joueur, sa profondeur sous l’eau et le contrôle de plusieurs états acoustiques dans FMOD.
En s’appuyant sur un raycast vertical, il évalue en permanence la distance entre la position du joueur et la surface de l’eau afin de déterminer un paramètre de profondeur (depth). Ce dernier alimente directement le paramètre FMOD « Parameter_Water », contrôlant la présence d’une couche sonore liée à l’eau dans les footsteps et d’autres éléments ambiants.
Lorsque la profondeur dépasse un seuil défini (environ 1.6 mètre, correspondant au passage de la tête sous l’eau), un snapshot FMOD dédié (Snapshot_UnderWater) est activé. Celui-ci modifie globalement la scène sonore en filtrant les hautes fréquences, en augmentant la réverbération interne et en réduisant les sons extérieurs, simulant ainsi la propagation atténuée et amortie du son sous l’eau.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
using UnityEngine; using FMODUnity; using FMOD.Studio; public class WaterSound : MonoBehaviour { public int LayerWater = 4; public float maxDepth = 10f; public float raycastDistance = 10f; private float depth = 0f; private Collider waterCollider; public static bool isInWater = false; public static bool snapshotActive = false; private float parameterWaterEndValue = 0f; public float fadeDuration = 20f; private float fadeTimer = 0f; private bool hasStartedFading = false; private EventInstance snapshotUnderwater; private EventInstance ev_AMB_OutWater; public EventInstance ev_AMB_Underwater; private void Start() { snapshotUnderwater = RuntimeManager.CreateInstance("snapshot:/Snaapshot_UnderWater"); ev_AMB_OutWater = RuntimeManager.CreateInstance("event:/Ambiance/EV_AMB_OutWater"); ev_AMB_Underwater = RuntimeManager.CreateInstance("event:/Ambiance/EV_AMB_Underwater"); RuntimeManager.StudioSystem.setParameterByName("Parameter_WaterEnd", 0f); } private void OnTriggerEnter(Collider other) { if (other.gameObject.layer == LayerWater) { waterCollider = other; isInWater = true; } } public void StopSnapshot() { ev_AMB_Underwater.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); print("forcemode"); } private void FixedUpdate() { if (isInWater && waterCollider != null) { RaycastHit hit; Vector3 raycastStart = transform.position + Vector3.up * raycastDistance; Vector3 raycastDirection = Vector3.down; LayerMask mask = LayerMask.GetMask("Water"); if (Physics.Raycast(raycastStart, raycastDirection, out hit, raycastDistance, mask)) { depth = Mathf.Clamp(raycastDistance - hit.distance, 0, maxDepth); RuntimeManager.StudioSystem.setParameterByName("Parameter_Water", depth); isInWater = true; if (depth >= 1.6 && !snapshotActive) { snapshotUnderwater.start(); ev_AMB_Underwater.start(); snapshotActive = true; } else if (depth < 1.6f && snapshotActive) { snapshotUnderwater.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); ev_AMB_Underwater.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); ev_AMB_OutWater.start(); snapshotActive = false; } if (snapshotActive == false) { ev_AMB_Underwater.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); snapshotUnderwater.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); } parameterWaterEndValue = 0f; fadeTimer = 0f; hasStartedFading = false; RuntimeManager.StudioSystem.setParameterByName("Parameter_WaterEnd", parameterWaterEndValue); //Debug.DrawRay(raycastStart, raycastDirection * raycastDistance, Color.blue, 1f); //print($"Profondeur : {depth}"); } else { //Debug.DrawRay(raycastStart, raycastDirection * raycastDistance, Color.red, 1f); isInWater = false; depth = 0f; } } if (!isInWater) { RuntimeManager.StudioSystem.setParameterByName("Parameter_Water", 0); if (!hasStartedFading) { parameterWaterEndValue = 1f; fadeTimer = 0f; hasStartedFading = true; RuntimeManager.StudioSystem.setParameterByName("Parameter_WaterEnd", parameterWaterEndValue); } if (parameterWaterEndValue > 0f) { fadeTimer += Time.fixedDeltaTime; parameterWaterEndValue = Mathf.Clamp01(1f - (fadeTimer / fadeDuration)); RuntimeManager.StudioSystem.setParameterByName("Parameter_WaterEnd", parameterWaterEndValue); } } } } |
Le script gère également une phase de transition douce : lors de la sortie du joueur de la zone aquatique, un paramètre "Parameter_WaterEnd" est progressivement décrémenté sur une durée paramétrable, permettant un fondu acoustique cohérent entre les deux milieux.
En combinant tout cela, ce système recrée une immersion audio-réaliste et réactive, fidèle aux variations physiques perçues dans un environnement aquatique réel — un point essentiel dans le cadre d’une simulation VR scientifique et pédagogique.
FoliageSound
Le script FoliageSound simule la réaction sonore du feuillage lorsque le joueur entre en contact avec la végétation. Chaque collision ou proximité avec un volume végétal déclenche dynamiquement un événement FMOD, ajusté selon la vitesse du joueur et la densité du feuillage.
Le système repose sur une détection par colliders multiples associés aux zones de végétation. Lorsqu’un contact est détecté, un événement event:/Environment/Foliage est joué à la position correspondante, avec un paramètre d’intensité dérivé de la magnitude de la vélocité.
Cette approche permet une réponse graduée — un léger bruissement lors d’un déplacement lent, un craquement marqué lors d’une traversée rapide.
Pour éviter la redondance sonore, un délai de réactivation empêche les déclenchements successifs sur la même zone.
Le résultat est une ambiance végétale vivante, synchronisée avec le mouvement du joueur, participant activement à la perception de l’espace et de la matière dans l’environnement VR.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
using FMODUnity; using UnityEngine; using FMOD.Studio; using static SoundEvents; public class FoliageSound : MonoBehaviour { public EventInstance ev_TautavelMan_FootstepsFoliage; public static float foliageRadius = 0.55f, minVelocity = 0.005f; private Vector3 lastPosition; private Vector3 velocity; void Start() { ev_TautavelMan_FootstepsFoliage = RuntimeManager.CreateInstance("event:/TautavelMan/TautavelMan_Footsteps/EV_TautavelMan_FootstepsFoliage"); UpdateSoundAttributes(); } void Update() { Terrain terrain = Terrain.activeTerrain; ev_TautavelMan_FootstepsFoliage.getPlaybackState(out var state); if (state == PLAYBACK_STATE.PLAYING) { UpdateSoundAttributes(); return; } velocity = (transform.position - lastPosition)/Time.deltaTime; lastPosition = transform.position; if (velocity.sqrMagnitude < minVelocity * minVelocity) { return; } var data = terrain.terrainData; var patchSize = data.detailPatchCount; var patch = (transform.position - terrain.GetPosition()) / patchSize; for (int i = 0; i < data.detailPrototypes.Length; i++) { var tfs = data.ComputeDetailInstanceTransforms((int) patch.x, (int) patch.z, i, terrain.detailObjectDensity, out var _); foreach(var t in tfs) { var pos = new Vector3(t.posX, t.posY, t.posZ) + terrain.GetPosition(); //Debug.DrawLine(pos, pos + Vector3.one * 0.01f, Color.red); var len2 = (pos - transform.position).sqrMagnitude; if (len2 < foliageRadius*foliageRadius) { UpdateSoundAttributes(); ev_TautavelMan_FootstepsFoliage.start(); SignalTautavelManEvent(); break; } } } } private void UpdateSoundAttributes() { ev_TautavelMan_FootstepsFoliage.set3DAttributes(transform.To3DAttributes()); ev_TautavelMan_FootstepsFoliage.setParameterByName("parameter:/Parameter_Velocity", velocity.magnitude); } } |
III – Occlusion et propagation
La simulation de l’occlusion acoustique constitue un élément essentiel dans toute expérience immersive, notamment en réalité virtuelle. Dans un environnement 3D complexe, la perception sonore dépend non seulement de la distance à la source, mais également des obstacles intermédiaires (reliefs, murs, végétation, etc.) qui altèrent la propagation du signal.
Afin de renforcer le réalisme perceptif et la cohérence spatiale, un système d’occlusion dynamique a été mis en place. Celui-ci repose sur des calculs en temps réel effectués par raycasts, permettant d’ajuster la réponse fréquentielle et l’atténuation du son selon la géométrie rencontrée entre la source et le joueur.
Ce dispositif garantit que le son ne se comporte pas uniquement selon des lois géométriques simples, mais qu’il réagit physiquement à l’environnement — une condition indispensable pour la crédibilité de la scène sonore dans un contexte immersif comme celui ci.
SoundOcclusionDynamic
Ce script implémente une méthode d’occlusion sonore adaptative à partir de raycasts projetés entre la source sonore et la position du joueur.
Chaque trame, le script effectue une série de tests de collision qui permettent de déterminer si un obstacle bloque partiellement ou totalement la ligne directe de propagation.
Le résultat de cette détection (binaire ou pondéré selon le matériau traversé) influence directement un paramètre FMOD dédié, généralement nommé "Parameter_Occlusion". Ce paramètre agit sur un filtre passe-bas et un volume global dans FMOD, simulant ainsi la perte de clarté sonore et la réduction d’intensité dues à l’obstruction.
La conception modulaire du script permet de l’attacher à n’importe quel objet sonore : il peut ainsi être appliqué à des sources environnementales fixes (comme une cascade ou un feu de camp) ou à des entités mobiles. De plus, les calculs ont été optimisés afin de limiter la charge CPU, en ajustant la fréquence d’échantillonnage du raycast et en restreignant la portée de détection.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
using FMOD.Studio; using FMODUnity; using UnityEngine; using System.Collections.Generic; using FMOD; public class SoundOcclusionDynamic : MonoBehaviour { [Header("Detection Settings")] [SerializeField] private float maxDistanceOcclusion = 60f; [SerializeField] private LayerMask layerMaskObstacle; [SerializeField] private string tagSoundSource = "SoundSource"; [Header("FMOD Parameters")] [SerializeField] private string occlusionParameter = "Parameter_Occlusion"; private List<Transform> soundSources = new List<Transform>(); private void Start() { GameObject[] sources = GameObject.FindGameObjectsWithTag(tagSoundSource); foreach (GameObject source in sources) { soundSources.Add(source.transform); } } private void Update() { foreach (Transform source in soundSources) { DetectObstruction(source); } } private void DetectObstruction(Transform source) { Vector3 directionToSource = source.position - transform.position; float distanceToSource = directionToSource.magnitude; if (distanceToSource <= maxDistanceOcclusion) { RaycastHit hit; bool isObstructed = Physics.Raycast(transform.position, directionToSource, out hit, maxDistanceOcclusion, layerMaskObstacle); if (source.TryGetComponent<StudioEventEmitter>(out StudioEventEmitter emitter)) { float occlusionValue = 0f; if (isObstructed) { occlusionValue = 1f; } emitter.EventInstance.setParameterByName(occlusionParameter, occlusionValue); } } } private void OnDrawGizmos() { Gizmos.color = Color.red; foreach (Transform source in soundSources) { if (source != null) { Gizmos.DrawLine(transform.position, source.position); } } } } |
IV – Routage et multi-périphériques
Dans le cadre d’expériences collaboratives en réalité virtuelle, la gestion multi-utilisateur et multi-périphérique représente un enjeu majeur pour l’immersion. En particulier, certaines configurations expérimentales impliquent la présence d’un joueur en casque VR et d’un second utilisateur sur écran, chacun nécessitant une spatialisation audio adaptée à sa propre perspective.
La mise en place d’un routage sonore différencié vise donc à assigner des sorties audio distinctes à chaque dispositif, permettant d’optimiser la perception auditive selon le contexte (proximité, orientation, champ visuel). Cette approche ouvre la voie à des scénarios collaboratifs ou pédagogiques où plusieurs auditeurs perçoivent simultanément des paysages sonores cohérents, mais personnalisés.
DriversOutputs
Le script DriversOutputs constitue une tentative de routage audio multi-périphérique exploitant les capacités internes de FMOD CoreSystem. L’objectif initial était de séparer le flux audio vers deux périphériques distincts :
le casque VR, associé à un StudioListener spécifique,
l’ordinateur principal, gérant les sons perçus par un second auditeur ou un observateur extérieur.
Le script procède à une initialisation parallèle de deux systèmes FMOD (CoreSystem et CoreSystem2) et tente d’assigner à chacun un driver audio différent via setDriver(). Chaque système est ensuite lié à son propre bus logique dans FMOD (BUS_Computer et BUS_VR), permettant d’acheminer des événements sonores indépendants.
Bien que le fonctionnement complet n’ait pas été atteint (limitations du moteur Unity et de l’API FMOD Unity), cette approche illustre une piste de recherche prometteuse pour la diffusion spatiale multi-canal et multi-dispositif.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using FMODUnity; using System; public class DriversOutput : MonoBehaviour { public StudioListener listenerComputer; public StudioListener listenerVR; public GameObject Computer; public GameObject VR; public GameObject Target; private GameObject Targetbotcontroller; private GameObject TargetListenerVR; FMOD.ChannelGroup channelGroupSys1; FMOD.ChannelGroup channelGroupSys2; FMOD.Channel channelSys1; FMOD.Channel channelSys2; FMOD.RESULT playResult1; FMOD.RESULT playResult2; //Busses public FMOD.Studio.Bus BusComputer; public FMOD.Studio.Bus BusVR; //Events public FMOD.Studio.EventInstance EventTestComputer; public FMOD.Studio.EventInstance EventTestVR; void Start() { BusComputer = RuntimeManager.GetBus("bus:/BUS_Computer"); BusVR = RuntimeManager.GetBus("bus:/BUS_VR"); Targetbotcontroller = GameObject.Find("mixamorig:Neck"); TargetListenerVR = GameObject.Find("ListenerVR"); var resGetDrivers_Computer = RuntimeManager.CoreSystem.getNumDrivers(out int TotalDrivers); Debug.Log($"Total Computer Drivers {TotalDrivers}"); var resGetDrivers_VR = RuntimeManager.CoreSystem2.getNumDrivers(out int TotalDrivers2); Debug.Log($"Total Computer Drivers {TotalDrivers2}"); print("Drivers :"); for(int i = 0; i<TotalDrivers; i++) { RuntimeManager.CoreSystem.getDriverInfo(i, out var name, 100000, out var g, out var r, out var s, out var m); print(name); } Check(RuntimeManager.StudioSystem2.initialize(1, FMOD.Studio.INITFLAGS.NORMAL, FMOD.INITFLAGS.NORMAL, IntPtr.Zero)); Check(RuntimeManager.CoreSystem2.init(2, FMOD.INITFLAGS.NORMAL, (System.IntPtr)0)); Check(RuntimeManager.CoreSystem.setDriver(0)); Check(RuntimeManager.CoreSystem2.setDriver(2)); //EVENT TEST BUS COMPUTER { Check(RuntimeManager.StudioSystem.getEvent("event:/EV_Test_ComputerBus", out var ev)); ev.createInstance(out var EventTestComputer); EventTestComputer.set3DAttributes(RuntimeUtils.To3DAttributes(Targetbotcontroller)); EventTestComputer.start(); } //EVENT TEST BUS VR { Check(RuntimeManager.StudioSystem2.getEvent("event:/EV_Test_VRBus", out var ev)); ev.createInstance(out var EventTestComputer); EventTestComputer.set3DAttributes(RuntimeUtils.To3DAttributes(Targetbotcontroller)); EventTestComputer.start(); } } private void Check(FMOD.RESULT result) { if (result != FMOD.RESULT.OK) print(result); } void Update() { } } |