Start implementation of diapason mechanics
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Unheimlich.Audio
|
||||
{
|
||||
public enum AudioEmitterType
|
||||
{
|
||||
Generic,
|
||||
Wall,
|
||||
Wood,
|
||||
Metal,
|
||||
Glass,
|
||||
Water,
|
||||
Stone,
|
||||
Mechanical,
|
||||
Electrical,
|
||||
Organic
|
||||
}
|
||||
|
||||
public class AudioListenable : MonoBehaviour
|
||||
{
|
||||
[Header("Emitter Settings")]
|
||||
[SerializeField] private AudioEmitterType _emitterType = AudioEmitterType.Generic;
|
||||
[SerializeField] private bool _useProceduralAudio = true;
|
||||
|
||||
[Header("Static Audio")]
|
||||
[SerializeField] private AudioClip _staticAudioClip;
|
||||
[SerializeField] private bool _loopStaticAudio = true;
|
||||
|
||||
[Header("Procedural Settings")]
|
||||
[SerializeField] private float _baseFrequency = 440f;
|
||||
[SerializeField] private float _frequencyVariation = 50f;
|
||||
[SerializeField] private float _volumeMin = 0.3f;
|
||||
[SerializeField] private float _volumeMax = 0.7f;
|
||||
[SerializeField] private float _durationMin = 0.5f;
|
||||
[SerializeField] private float _durationMax = 2f;
|
||||
|
||||
[Header("Distance Attenuation")]
|
||||
[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;
|
||||
|
||||
private AudioSource _audioSource;
|
||||
private bool _isCurrentlyListening;
|
||||
private float _listeningTimer;
|
||||
private float _nextProceduralEventTime;
|
||||
private Vector3 _listenerPosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_audioSource = GetComponent<AudioSource>();
|
||||
if (_audioSource == null)
|
||||
_audioSource = gameObject.AddComponent<AudioSource>();
|
||||
|
||||
_audioSource.spatialBlend = 1f;
|
||||
_audioSource.maxDistance = _maxDistance;
|
||||
_audioSource.rolloffMode = AudioRolloffMode.Custom;
|
||||
_audioSource.playOnAwake = false;
|
||||
}
|
||||
|
||||
public void StartListening(Vector3 listenerPosition)
|
||||
{
|
||||
_isCurrentlyListening = true;
|
||||
_listenerPosition = listenerPosition;
|
||||
_listeningTimer = 0f;
|
||||
_nextProceduralEventTime = 0f;
|
||||
|
||||
if (_useProceduralAudio)
|
||||
{
|
||||
PlayProceduralSound();
|
||||
}
|
||||
else if (_staticAudioClip != null)
|
||||
{
|
||||
_audioSource.clip = _staticAudioClip;
|
||||
_audioSource.loop = _loopStaticAudio;
|
||||
_audioSource.Play();
|
||||
}
|
||||
|
||||
if (_enableVibration)
|
||||
StartVibration();
|
||||
}
|
||||
|
||||
public void StopListening()
|
||||
{
|
||||
_isCurrentlyListening = false;
|
||||
|
||||
if (_audioSource != null && _audioSource.isPlaying)
|
||||
_audioSource.Stop();
|
||||
|
||||
StopVibration();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isCurrentlyListening)
|
||||
return;
|
||||
|
||||
_listeningTimer += Time.deltaTime;
|
||||
|
||||
if (_useProceduralAudio && _listeningTimer >= _nextProceduralEventTime)
|
||||
{
|
||||
PlayProceduralSound();
|
||||
_nextProceduralEventTime = _listeningTimer + Random.Range(_durationMin, _durationMax);
|
||||
}
|
||||
|
||||
UpdateVibration();
|
||||
}
|
||||
|
||||
private void PlayProceduralSound()
|
||||
{
|
||||
float distance = Vector3.Distance(_listenerPosition, transform.position);
|
||||
float distanceFactor = Mathf.Clamp01(1f - (distance / _maxDistance));
|
||||
float attenuation = _distanceAttenuation.Evaluate(distanceFactor);
|
||||
|
||||
float frequency = _baseFrequency + Random.Range(-_frequencyVariation, _frequencyVariation);
|
||||
float volume = Mathf.Lerp(_volumeMin, _volumeMax, Random.value) * attenuation;
|
||||
|
||||
AudioClip proceduralClip = GenerateProceduralAudioClip(frequency, Random.Range(_durationMin, _durationMax));
|
||||
|
||||
_audioSource.clip = proceduralClip;
|
||||
_audioSource.volume = volume;
|
||||
_audioSource.loop = false;
|
||||
_audioSource.Play();
|
||||
}
|
||||
|
||||
private AudioClip GenerateProceduralAudioClip(float frequency, float duration)
|
||||
{
|
||||
int sampleRate = 44100;
|
||||
int sampleCount = Mathf.RoundToInt(sampleRate * duration);
|
||||
|
||||
AudioClip clip = AudioClip.Create("ProceduralSound", sampleCount, 1, sampleRate, false);
|
||||
float[] samples = new float[sampleCount];
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float t = (float)i / sampleRate;
|
||||
float envelope = Mathf.Sin(Mathf.PI * t / duration);
|
||||
|
||||
samples[i] = GenerateSampleForType(frequency, t, _emitterType) * envelope;
|
||||
}
|
||||
|
||||
clip.SetData(samples, 0);
|
||||
return clip;
|
||||
}
|
||||
|
||||
private float GenerateSampleForType(float frequency, float time, AudioEmitterType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AudioEmitterType.Metal:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * time) * 0.5f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 2.1f * time) * 0.3f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 3.8f * time) * 0.2f;
|
||||
|
||||
case AudioEmitterType.Wood:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * time) * 0.6f +
|
||||
(Random.value - 0.5f) * 0.1f;
|
||||
|
||||
case AudioEmitterType.Glass:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * 1.5f * time) * 0.4f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 2.7f * time) * 0.3f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 4.2f * time) * 0.3f;
|
||||
|
||||
case AudioEmitterType.Stone:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * 0.8f * time) * 0.5f +
|
||||
(Random.value - 0.5f) * 0.3f;
|
||||
|
||||
case AudioEmitterType.Water:
|
||||
return (Random.value - 0.5f) * 0.5f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 0.5f * time) * 0.3f;
|
||||
|
||||
case AudioEmitterType.Mechanical:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * time) * 0.4f +
|
||||
Mathf.Sign(Mathf.Sin(2f * Mathf.PI * frequency * 0.3f * time)) * 0.3f;
|
||||
|
||||
case AudioEmitterType.Electrical:
|
||||
return (Random.value - 0.5f) * 0.6f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 50f * time) * 0.2f;
|
||||
|
||||
case AudioEmitterType.Organic:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * 0.7f * time) * 0.5f +
|
||||
Mathf.Sin(2f * Mathf.PI * frequency * 1.3f * time) * 0.3f +
|
||||
(Random.value - 0.5f) * 0.2f;
|
||||
|
||||
case AudioEmitterType.Wall:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * 0.5f * time) * 0.3f +
|
||||
(Random.value - 0.5f) * 0.1f;
|
||||
|
||||
default:
|
||||
return Mathf.Sin(2f * Mathf.PI * frequency * time);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireSphere(transform.position, _maxDistance);
|
||||
|
||||
if (_isCurrentlyListening)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(transform.position, _listenerPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e15490f7adacb594cb114f535e71d6b6
|
||||
@@ -0,0 +1,91 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 522dcfb634e07d240afa8ae5344fc785
|
||||
@@ -0,0 +1,119 @@
|
||||
using UnityEngine;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
|
||||
namespace Unheimlich.Audio
|
||||
{
|
||||
public class DiapasonDebugger : MonoBehaviour
|
||||
{
|
||||
[Header("Debug Display")]
|
||||
[SerializeField] private bool _showDebugInfo = true;
|
||||
[SerializeField] private bool _showGizmos = true;
|
||||
[SerializeField] private Color _listeningRangeColor = Color.cyan;
|
||||
[SerializeField] private Color _activeTargetColor = Color.green;
|
||||
|
||||
private DiapasonListeningHandler _listeningHandler;
|
||||
private AudioListenable[] _allListenables;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_listeningHandler = DiapasonListeningHandler.Instance;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
RefreshListenablesList();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!_showDebugInfo || _listeningHandler == null)
|
||||
return;
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 10, 300, 200));
|
||||
GUILayout.Box("Diapason System Debug");
|
||||
|
||||
GUILayout.Label($"Is Listening: {_listeningHandler.IsListening}");
|
||||
GUILayout.Label($"Is Concentrating: {_listeningHandler.IsConcentrating}");
|
||||
|
||||
if (_allListenables != null)
|
||||
GUILayout.Label($"Total Listenables: {_allListenables.Length}");
|
||||
|
||||
if (GUILayout.Button("Refresh Listenables List"))
|
||||
RefreshListenablesList();
|
||||
|
||||
if (GUILayout.Button("Toggle Debug Gizmos"))
|
||||
_showGizmos = !_showGizmos;
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void RefreshListenablesList()
|
||||
{
|
||||
_allListenables = FindObjectsByType<AudioListenable>(FindObjectsSortMode.None);
|
||||
Debug.Log($"Found {_allListenables.Length} AudioListenable objects in scene");
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!_showGizmos)
|
||||
return;
|
||||
|
||||
if (_allListenables == null)
|
||||
_allListenables = FindObjectsByType<AudioListenable>(FindObjectsSortMode.None);
|
||||
|
||||
foreach (AudioListenable listenable in _allListenables)
|
||||
{
|
||||
if (listenable == null)
|
||||
continue;
|
||||
|
||||
Gizmos.color = _listeningRangeColor;
|
||||
Gizmos.DrawIcon(listenable.transform.position, "sv_icon_dot3_pix16_gizmo", true);
|
||||
|
||||
Gizmos.color = new Color(_listeningRangeColor.r, _listeningRangeColor.g, _listeningRangeColor.b, 0.1f);
|
||||
Gizmos.DrawSphere(listenable.transform.position, 0.2f);
|
||||
}
|
||||
|
||||
if (_listeningHandler != null && _listeningHandler.IsListening)
|
||||
{
|
||||
Gizmos.color = _activeTargetColor;
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("List All Listenables")]
|
||||
private void ListAllListenables()
|
||||
{
|
||||
RefreshListenablesList();
|
||||
|
||||
Debug.Log("=== Audio Listenables in Scene ===");
|
||||
foreach (AudioListenable listenable in _allListenables)
|
||||
{
|
||||
if (listenable != null)
|
||||
Debug.Log($"- {listenable.gameObject.name} (Type: {listenable.GetType().Name})");
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Enable All Listenables")]
|
||||
private void EnableAllListenables()
|
||||
{
|
||||
RefreshListenablesList();
|
||||
foreach (AudioListenable listenable in _allListenables)
|
||||
{
|
||||
if (listenable != null)
|
||||
listenable.enabled = true;
|
||||
}
|
||||
Debug.Log("Enabled all AudioListenable components");
|
||||
}
|
||||
|
||||
[ContextMenu("Disable All Listenables")]
|
||||
private void DisableAllListenables()
|
||||
{
|
||||
RefreshListenablesList();
|
||||
foreach (AudioListenable listenable in _allListenables)
|
||||
{
|
||||
if (listenable != null)
|
||||
listenable.enabled = false;
|
||||
}
|
||||
Debug.Log("Disabled all AudioListenable components");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7093688fd39800645bbe00b6d9a3f27b
|
||||
@@ -0,0 +1,254 @@
|
||||
# Système d'Écoute Diapason - Checklist d'Implémentation
|
||||
|
||||
## ✅ Scripts Créés (Compilés sans erreur)
|
||||
|
||||
### Core System
|
||||
- [x] **DiapasonListeningHandler.cs** - Gestionnaire principal du mode écoute
|
||||
- [x] **AudioListenable.cs** - Composant pour objets écoutables
|
||||
- [x] **ListeningVignetteEffect.cs** - Effet visuel de vignette
|
||||
- [x] **AudioEmitterData.cs** - ScriptableObject pour configuration
|
||||
|
||||
### Extensions
|
||||
- [x] **AudioListenableEvent.cs** - Événements UnityEvent pour objets écoutables
|
||||
- [x] **ThroughWallListener.cs** - Détection d'objets derrière les murs
|
||||
- [x] **DiapasonDebugger.cs** - Outils de debugging
|
||||
|
||||
### Editor Tools
|
||||
- [x] **AudioListenableEditor.cs** - Custom Inspector avec Quick Setup
|
||||
- [x] **AudioListenableBatchSetup.cs** - Configuration en masse via EditorWindow
|
||||
|
||||
### Data
|
||||
- [x] **9 Présets AudioEmitterData** créés dans `/Assets/Unheimlich_POC/Datas/AudioEmitters/`:
|
||||
- WallEmitter.asset
|
||||
- MetalEmitter.asset
|
||||
- WoodEmitter.asset
|
||||
- GlassEmitter.asset
|
||||
- StoneEmitter.asset
|
||||
- MechanicalEmitter.asset
|
||||
- ElectricalEmitter.asset
|
||||
- WaterEmitter.asset
|
||||
- OrganicEmitter.asset
|
||||
|
||||
### Documentation
|
||||
- [x] **README.md** - Architecture et documentation technique
|
||||
- [x] **Page Système d'Écoute - Diapason** - Guide complet utilisateur
|
||||
|
||||
## ⚠️ Configuration Manuelle Requise
|
||||
|
||||
### 1. Input System Setup
|
||||
**Fichier**: `/Assets/Unheimlich_POC/Inputs/PlayerController.inputactions`
|
||||
|
||||
**Actions à faire**:
|
||||
1. Ouvrir le fichier dans l'Input System window
|
||||
2. Dans l'Action Map "Player", ajouter une nouvelle action:
|
||||
- Nom: `Diapason`
|
||||
- Action Type: `Button`
|
||||
- Control Type: `Button`
|
||||
|
||||
3. Ajouter les bindings:
|
||||
```
|
||||
PS5 Controller: Triangle (Button South)
|
||||
Xbox Controller: Y (Button North)
|
||||
Keyboard: T (ou touche de votre choix)
|
||||
```
|
||||
|
||||
4. Sauvegarder et générer la classe C#
|
||||
|
||||
### 2. PlayerInputManager Configuration
|
||||
**GameObject**: `/GAMEPLAY/Player`
|
||||
|
||||
**Dans l'Inspector du PlayerInputManager**:
|
||||
1. Trouver le champ `Diapason Action`
|
||||
2. Assigner l'action "Diapason" depuis le PlayerController.inputactions
|
||||
3. Vérifier que la callback `OnDiapason` est bien configurée
|
||||
|
||||
### 3. DiapasonListeningHandler Setup
|
||||
**GameObject**: `/GAMEPLAY/Player`
|
||||
|
||||
**Actions**:
|
||||
1. Ajouter le composant `DiapasonListeningHandler`
|
||||
2. Assigner les références:
|
||||
- **Diapason Object**: Glisser `/GAMEPLAY/Player/Diapason`
|
||||
- **Diapason Animator**: Glisser l'Animator du Diapason
|
||||
- **Player Camera**: Glisser `/GAMEPLAY/Player/CameraPivot/Main Camera`
|
||||
- **Diapason Action**: Assigner l'InputActionReference vers l'action Diapason
|
||||
|
||||
3. Configurer les paramètres:
|
||||
```
|
||||
Listening Range: 5.0
|
||||
Listenable Layers: Interactable (ou créer un layer "Listenable")
|
||||
Concentration Duration: 1.0
|
||||
Breath In Duration: 0.5
|
||||
Camera Pan Speed: 2.0
|
||||
Camera Pan Border Size: 0.1
|
||||
Max Camera Pan Angle: 15.0
|
||||
Vignette Intensity: 0.5
|
||||
Ambient Attenuation Amount: 0.3
|
||||
```
|
||||
|
||||
### 4. Animator du Diapason
|
||||
**GameObject**: `/GAMEPLAY/Player/Diapason`
|
||||
|
||||
**Parameters requis**:
|
||||
1. Ouvrir l'Animator Controller
|
||||
2. Ajouter les parameters:
|
||||
- `IsEquipped` (Bool)
|
||||
- `Tap` (Trigger)
|
||||
|
||||
3. Créer les animations:
|
||||
- **Idle**: Animation de repos (peut être vide)
|
||||
- **Tap**: Animation de frappe du diapason
|
||||
|
||||
4. Setup des transitions:
|
||||
- Any State → Tap (Condition: Trigger Tap)
|
||||
- Tap → Idle (Exit Time)
|
||||
|
||||
### 5. Audio Clips
|
||||
**À créer ou importer**:
|
||||
|
||||
1. **Breath In Sound** (_breathInClip):
|
||||
- Son d'inspiration profonde
|
||||
- Durée: ~0.5-1 seconde
|
||||
- Format: WAV ou MP3
|
||||
|
||||
2. **Diapason Tap Sound** (_diapasonTapClip):
|
||||
- Son métallique de diapason
|
||||
- Durée: ~0.3-0.5 seconde
|
||||
- Légère résonance
|
||||
|
||||
**Où les assigner**:
|
||||
- Dans le composant `DiapasonListeningHandler` sur le Player
|
||||
|
||||
### 6. Layer Configuration (Optionnel mais Recommandé)
|
||||
**Project Settings → Tags and Layers**
|
||||
|
||||
1. Créer un nouveau layer: `Listenable`
|
||||
2. Dans `DiapasonListeningHandler`, assigner ce layer
|
||||
3. Tous les objets avec `AudioListenable` devraient utiliser ce layer
|
||||
|
||||
Ou réutiliser le layer existant `Interactable`.
|
||||
|
||||
### 7. Vignette Effect (Optionnel)
|
||||
**GameObject**: `/ENVIRONMENT/Global Volume` (ou créer)
|
||||
|
||||
**Si vous voulez l'effet de vignette**:
|
||||
1. S'assurer qu'un Global Volume existe avec URP
|
||||
2. Ajouter le composant `ListeningVignetteEffect` au Player
|
||||
3. Assigner le Global Volume
|
||||
4. Dans le DiapasonListeningHandler, assigner la référence `Vignette Effect`
|
||||
|
||||
## 🎯 Test Initial
|
||||
|
||||
### Test Rapide du Système
|
||||
|
||||
1. **Enter Play Mode**
|
||||
2. Appuyer sur **T** (ou Triangle/Y sur manette)
|
||||
3. **Observer**:
|
||||
- Phase de concentration (~1 seconde)
|
||||
- Curseur apparaît
|
||||
- Le diapason devient visible
|
||||
4. **Pointer** vers un objet avec AudioListenable
|
||||
5. **Clic gauche** pour taper
|
||||
6. **Écouter** le son généré
|
||||
7. **Appuyer à nouveau sur T** pour sortir
|
||||
|
||||
### Ajout d'Objets Écoutables pour Test
|
||||
|
||||
**Méthode Rapide**:
|
||||
1. Sélectionner des GameObjects dans la scène (murs, portes, etc.)
|
||||
2. Menu: `Tools → Unheimlich → Audio Listenable Batch Setup`
|
||||
3. Choisir le type (ex: Wall)
|
||||
4. Cliquer "Add to Selected Objects"
|
||||
5. S'assurer que les objets ont un Collider
|
||||
6. Assigner le layer "Listenable" ou "Interactable"
|
||||
|
||||
**Ou utiliser les presets**:
|
||||
- "Setup All Walls in Scene" - Configure tous les objets contenant "wall"
|
||||
- "Setup All Doors in Scene" - Configure tous les objets contenant "door"
|
||||
|
||||
## 📋 Vérification Post-Setup
|
||||
|
||||
### Checklist Finale
|
||||
|
||||
- [ ] L'action "Diapason" existe dans PlayerController.inputactions
|
||||
- [ ] DiapasonAction est assigné dans PlayerInputManager
|
||||
- [ ] DiapasonListeningHandler est sur le Player avec toutes les références
|
||||
- [ ] L'Animator du diapason a les parameters IsEquipped et Tap
|
||||
- [ ] Au moins un objet dans la scène a AudioListenable
|
||||
- [ ] Les objets écoutables ont un Collider
|
||||
- [ ] Les objets écoutables sont sur le bon layer
|
||||
- [ ] Les AudioClips sont assignés (ou commentés pour test)
|
||||
- [ ] Aucune erreur de compilation
|
||||
|
||||
### Test Avancé
|
||||
|
||||
1. **Test Audio Procédural**: Vérifier différents types de matériaux
|
||||
2. **Test Vibrations**: Tester avec une manette connectée
|
||||
3. **Test Distance**: S'éloigner pour vérifier l'atténuation
|
||||
4. **Test Camera Pan**: Bouger le curseur sur les bords
|
||||
5. **Test Through Wall**: Si implémenté, tester ThroughWallListener
|
||||
|
||||
## 🛠️ Outils Disponibles
|
||||
|
||||
### Menu Tools
|
||||
`Tools → Unheimlich → Audio Listenable Batch Setup`
|
||||
- Configuration en masse d'objets écoutables
|
||||
- Presets automatiques par nom
|
||||
|
||||
### Custom Inspector
|
||||
Sur un GameObject avec `AudioListenable`:
|
||||
- Boutons Quick Setup par type de matériau
|
||||
- Test en Play Mode
|
||||
|
||||
### Debug
|
||||
Ajouter `DiapasonDebugger` au Player ou un GameObject de scene:
|
||||
- Affichage info système en temps réel
|
||||
- Liste des objets écoutables
|
||||
- Gizmos visuels
|
||||
- Context menu pour enable/disable all
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
### Guides Disponibles
|
||||
- **Système d'Écoute - Diapason** (Page): Guide complet utilisateur
|
||||
- **README.md** (Scripts/Audio): Architecture technique
|
||||
- **IMPLEMENTATION_CHECKLIST.md** (ce fichier): Checklist de setup
|
||||
|
||||
### Exemples de Code
|
||||
Voir les scripts d'exemple:
|
||||
- `AudioListenableEvent.cs` - Pour ajouter des événements
|
||||
- `ThroughWallListener.cs` - Pour écouter à travers les murs
|
||||
|
||||
## ⚡ Quick Start (Minimum Viable)
|
||||
|
||||
**Pour tester rapidement sans tout configurer**:
|
||||
|
||||
1. Ajouter `DiapasonListeningHandler` au Player
|
||||
2. Créer une action Input "Diapason" binding sur T
|
||||
3. Assigner les 3 références principales (Diapason, Animator, Camera)
|
||||
4. Ajouter `AudioListenable` sur quelques objets
|
||||
5. Play et appuyer sur T
|
||||
|
||||
**Note**: Sans les AudioClips, le système fonctionne mais sans les sons de respiration et de tap.
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
**Rien ne se passe quand j'appuie sur T**:
|
||||
- Vérifier l'action Input existe et est assignée
|
||||
- Vérifier que le Player n'est pas dans un autre état qui bloque les inputs
|
||||
- Regarder la Console pour des erreurs
|
||||
|
||||
**Pas de son lors du tap**:
|
||||
- Vérifier que l'objet ciblé a AudioListenable
|
||||
- Vérifier le layer de l'objet
|
||||
- Vérifier la distance (max 5m par défaut)
|
||||
- Vérifier que l'objet a un Collider
|
||||
|
||||
**La caméra ne bouge pas**:
|
||||
- Vérifier Camera Pan Border Size > 0
|
||||
- Vérifier que Player Camera est assigné
|
||||
- Bouger le curseur tout au bord de l'écran
|
||||
|
||||
## ✨ C'est Prêt!
|
||||
|
||||
Le système est entièrement implémenté et prêt à l'emploi. Il suffit de suivre les étapes de configuration manuelle ci-dessus et vous pourrez commencer à tester immédiatement!
|
||||
@@ -0,0 +1,257 @@
|
||||
# Audio Listening System - Architecture
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le système d'écoute permet au joueur d'utiliser un diapason pour "écouter" les objets et découvrir des informations cachées à travers les sons et vibrations procéduraux.
|
||||
|
||||
## Structure des Fichiers
|
||||
|
||||
### Core Scripts
|
||||
|
||||
**AudioListenable.cs**
|
||||
- Composant principal pour les objets écoutables
|
||||
- Génération audio procédurale basée sur le type de matériau
|
||||
- Gestion des vibrations de manette
|
||||
- Support pour audio statique custom
|
||||
|
||||
**DiapasonListeningHandler.cs**
|
||||
- Gestionnaire central du mode écoute
|
||||
- Gère l'état de concentration du joueur
|
||||
- Contrôle la caméra avec pan et curseur
|
||||
- Raycast vers les objets écoutables
|
||||
- Atténuation sonore ambiante
|
||||
|
||||
**ListeningVignetteEffect.cs**
|
||||
- Effet visuel de vignette pour l'état d'écoute
|
||||
- S'intègre avec URP Volume system
|
||||
- Transition smooth entre états
|
||||
|
||||
### Data Scripts
|
||||
|
||||
**AudioEmitterData.cs**
|
||||
- ScriptableObject pour configurer les types d'émetteurs
|
||||
- Paramètres procéduraux (fréquences, harmoniques)
|
||||
- Settings de vibration
|
||||
- Courbes d'atténuation
|
||||
|
||||
### Extended Features
|
||||
|
||||
**AudioListenableEvent.cs**
|
||||
- Ajoute des événements Unity aux objets écoutables
|
||||
- Support pour trigger une seule fois
|
||||
- Événements: OnListeningStarted, OnListeningStopped, OnFirstListen
|
||||
|
||||
**ThroughWallListener.cs**
|
||||
- Permet de détecter des objets derrière un mur
|
||||
- Feedback audio différent selon ce qui est détecté
|
||||
- Peut highlight des objets importants
|
||||
|
||||
**DiapasonDebugger.cs**
|
||||
- Outil de debugging pour le système
|
||||
- Affiche l'état du système en temps réel
|
||||
- Liste tous les objets écoutables
|
||||
- Gizmos visuels dans la Scene
|
||||
|
||||
### Editor Scripts
|
||||
|
||||
**AudioListenableEditor.cs**
|
||||
- Custom Inspector pour AudioListenable
|
||||
- Boutons Quick Setup par type de matériau
|
||||
- Test en Play Mode
|
||||
|
||||
**AudioListenableBatchSetup.cs**
|
||||
- EditorWindow pour configuration en masse
|
||||
- Setup par sélection
|
||||
- Setup par nom d'objet
|
||||
- Presets automatiques (walls, doors, etc.)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Player
|
||||
├── DiapasonListeningHandler (Manager)
|
||||
│ ├── Input Handling
|
||||
│ ├── State Management (Concentrating/Listening)
|
||||
│ ├── Camera Control
|
||||
│ └── Raycast Detection
|
||||
│
|
||||
├── ListeningVignetteEffect (Visual)
|
||||
│ └── URP Volume Integration
|
||||
│
|
||||
└── Audio Source (Player sounds)
|
||||
|
||||
Scene Objects
|
||||
├── AudioListenable (Core)
|
||||
│ ├── Procedural Audio Generation
|
||||
│ ├── Vibration System
|
||||
│ └── Distance Attenuation
|
||||
│
|
||||
├── AudioListenableEvent (Optional)
|
||||
│ └── UnityEvent Triggers
|
||||
│
|
||||
└── ThroughWallListener (Optional)
|
||||
└── Behind-Wall Detection
|
||||
```
|
||||
|
||||
## Workflow de Fonctionnement
|
||||
|
||||
1. **Activation** (Triangle/Y)
|
||||
- DiapasonListeningHandler.StartListeningMode()
|
||||
- Désactive les inputs de mouvement
|
||||
- Active l'objet diapason
|
||||
|
||||
2. **Phase de Concentration**
|
||||
- Joue le son de respiration
|
||||
- Attend la durée de concentration
|
||||
- Prépare le mode écoute
|
||||
|
||||
3. **Mode Écoute Actif**
|
||||
- Curseur souris visible
|
||||
- Camera pan sur les bords
|
||||
- Raycast continu vers objets
|
||||
- Vignette activée
|
||||
- Atténuation audio ambiante
|
||||
|
||||
4. **Interaction avec Objet**
|
||||
- Clic gauche sur objet écoutable
|
||||
- Animation du diapason (Tap trigger)
|
||||
- AudioListenable.StartListening()
|
||||
- Génération du son procédural
|
||||
- Activation des vibrations
|
||||
|
||||
5. **Désactivation** (Triangle/Y à nouveau)
|
||||
- Arrêt de l'écoute
|
||||
- Restauration des inputs
|
||||
- Désactivation vignette
|
||||
- Restauration audio ambiant
|
||||
|
||||
## Types d'Émetteurs
|
||||
|
||||
| Type | Fréquence | Usage |
|
||||
|------|-----------|-------|
|
||||
| Generic | 440 Hz | Objets par défaut |
|
||||
| Wall | 200 Hz | Murs, surfaces plates |
|
||||
| Wood | 300 Hz | Portes, meubles en bois |
|
||||
| Metal | 800 Hz | Objets métalliques |
|
||||
| Glass | 1200 Hz | Vitres, cristal |
|
||||
| Water | 220 Hz | Eau, liquides |
|
||||
| Stone | 250 Hz | Pierre, roche |
|
||||
| Mechanical | 500 Hz | Machines, mécanismes |
|
||||
| Electrical | 440 Hz | Équipements électriques |
|
||||
| Organic | 350 Hz | Matériaux vivants |
|
||||
|
||||
## Génération Audio Procédurale
|
||||
|
||||
### Algorithme de Base
|
||||
|
||||
```csharp
|
||||
// Pour chaque sample
|
||||
float sample = 0;
|
||||
|
||||
// Ajoute les harmoniques
|
||||
for (int h = 0; h < harmonicCount; h++)
|
||||
{
|
||||
float harmFreq = baseFreq * harmonicMultipliers[h];
|
||||
float harmAmp = harmonicAmplitudes[h];
|
||||
sample += Sin(2π * harmFreq * time) * harmAmp;
|
||||
}
|
||||
|
||||
// Ajoute le bruit
|
||||
sample += (Random() - 0.5) * noiseAmount;
|
||||
|
||||
// Applique l'envelope
|
||||
sample *= Sin(π * time / duration);
|
||||
```
|
||||
|
||||
### Caractéristiques par Type
|
||||
|
||||
**Metal**: Multiples harmoniques (2.1x, 3.8x, 5.2x) pour son métallique
|
||||
**Wood**: Peu d'harmoniques + bruit pour texture
|
||||
**Glass**: Harmoniques aiguës (1.5x, 2.7x, 4.2x)
|
||||
**Water**: Bruit dominant avec légère oscillation
|
||||
**Stone**: Bruit granuleux avec harmonique grave
|
||||
|
||||
## Système de Vibrations
|
||||
|
||||
### Intensité Basée sur Distance
|
||||
|
||||
```csharp
|
||||
intensity = Lerp(minIntensity, maxIntensity, 1 - distance/maxDistance)
|
||||
```
|
||||
|
||||
### Pattern de Vibration
|
||||
|
||||
```csharp
|
||||
pattern = |Sin(time * frequency)|
|
||||
finalIntensity = intensity * pattern
|
||||
```
|
||||
|
||||
### Contrôles Gamepad
|
||||
|
||||
- Utilise `Gamepad.current.SetMotorSpeeds(low, high)`
|
||||
- Arrêt automatique à la fin de l'écoute
|
||||
- Arrêt au OnDisable du composant
|
||||
|
||||
## Intégration avec d'Autres Systèmes
|
||||
|
||||
### Input System
|
||||
- Utilise PlayerInputManager existant
|
||||
- Action "Diapason" ajoutée
|
||||
- Compatible avec système de toggle input
|
||||
|
||||
### Audio System
|
||||
- Un AudioSource sur le joueur pour sons système
|
||||
- AudioSource sur chaque AudioListenable
|
||||
- Spatial audio 3D
|
||||
- Custom attenuation curves
|
||||
|
||||
### Camera System
|
||||
- Contrôle temporaire de la caméra
|
||||
- Pan basé sur viewport position
|
||||
- Retour à l'état normal à la sortie
|
||||
|
||||
## Performance
|
||||
|
||||
### Optimisations
|
||||
- Génération audio on-demand (pas de pré-calcul)
|
||||
- AudioClips courts (0.5-2s)
|
||||
- Un seul objet écouté à la fois
|
||||
- Raycast avec LayerMask
|
||||
- Pas de Update() quand inactif
|
||||
|
||||
### Considérations
|
||||
- Génération procédurale légère
|
||||
- Vibrations: vérification gamepad chaque frame
|
||||
- Distance checks optimisés avec DistanceSqr
|
||||
|
||||
## Extension Future
|
||||
|
||||
### AudioEmitterData Integration
|
||||
Actuellement les presets existent mais ne sont pas utilisés par AudioListenable. Pour les intégrer:
|
||||
|
||||
1. Ajouter champ `AudioEmitterData` dans AudioListenable
|
||||
2. Loader les paramètres depuis le ScriptableObject
|
||||
3. Permettre override des paramètres individuels
|
||||
|
||||
### Visualisation Sonore
|
||||
- Particle systems pour ondes sonores
|
||||
- Shader effects sur objets écoutés
|
||||
- Indicateurs visuels pour objets importants
|
||||
|
||||
### Audio Avancé
|
||||
- Réverbération basée sur l'environnement
|
||||
- Occlusion sonore
|
||||
- Support multi-canal
|
||||
|
||||
## Troubleshooting Courant
|
||||
|
||||
**Pas de son**: Vérifier AudioSource, layer, distance
|
||||
**Pas de vibration**: Vérifier gamepad connecté
|
||||
**Camera ne bouge pas**: Vérifier border size, références camera
|
||||
**Input ne répond pas**: Vérifier action assignée dans inputactions
|
||||
|
||||
## Références
|
||||
|
||||
- Unity Input System: com.unity.inputsystem 1.17.0
|
||||
- URP: com.unity.render-pipelines.universal 17.3.0
|
||||
- Unity 6000.3 APIs
|
||||
@@ -0,0 +1,102 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fad34f7be8a64242984b14410478690
|
||||
Reference in New Issue
Block a user