Audio position in Unity
Is it possible to somehow change the behavior in Unity when AudioClips are played fixed to the world position of Vector3.zero? For example, use the current camera position instead?
If the camera moves or is in a different position than Vector3.zero, the panning and volume of the AudioClip changes depending on the camera position to Vector3.zero.
Is PlaySoundAction played using PlayAudio method in NoesisProviders.cs?
If the camera moves or is in a different position than Vector3.zero, the panning and volume of the AudioClip changes depending on the camera position to Vector3.zero.
Is PlaySoundAction played using PlayAudio method in NoesisProviders.cs?
Code: Select all
public void PlayAudio(string uri, float volume)
{
Value v;
if (_audios.TryGetValue(uri, out v) && v.audio != null)
{
UnityEngine.AudioSource.PlayClipAtPoint(v.audio, UnityEngine.Vector3.zero, volume);
}
else
{
UnityEngine.Debug.LogError("AudioClip not found '" + uri + "'");
}
}Re: Audio position in Unity
I propose adding a new PlayAudio function to NoesisProviders.cs (we can add this to noesis, please report a ticket about it)
And then, I recommend creating a new action PlayPositionalSoundAction (cloning from PlaySoundAction) and adding a new dependency property for the Position and implementing Invoke as:
And now, you have a Position property in XAML that you can bind to your data context and use whatever value you need (camera position for example).
Please, let us know if this works for you and let's see if we can include a similar solution in Noesis.
Code: Select all
public void PlayAudio(string uri, UnityEngine.Vector3 position, float volume)
{
Value v;
if (_audios.TryGetValue(uri, out v) && v.audio != null)
{
UnityEngine.AudioSource.PlayClipAtPoint(v.audio, position, volume);
}
else
{
UnityEngine.Debug.LogError("AudioClip not found '" + uri + "'");
}
}
Code: Select all
protected override void Invoke(object parameter)
{
Uri source = Source;
if (AssociatedObject != null && source != null &&
!string.IsNullOrEmpty(source.OriginalString))
{
AudioProvider.instance.PlayAudio(uri.GetPath(), Position, Volume);
}
}
Please, let us know if this works for you and let's see if we can include a similar solution in Noesis.
Re: Audio position in Unity
Thank you for your solution, it works as expected. Below are the steps, if anyone wants to implement this.
It's a good universal solution, after some modifications it could be used so that the sound comes directly from the individual buttons in 3D.
However, I think for most cases it will be enough if the sound always comes from one place, and I expected that to be the default behavior as well.
If the NoesisView component is attached to a camera that is moving, now it doesn't work as expected.
So I decided to use a simpler solution and that is to modify the AudioSource.PlayClipAtPoint method, which actually just creates a new temporary GameObject with the AudioSource component, which sets the spatialBlend property to 1.0, which means that the audio will be played in 3D. For my needs, I just set this property to zero. So, if Unity decided to allow this property to be set as a parameter in PlayClipAtPoint, everything would be easier. I decided to modify the PlayClipAtPoint method and also use pooling for temporary AudioSource objects. You can find the code below as well.
PlayPositionalSoundAction solution:
1. Add PlayAudio method overloading in the NoesisProviders class:
2. Add to the DataContext (my ViewModel inherits from MonoBehaviour):
3. Add the PlayPositionalSoundAction class to the Assets folder
4. Use in xaml:
5. In the Unity inspector, assign a camera object (or other as needed) to the uiSoundPositionObject property
Solution using the PlayClip custom method
1. Create a PlayClip method in the NoesisProviders class
2. Edit PlayAudio method
Solution using custom PlayClip method and AudioSource object pooling
1. Insert the following code into the NoesisProviders class
2. Edit PlayAudio method
I think that the PlayClip method could be the default solution in NoesisGUI. The playback of sounds in the menu should be the same wherever the camera is.
In case a specific sound needs to be played in 3D (for example if the NoesisView is like a GameObject in a scene), I would just use PlayPositionalSoundAction, maybe named PlaySoundAction3D.
All the solutions listed work well, I hope this helps someone.
It's a good universal solution, after some modifications it could be used so that the sound comes directly from the individual buttons in 3D.
However, I think for most cases it will be enough if the sound always comes from one place, and I expected that to be the default behavior as well.
If the NoesisView component is attached to a camera that is moving, now it doesn't work as expected.
So I decided to use a simpler solution and that is to modify the AudioSource.PlayClipAtPoint method, which actually just creates a new temporary GameObject with the AudioSource component, which sets the spatialBlend property to 1.0, which means that the audio will be played in 3D. For my needs, I just set this property to zero. So, if Unity decided to allow this property to be set as a parameter in PlayClipAtPoint, everything would be easier. I decided to modify the PlayClipAtPoint method and also use pooling for temporary AudioSource objects. You can find the code below as well.
PlayPositionalSoundAction solution:
1. Add PlayAudio method overloading in the NoesisProviders class:
Code: Select all
public void PlayAudio(string uri, UnityEngine.Vector3 position, float volume)
{
if (_audios.TryGetValue(uri, out var v) && v.audio != null)
{
UnityEngine.AudioSource.PlayClipAtPoint(v.audio, position, volume);
}
else
{
UnityEngine.Debug.LogError("AudioClip not found '" + uri + "'");
}
}
Code: Select all
[SerializeField]
private UnityEngine.Transform _uiSoundPositionObject;
public Vector3 SoundPosition => _uiSoundPositionObject != null ? _uiSoundPositionObject.position : Vector3.zero;
private void Update()
{
if (_uiSoundPositionObject != null)
{
OnPropertyChanged(nameof(SoundPosition));
}
}
Code: Select all
#if UNITY_5_3_OR_NEWER
#define NOESIS
using UnityEngine;
using Noesis;
using NoesisApp;
#else
using Microsoft.Xaml.Behaviors;
using System.Numerics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
#endif
using System;
namespace YourNamespace
{
public class PlayPositionalSoundAction : TriggerAction<DependencyObject>
{
#if NOESIS
public new PlayPositionalSoundAction Clone()
{
return (PlayPositionalSoundAction)base.Clone();
}
public new PlayPositionalSoundAction CloneCurrentValue()
{
return (PlayPositionalSoundAction)base.CloneCurrentValue();
}
protected override void Invoke(object parameter)
{
var source = Source;
if (AssociatedObject != null && source != null &&
!string.IsNullOrEmpty(source.OriginalString))
{
AudioProvider.instance.PlayAudio(source.GetPath(), Position, (float)Volume);
}
}
#else
public PlayPositionalSoundAction()
{
}
protected virtual void SetMediaElementProperties(MediaElement mediaElement)
{
if (mediaElement != null)
{
mediaElement.Source = Source;
mediaElement.Volume = Volume;
}
}
protected override void Invoke(object parameter)
{
if (!(Source == null) && base.AssociatedObject != null)
{
Popup popup = new Popup();
MediaElement mediaElement = new MediaElement();
popup.Child = mediaElement;
mediaElement.Visibility = Visibility.Collapsed;
SetMediaElementProperties(mediaElement);
mediaElement.MediaEnded += delegate
{
popup.Child = null;
popup.IsOpen = false;
};
mediaElement.MediaFailed += delegate
{
popup.Child = null;
popup.IsOpen = false;
};
popup.IsOpen = true;
}
}
#endif
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position",
typeof(Vector3), typeof(PlayPositionalSoundAction), new PropertyMetadata(default(Vector3)));
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(Uri), typeof(PlayPositionalSoundAction), null);
public static readonly DependencyProperty VolumeProperty = DependencyProperty.Register("Volume", typeof(double),
typeof(PlayPositionalSoundAction), new PropertyMetadata(0.5));
public Vector3 Position
{
get => (Vector3)GetValue(PositionProperty);
set => SetValue(PositionProperty, value);
}
public Uri Source
{
get => (Uri)GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
public double Volume
{
get => (double)GetValue(VolumeProperty);
set => SetValue(VolumeProperty, value);
}
}
}
Code: Select all
<local:PlayPositionalSoundAction Source="/Assets/Sounds/ButtonHoverSound.wav" Volume="1" Position="{Binding SoundPosition, Mode=OneWay}"/>
Solution using the PlayClip custom method
1. Create a PlayClip method in the NoesisProviders class
Code: Select all
public static void PlayClip(AudioClip clip, [DefaultValue("1.0F")] float volume)
{
GameObject gameObject = new GameObject("One shot audio");
var audioSource = gameObject.AddComponent<AudioSource>();
audioSource.clip = clip;
audioSource.volume = volume;
audioSource.bypassEffects = true;
audioSource.bypassListenerEffects = true;
audioSource.bypassReverbZones = true;
audioSource.spatialize = false;
audioSource.spatialBlend = 0f;
audioSource.Play();
UnityEngine.Object.Destroy(audioSource.gameObject, clip.length * (Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale));
}
Code: Select all
public void PlayAudio(string uri, float volume)
{
if (_audios.TryGetValue(uri, out var v) && v.audio != null)
{
// UnityEngine.AudioSource.PlayClipAtPoint(v.audio, UnityEngine.Vector3.zero, volume);
PlayClip(v.audio, volume);
}
else
{
UnityEngine.Debug.LogError("AudioClip not found '" + uri + "'");
}
}
1. Insert the following code into the NoesisProviders class
Code: Select all
private static NoesisView _noesisView;
private static NoesisView noesisView => _noesisView ??= UnityEngine.Object.FindObjectOfType<NoesisView>();
public static void PlayClip(AudioClip clip, [DefaultValue("1.0F")] float volume)
{
var audioSource = AudioSourcePool.Obtain();
audioSource.clip = clip;
audioSource.volume = volume;
audioSource.bypassEffects = true;
audioSource.bypassListenerEffects = true;
audioSource.bypassReverbZones = true;
audioSource.spatialize = false;
audioSource.spatialBlend = 0f;
audioSource.Play();
noesisView.StartCoroutine(FreeAudioSource(audioSource,
clip.length * ((double)Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale)));
}
private static IEnumerator FreeAudioSource(AudioSource audioSource, float delay)
{
yield return new WaitForSeconds(delay);
AudioSourcePool.Put(audioSource);
}
internal static class AudioSourcePool
{
private static readonly Queue<AudioSource> audioSources = new Queue<AudioSource>();
public static int maxSize = 3;
public static AudioSource Obtain()
{
var audioSource = audioSources.Count > 0 ? audioSources.Dequeue() : NewAudioSource();
audioSource.gameObject.SetActive(true);
return audioSource;
}
private static AudioSource NewAudioSource()
{
var gameObject = new GameObject("One shot audio");
var audioSource = gameObject.AddComponent<AudioSource>();
return audioSource;
}
public static void Put(AudioSource audioSource)
{
if (audioSource == null) return;
audioSource.gameObject.SetActive(false);
if (audioSources.Count >= maxSize)
{
UnityEngine.Object.Destroy(audioSource.gameObject);
}
else
{
audioSources.Enqueue(audioSource);
}
}
}
Code: Select all
public void PlayAudio(string uri, float volume)
{
if (_audios.TryGetValue(uri, out var v) && v.audio != null)
{
// UnityEngine.AudioSource.PlayClipAtPoint(v.audio, UnityEngine.Vector3.zero, volume);
PlayClip(v.audio, volume);
}
else
{
UnityEngine.Debug.LogError("AudioClip not found '" + uri + "'");
}
}
In case a specific sound needs to be played in 3D (for example if the NoesisView is like a GameObject in a scene), I would just use PlayPositionalSoundAction, maybe named PlaySoundAction3D.
All the solutions listed work well, I hope this helps someone.
Re: Audio position in Unity
Thanks for the time posting this! Good work there.
Who is online
Users browsing this forum: No registered users and 3 guests