Improve diapason

This commit is contained in:
2026-02-03 18:50:04 +01:00
parent 23d3cb5d2c
commit 71c44eb39e
12 changed files with 1172 additions and 602 deletions
@@ -59,12 +59,6 @@ namespace Unheimlich.Audio
[SerializeField] private float _maxDistance = 10f;
[SerializeField] private AnimationCurve _distanceAttenuation = AnimationCurve.EaseInOut(0f, 1f, 1f, 0f);
[Header("Vibration Settings")]
[SerializeField] private bool _enableVibration = true;
[SerializeField] private float _vibrationIntensityMin = 0.1f;
[SerializeField] private float _vibrationIntensityMax = 0.5f;
[SerializeField] private float _vibrationFrequency = 10f;
[Header("Visual Feedback")]
[SerializeField] private ParticleSystem _emitterParticles;
@@ -168,17 +162,6 @@ namespace Unheimlich.Audio
duration);
}
public void ResetConcentrationVolume()
{
TweenHelper.CancelTween(ref _concentrationFadeTweenId);
if (_continuousAudioSource == null)
return;
float baselineVolume = _continuousBaseVolume * BASELINE_VOLUME_MULTIPLIER;
_continuousAudioSource.volume = baselineVolume;
}
/// <summary>
/// Starts the listening sequence for this emitter.
/// Blocked if the emitter has already been tapped during this session.
@@ -253,9 +236,6 @@ namespace Unheimlich.Audio
_audioSource.loop = _loopStaticAudio;
_audioSource.Play();
}
if (_enableVibration)
StartVibration();
}
/// <summary>
@@ -295,7 +275,6 @@ namespace Unheimlich.Audio
}
EnableReverb(false);
StopVibration();
}
private void Update()
@@ -311,8 +290,6 @@ namespace Unheimlich.Audio
PlayProceduralSound();
_nextProceduralEventTime = _listeningTimer + Random.Range(_durationMin, _durationMax);
}
UpdateVibration();
}
@@ -420,39 +397,6 @@ namespace Unheimlich.Audio
}
}
private void StartVibration()
{
if (Gamepad.current == null)
return;
float distance = Vector3.Distance(_listenerPosition, transform.position);
float distanceFactor = Mathf.Clamp01(1f - (distance / _maxDistance));
float intensity = Mathf.Lerp(_vibrationIntensityMin, _vibrationIntensityMax, distanceFactor);
Gamepad.current.SetMotorSpeeds(intensity, intensity);
}
private void UpdateVibration()
{
if (!_enableVibration || Gamepad.current == null)
return;
float distance = Vector3.Distance(_listenerPosition, transform.position);
float distanceFactor = Mathf.Clamp01(1f - (distance / _maxDistance));
float intensity = Mathf.Lerp(_vibrationIntensityMin, _vibrationIntensityMax, distanceFactor);
float vibrationPattern = Mathf.Abs(Mathf.Sin(Time.time * _vibrationFrequency));
intensity *= vibrationPattern;
Gamepad.current.SetMotorSpeeds(intensity, intensity);
}
private void StopVibration()
{
if (Gamepad.current != null)
Gamepad.current.SetMotorSpeeds(0f, 0f);
}
private void OnDisable()
{
StopListening();
@@ -1,91 +0,0 @@
using UnityEngine;
using UnityEngine.Events;
namespace Unheimlich.Audio
{
public class AudioListenableEvent : MonoBehaviour
{
[Header("Event Settings")]
[SerializeField] private bool _triggerOnce = false;
[SerializeField] private float _delayBeforeEvent = 0f;
[Header("Events")]
[SerializeField] private UnityEvent _onListeningStarted;
[SerializeField] private UnityEvent _onListeningStopped;
[SerializeField] private UnityEvent _onFirstListen;
private AudioListenable _listenable;
private bool _hasBeenListenedTo = false;
private bool _isCurrentlyListening = false;
private void Awake()
{
_listenable = GetComponent<AudioListenable>();
if (_listenable == null)
{
Debug.LogError($"AudioListenableEvent on {gameObject.name} requires AudioListenable component!");
enabled = false;
}
}
private void Update()
{
if (_listenable == null)
return;
bool wasListening = _isCurrentlyListening;
if (wasListening && !_isCurrentlyListening)
{
OnListeningStopped();
}
}
public void OnListeningStart()
{
if (_triggerOnce && _hasBeenListenedTo)
return;
_isCurrentlyListening = true;
if (_delayBeforeEvent > 0)
Invoke(nameof(TriggerListeningStartedEvent), _delayBeforeEvent);
else
TriggerListeningStartedEvent();
if (!_hasBeenListenedTo)
{
_hasBeenListenedTo = true;
_onFirstListen?.Invoke();
}
}
private void TriggerListeningStartedEvent()
{
_onListeningStarted?.Invoke();
}
private void OnListeningStopped()
{
_isCurrentlyListening = false;
_onListeningStopped?.Invoke();
}
public void EnableListenable()
{
if (_listenable != null)
_listenable.enabled = true;
}
public void DisableListenable()
{
if (_listenable != null)
_listenable.enabled = false;
}
public void ResetTrigger()
{
_hasBeenListenedTo = false;
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 522dcfb634e07d240afa8ae5344fc785
@@ -249,36 +249,6 @@ namespace Unheimlich.Audio
}
}
private float CalculateVolumeFromDistance(float distance, AudioSource source)
{
float baseVolume = source.volume;
AudioSourceDemo demo = source.GetComponent<AudioSourceDemo>();
if (demo != null)
baseVolume = demo.OriginalVolume;
if (distance > source.maxDistance)
return 0f;
if (distance < source.minDistance)
return baseVolume;
switch (source.rolloffMode)
{
case AudioRolloffMode.Logarithmic:
float normalizedDist = (distance - source.minDistance) / (source.maxDistance - source.minDistance);
float logVolume = baseVolume * (1f / (1f + normalizedDist * 10f));
return logVolume * Mathf.Pow(1f - normalizedDist, _volumeRolloffPower);
case AudioRolloffMode.Linear:
float linearFactor = 1f - ((distance - source.minDistance) / (source.maxDistance - source.minDistance));
linearFactor = Mathf.Pow(linearFactor, _volumeRolloffPower);
return baseVolume * Mathf.Max(0f, linearFactor);
default:
return baseVolume;
}
}
private AudioSource GetOrCreateProxy(AudioSource remoteSource)
{
@@ -1,102 +0,0 @@
using UnityEngine;
namespace Unheimlich.Audio
{
[RequireComponent(typeof(AudioListenable))]
public class ThroughWallListener : MonoBehaviour
{
[Header("Behind Wall Detection")]
[SerializeField] private LayerMask _detectionLayers;
[SerializeField] private float _detectionDistance = 2f;
[SerializeField] private float _detectionRadius = 1f;
[Header("Audio Feedback")]
[SerializeField] private AudioClip _nothingBehindClip;
[SerializeField] private AudioClip _somethingBehindClip;
[SerializeField] private AudioClip _importantObjectClip;
[SerializeField] private string _importantObjectTag = "Important";
[Header("Visual Feedback")]
[SerializeField] private GameObject _highlightEffectPrefab;
[SerializeField] private float _highlightDuration = 3f;
private AudioSource _audioSource;
private AudioListenable _listenable;
private GameObject _currentHighlight;
private void Awake()
{
_listenable = GetComponent<AudioListenable>();
_audioSource = GetComponent<AudioSource>();
if (_audioSource == null)
_audioSource = gameObject.AddComponent<AudioSource>();
}
private void Update()
{
}
public void OnWallListened()
{
Vector3 checkPosition = transform.position + transform.forward * (_detectionDistance * 0.5f);
Collider[] hitColliders = Physics.OverlapSphere(checkPosition, _detectionRadius, _detectionLayers);
if (hitColliders.Length > 0)
{
bool foundImportant = false;
foreach (Collider col in hitColliders)
{
if (col.CompareTag(_importantObjectTag))
{
foundImportant = true;
PlayFeedbackSound(_importantObjectClip);
ShowHighlight(col.transform.position);
break;
}
}
if (!foundImportant)
{
PlayFeedbackSound(_somethingBehindClip);
}
}
else
{
PlayFeedbackSound(_nothingBehindClip);
}
}
private void PlayFeedbackSound(AudioClip clip)
{
if (clip != null && _audioSource != null)
{
_audioSource.PlayOneShot(clip);
}
}
private void ShowHighlight(Vector3 position)
{
if (_highlightEffectPrefab != null)
{
if (_currentHighlight != null)
Destroy(_currentHighlight);
_currentHighlight = Instantiate(_highlightEffectPrefab, position, Quaternion.identity);
Destroy(_currentHighlight, _highlightDuration);
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Vector3 checkPosition = transform.position + transform.forward * (_detectionDistance * 0.5f);
Gizmos.DrawWireSphere(checkPosition, _detectionRadius);
Gizmos.color = Color.blue;
Gizmos.DrawRay(transform.position, transform.forward * _detectionDistance);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3fad34f7be8a64242984b14410478690
@@ -1,181 +0,0 @@
using UnityEngine;
using UnityEditor;
using Unheimlich.Audio;
namespace Unheimlich.Editor
{
public class AudioListenableBatchSetup : EditorWindow
{
private AudioEmitterType _selectedType = AudioEmitterType.Wall;
private LayerMask _targetLayer;
private bool _overwriteExisting = false;
[MenuItem("Tools/Unheimlich/Audio Listenable Batch Setup")]
public static void ShowWindow()
{
GetWindow<AudioListenableBatchSetup>("Audio Batch Setup");
}
private void OnGUI()
{
GUILayout.Label("Batch Audio Listenable Setup", EditorStyles.boldLabel);
EditorGUILayout.Space(10);
EditorGUILayout.HelpBox(
"This tool adds AudioListenable components to all selected GameObjects in the scene.",
MessageType.Info
);
EditorGUILayout.Space(5);
_selectedType = (AudioEmitterType)EditorGUILayout.EnumPopup("Emitter Type", _selectedType);
_overwriteExisting = EditorGUILayout.Toggle("Overwrite Existing", _overwriteExisting);
EditorGUILayout.Space(10);
if (GUILayout.Button("Add to Selected Objects", GUILayout.Height(30)))
{
AddToSelectedObjects();
}
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Quick Presets", EditorStyles.boldLabel);
if (GUILayout.Button("Setup All Walls in Scene"))
{
SetupObjectsByName("wall", AudioEmitterType.Wall);
}
if (GUILayout.Button("Setup All Doors in Scene"))
{
SetupObjectsByName("door", AudioEmitterType.Wood);
}
}
private void AddToSelectedObjects()
{
GameObject[] selectedObjects = Selection.gameObjects;
if (selectedObjects.Length == 0)
{
EditorUtility.DisplayDialog("No Selection", "Please select at least one GameObject in the scene.", "OK");
return;
}
int addedCount = 0;
int skippedCount = 0;
foreach (GameObject obj in selectedObjects)
{
AudioListenable existing = obj.GetComponent<AudioListenable>();
if (existing != null && !_overwriteExisting)
{
skippedCount++;
continue;
}
if (existing == null)
{
Undo.AddComponent<AudioListenable>(obj);
existing = obj.GetComponent<AudioListenable>();
}
ConfigureListenable(existing, _selectedType);
addedCount++;
}
EditorUtility.DisplayDialog(
"Batch Setup Complete",
$"Added/Updated: {addedCount}\nSkipped: {skippedCount}",
"OK"
);
}
private void SetupObjectsByName(string nameContains, AudioEmitterType type)
{
GameObject[] allObjects = FindObjectsByType<GameObject>(FindObjectsSortMode.None);
int count = 0;
foreach (GameObject obj in allObjects)
{
if (obj.name.ToLower().Contains(nameContains.ToLower()))
{
AudioListenable existing = obj.GetComponent<AudioListenable>();
if (existing == null)
{
Undo.AddComponent<AudioListenable>(obj);
existing = obj.GetComponent<AudioListenable>();
}
ConfigureListenable(existing, type);
count++;
}
}
EditorUtility.DisplayDialog(
"Setup Complete",
$"Configured {count} objects containing '{nameContains}' as {type}",
"OK"
);
}
private void ConfigureListenable(AudioListenable listenable, AudioEmitterType type)
{
SerializedObject so = new SerializedObject(listenable);
so.FindProperty("_emitterType").enumValueIndex = (int)type;
so.FindProperty("_useProceduralAudio").boolValue = true;
switch (type)
{
case AudioEmitterType.Wall:
so.FindProperty("_baseFrequency").floatValue = 200f;
so.FindProperty("_frequencyVariation").floatValue = 20f;
so.FindProperty("_volumeMin").floatValue = 0.2f;
so.FindProperty("_volumeMax").floatValue = 0.4f;
break;
case AudioEmitterType.Metal:
so.FindProperty("_baseFrequency").floatValue = 800f;
so.FindProperty("_frequencyVariation").floatValue = 100f;
so.FindProperty("_volumeMin").floatValue = 0.4f;
so.FindProperty("_volumeMax").floatValue = 0.7f;
break;
case AudioEmitterType.Wood:
so.FindProperty("_baseFrequency").floatValue = 300f;
so.FindProperty("_frequencyVariation").floatValue = 50f;
so.FindProperty("_volumeMin").floatValue = 0.3f;
so.FindProperty("_volumeMax").floatValue = 0.6f;
break;
case AudioEmitterType.Glass:
so.FindProperty("_baseFrequency").floatValue = 1200f;
so.FindProperty("_frequencyVariation").floatValue = 150f;
so.FindProperty("_volumeMin").floatValue = 0.3f;
so.FindProperty("_volumeMax").floatValue = 0.6f;
break;
case AudioEmitterType.Stone:
so.FindProperty("_baseFrequency").floatValue = 250f;
so.FindProperty("_frequencyVariation").floatValue = 30f;
so.FindProperty("_volumeMin").floatValue = 0.3f;
so.FindProperty("_volumeMax").floatValue = 0.5f;
break;
case AudioEmitterType.Mechanical:
so.FindProperty("_baseFrequency").floatValue = 500f;
so.FindProperty("_frequencyVariation").floatValue = 80f;
so.FindProperty("_volumeMin").floatValue = 0.4f;
so.FindProperty("_volumeMax").floatValue = 0.7f;
break;
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(listenable);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5d813501fc2b446419db87d892d79ce3
@@ -4,6 +4,7 @@ using UnityEngine.InputSystem;
using Unheimlich.Audio;
using Unheimlich.Core;
using Unheimlich.Utility;
using Unheimlich.Gameplay.Player;
using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Gameplay.Player
@@ -38,11 +39,14 @@ namespace Unheimlich.Gameplay.Player
[SerializeField] private float _diapasonTweenDuration = 0.3f;
[SerializeField] private float _diapasonHideOffset = -0.5f;
[SerializeField] private AnimationCurve _diapasonTweenCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[SerializeField] private float _vibrationDuration = 15f;
[Header("Camera Settings")]
[SerializeField] private float _cameraPanSpeed = 2f;
[SerializeField] private float _cameraPanBorderSize = 0.1f;
[SerializeField] private float _maxCameraPanAngle = 15f;
[SerializeField] private float _cameraAlignmentSpeed = 3f;
[SerializeField] private float _cameraAlignmentThreshold = 0.5f;
[Header("Visual Feedback")]
[SerializeField] private float _vignetteIntensity = 0.5f;
@@ -78,8 +82,14 @@ namespace Unheimlich.Gameplay.Player
private float _originalAmbientVolume;
private Vector3 _diapasonEquippedPosition;
private Vector3 _diapasonHiddenPosition;
private Vector3 _originalCameraLocalPosition;
private Vector3 _originalPlayerPosition;
private Vector3 _originalPlayerRotation;
private Vector3 _originalCameraLocalRotation;
private bool _canUseDiapason;
private bool _isFadingOutConcentration;
private Coroutine _returnCoroutine;
private Coroutine _vibrationCoroutine;
protected override void OnAwake()
{
@@ -87,6 +97,15 @@ namespace Unheimlich.Gameplay.Player
if (_playerAudioSource == null)
_playerAudioSource = gameObject.AddComponent<AudioSource>();
if (_playerCamera != null)
{
_originalCameraLocalPosition = _playerCamera.transform.localPosition;
_originalCameraLocalRotation = _playerCamera.transform.localEulerAngles;
}
_originalPlayerPosition = transform.position;
_originalPlayerRotation = transform.eulerAngles;
if (_diapasonObject != null)
{
_diapasonEquippedPosition = _diapasonObject.transform.localPosition;
@@ -222,10 +241,11 @@ namespace Unheimlich.Gameplay.Player
/// <summary>
/// Main listening sequence coroutine:
/// 1. Concentration phase - player breathes in, diapason animates out
/// 2. Volume fades from 30% to 100% during concentration
/// 3. After concentration duration, enters listening mode
/// 4. Vignette appears, camera pan enabled, cursor shown
/// 1. Camera aligns perpendicular to the target object
/// 2. Concentration phase - player breathes in, diapason animates out
/// 3. Volume fades from 30% to 100% during concentration
/// 4. After concentration duration, enters listening mode
/// 5. Vignette appears, camera pan enabled, cursor shown
/// </summary>
private IEnumerator ListeningSequence()
{
@@ -233,7 +253,11 @@ namespace Unheimlich.Gameplay.Player
InputManager.SetContext(InputManager.InputContext.Listening);
if (_currentTargetEmitter != null)
{
_currentTargetEmitter.SetConcentrating(true);
yield return StartCoroutine(AlignCameraToTarget(_currentTargetEmitter.transform));
}
if (_diapasonObject != null)
{
@@ -281,6 +305,8 @@ namespace Unheimlich.Gameplay.Player
/// - Stops emitter listening
/// - Hides vignette and cursor
/// - Re-enables player movement
/// - Restores player position, rotation and camera with lerp
/// - Resets diapason animation to idle
/// </summary>
private void StopListeningMode()
{
@@ -290,9 +316,21 @@ namespace Unheimlich.Gameplay.Player
_listeningCoroutine = null;
}
if (_vibrationCoroutine != null)
{
StopCoroutine(_vibrationCoroutine);
_vibrationCoroutine = null;
}
IsListening = false;
IsConcentrating = false;
if (_returnCoroutine != null)
{
StopCoroutine(_returnCoroutine);
}
_returnCoroutine = StartCoroutine(ReturnToOriginalPosition());
if (_playerAudioSource != null && _playerAudioSource.isPlaying)
{
TweenHelper.CancelTween(ref _playerAudioFadeTweenId);
@@ -317,6 +355,7 @@ namespace Unheimlich.Gameplay.Player
if (_diapasonAnimator != null)
{
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
_diapasonAnimator.Play("Idle", 0, 0f);
_diapasonAnimator.enabled = false;
}
@@ -364,6 +403,127 @@ namespace Unheimlich.Gameplay.Player
_isFadingOutConcentration = false;
}
/// <summary>
/// Aligns the player and camera to the target object:
/// - Player moves along target's local X axis to align with target center
/// - Player rotates to face the target
/// - Camera pitch adjusts to look at target center
/// </summary>
private IEnumerator AlignCameraToTarget(Transform target)
{
if (_playerCamera == null || target == null)
yield break;
_originalCameraLocalPosition = _playerCamera.transform.localPosition;
_originalCameraLocalRotation = _playerCamera.transform.localEulerAngles;
_originalPlayerPosition = transform.position;
_originalPlayerRotation = transform.eulerAngles;
Vector3 targetCenter = target.position;
Renderer targetRenderer = target.GetComponent<Renderer>();
if (targetRenderer != null)
{
targetCenter = targetRenderer.bounds.center;
}
else
{
Collider targetCollider = target.GetComponent<Collider>();
if (targetCollider != null)
targetCenter = targetCollider.bounds.center;
}
Vector3 startPlayerPos = transform.position;
Vector3 startPlayerRotation = transform.eulerAngles;
Vector3 startCameraLocalRotation = _playerCamera.transform.localEulerAngles;
float startPitch = startCameraLocalRotation.x;
if (startPitch > 180f)
startPitch -= 360f;
float elapsed = 0f;
float duration = 1f / _cameraAlignmentSpeed;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
Vector3 currentCameraPos = _playerCamera.transform.position;
Vector3 toTarget = targetCenter - currentCameraPos;
Vector3 directionToTarget = toTarget.normalized;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
float targetYaw = targetRotation.eulerAngles.y;
float targetPitch = -Mathf.Asin(directionToTarget.y) * Mathf.Rad2Deg;
Vector3 targetRightAxis = target.right;
Vector3 playerToTargetCenter = targetCenter - transform.position;
float offsetAlongTargetRight = Vector3.Dot(playerToTargetCenter, targetRightAxis);
Vector3 targetPlayerPos = transform.position + targetRightAxis * offsetAlongTargetRight;
Vector3 currentPlayerPos = Vector3.Lerp(startPlayerPos, targetPlayerPos, t);
transform.position = currentPlayerPos;
float currentYaw = Mathf.LerpAngle(startPlayerRotation.y, targetYaw, t);
transform.eulerAngles = new Vector3(0f, currentYaw, 0f);
float currentPitch = Mathf.LerpAngle(startPitch, targetPitch, t);
_playerCamera.transform.localEulerAngles = new Vector3(currentPitch, 0f, 0f);
yield return null;
}
}
/// <summary>
/// Returns player and camera to original position and rotation with smooth lerp.
/// </summary>
private IEnumerator ReturnToOriginalPosition()
{
if (_playerCamera == null)
yield break;
Vector3 startPlayerPos = transform.position;
Vector3 startPlayerRotation = transform.eulerAngles;
Vector3 startCameraLocalPos = _playerCamera.transform.localPosition;
Vector3 startCameraLocalRotation = _playerCamera.transform.localEulerAngles;
float startPitch = startCameraLocalRotation.x;
if (startPitch > 180f)
startPitch -= 360f;
float targetPitch = _originalCameraLocalRotation.x;
if (targetPitch > 180f)
targetPitch -= 360f;
float elapsed = 0f;
float duration = 1f / _cameraAlignmentSpeed;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
transform.position = Vector3.Lerp(startPlayerPos, _originalPlayerPosition, t);
float currentYaw = Mathf.LerpAngle(startPlayerRotation.y, _originalPlayerRotation.y, t);
transform.eulerAngles = new Vector3(0f, currentYaw, 0f);
float currentPitch = Mathf.LerpAngle(startPitch, targetPitch, t);
_playerCamera.transform.localEulerAngles = new Vector3(currentPitch, 0f, 0f);
_playerCamera.transform.localPosition = Vector3.Lerp(startCameraLocalPos, _originalCameraLocalPosition, t);
yield return null;
}
transform.position = _originalPlayerPosition;
transform.eulerAngles = _originalPlayerRotation;
_playerCamera.transform.localPosition = _originalCameraLocalPosition;
_playerCamera.transform.localEulerAngles = _originalCameraLocalRotation;
_returnCoroutine = null;
}
/// <summary>
/// Updates cursor position and camera panning during listening mode.
/// Supports both gamepad (look input) and mouse (screen edges).
@@ -485,6 +645,7 @@ namespace Unheimlich.Gameplay.Player
/// - Starts listening sequence on the target emitter
/// - Plays diapason particles
/// - Plays tap sound
/// - Starts vibration timer to stop after duration
/// </summary>
private void TriggerDiapasonTap()
{
@@ -492,6 +653,10 @@ namespace Unheimlich.Gameplay.Player
{
_diapasonAnimator.SetTrigger(ANIM_TRIGGER_TAP);
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, true);
if (_vibrationCoroutine != null)
StopCoroutine(_vibrationCoroutine);
_vibrationCoroutine = StartCoroutine(StopVibrationAfterDuration());
}
if (_diapasonTapClip != null && _playerAudioSource != null)
@@ -510,6 +675,21 @@ namespace Unheimlich.Gameplay.Player
}
}
/// <summary>
/// Stops the diapason vibration animation after the specified duration.
/// </summary>
private IEnumerator StopVibrationAfterDuration()
{
yield return new WaitForSeconds(_vibrationDuration);
if (_diapasonAnimator != null)
{
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
}
_vibrationCoroutine = null;
}
private void PlayBreathIn()
{
if (_breathInClip != null && _playerAudioSource != null)