Fully functional diapason + upgrade input manager

This commit is contained in:
2026-01-30 02:23:06 +01:00
parent dcac15b3f2
commit 9ff17b5305
15 changed files with 1352 additions and 1698 deletions
@@ -6,15 +6,28 @@ using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Gameplay.Player
{
/// <summary>
/// Manages the tuning fork (diapason) listening mechanic.
/// Handles:
/// - Equipping/unequipping the diapason with tween animations
/// - Concentration phase (breathing sound and duration)
/// - Listening mode with camera pan control
/// - Tapping the diapason on listenable objects
/// - Visual feedback (vignette, cursor, particles)
/// - Preventing multiple taps on the same emitter
/// </summary>
public class DiapasonListeningHandler : MonoBehaviour
{
// Animator parameter names for diapason animations
private const string ANIM_TRIGGER_TAP = "Tap";
private const string ANIM_BOOL_VIBRATING = "IsVibrating";
public static DiapasonListeningHandler Instance { get; private set; }
[Header("References")]
[SerializeField] private GameObject _diapasonObject;
[SerializeField] private Animator _diapasonAnimator;
[SerializeField] private Camera _playerCamera;
[SerializeField] private Transform _cursorTransform;
[Header("Listening Settings")]
[SerializeField] private float _listeningRange = 5f;
@@ -35,13 +48,19 @@ namespace Unheimlich.Gameplay.Player
[Header("Visual Feedback")]
[SerializeField] private float _vignetteIntensity = 0.5f;
[SerializeField] private ListeningVignetteEffect _vignetteEffect;
[SerializeField] private GameObject _handTouchFeedback;
[SerializeField] private ParticleSystem _diapasonParticles;
[Header("Audio")]
[SerializeField] private AudioClip _breathInClip;
[SerializeField] private AudioClip _diapasonTapClip;
[SerializeField] [Range(0f, 1f)] private float _diapasonTapVolume = 1f;
[SerializeField] private float _ambientAttenuationAmount = 0.3f;
[SerializeField] private float _audioFadeDuration = 0.5f;
[Header("Touch Settings")]
[SerializeField] private float _touchDistance = 2f;
[Header("Input Action")]
[SerializeField] private InputActionReference _diapasonAction;
[SerializeField] private InputActionReference _lookAction;
@@ -59,10 +78,7 @@ namespace Unheimlich.Gameplay.Player
private float _originalAmbientVolume;
private Vector3 _diapasonEquippedPosition;
private Vector3 _diapasonHiddenPosition;
private bool _movementDisabled;
private const string ANIM_TRIGGER_TAP = "Tap";
private const string ANIM_BOOL_EQUIPPED = "IsEquipped";
private bool _canUseDiapason;
private void Awake()
{
@@ -80,40 +96,125 @@ namespace Unheimlich.Gameplay.Player
if (_diapasonObject != null)
{
_diapasonEquippedPosition = _diapasonObject.transform.localPosition;
_diapasonHiddenPosition = _diapasonEquippedPosition + Vector3.up * _diapasonHideOffset;
_diapasonHiddenPosition = _diapasonEquippedPosition + Vector3.down * Mathf.Abs(_diapasonHideOffset);
if (_diapasonAnimator != null)
_diapasonAnimator.enabled = false;
_diapasonObject.transform.localPosition = _diapasonHiddenPosition;
_diapasonObject.SetActive(false);
}
if (_diapasonParticles != null)
{
_diapasonParticles.Stop();
var emission = _diapasonParticles.emission;
emission.enabled = false;
}
if (_handTouchFeedback != null)
{
_handTouchFeedback.SetActive(false);
}
if (_diapasonAction == null)
{
Debug.LogError("[Diapason] _diapasonAction is NOT assigned! Please assign it in the Inspector.");
}
else
{
Debug.Log($"[Diapason] _diapasonAction assigned: {_diapasonAction.action.name}");
}
}
private void Update()
{
if (!IsListening && !IsConcentrating)
{
CheckForNearbyEmitter();
}
if (_diapasonAction != null && _diapasonAction.action.WasPressedThisFrame())
{
Debug.Log($"[Diapason] Action pressed! IsListening={IsListening}, IsConcentrating={IsConcentrating}");
if (!IsListening && !IsConcentrating)
if (!IsListening && !IsConcentrating && _canUseDiapason)
{
StartListeningMode();
}
else if (IsListening || IsConcentrating)
{
StopListeningMode();
}
}
if (IsListening)
{
UpdateCursorAndCameraPan();
CheckForListenableObject();
Debug.Log("Listening");
}
}
private void CheckForNearbyEmitter()
{
if (_playerCamera == null)
return;
Ray ray = _playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
if (Physics.Raycast(ray, out RaycastHit hit, _listeningRange, _listenableLayers))
{
AudioListenable listenable = hit.collider.GetComponent<AudioListenable>();
if (listenable != null)
{
if (_currentTargetEmitter != listenable)
{
if (_currentTargetEmitter != null && (IsConcentrating || IsListening))
_currentTargetEmitter.SetConcentrating(false);
_currentTargetEmitter = listenable;
}
float distance = hit.distance;
bool isInTouchRange = distance <= _touchDistance;
if (isInTouchRange && !_canUseDiapason)
{
_canUseDiapason = true;
if (_handTouchFeedback != null)
_handTouchFeedback.SetActive(true);
}
else if (!isInTouchRange && _canUseDiapason)
{
_canUseDiapason = false;
if (_handTouchFeedback != null)
_handTouchFeedback.SetActive(false);
}
}
else
{
if (_currentTargetEmitter != null && (IsConcentrating || IsListening))
{
_currentTargetEmitter.SetConcentrating(false);
_currentTargetEmitter = null;
}
_canUseDiapason = false;
if (_handTouchFeedback != null)
_handTouchFeedback.SetActive(false);
}
}
else
{
if (_currentTargetEmitter != null && (IsConcentrating || IsListening))
{
_currentTargetEmitter.SetConcentrating(false);
_currentTargetEmitter = null;
}
_canUseDiapason = false;
if (_handTouchFeedback != null)
_handTouchFeedback.SetActive(false);
}
}
/// <summary>
/// Initiates the listening mode sequence.
/// </summary>
private void StartListeningMode()
{
if (_listeningCoroutine != null)
@@ -122,47 +223,64 @@ namespace Unheimlich.Gameplay.Player
_listeningCoroutine = StartCoroutine(ListeningSequence());
}
/// <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
/// </summary>
private IEnumerator ListeningSequence()
{
Debug.Log("[Diapason] ListeningSequence START");
IsConcentrating = true;
DisablePlayerMovement(true);
InputManager.SetContext(InputManager.InputContext.Listening);
if (_currentTargetEmitter != null)
_currentTargetEmitter.SetConcentrating(true);
if (_diapasonObject != null)
{
_diapasonObject.SetActive(true);
if (_diapasonAnimator != null)
_diapasonAnimator.SetBool(ANIM_BOOL_EQUIPPED, true);
if (_tweenCoroutine != null)
StopCoroutine(_tweenCoroutine);
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonEquippedPosition, _diapasonTweenDuration));
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonEquippedPosition, _diapasonTweenDuration, () =>
{
if (_diapasonAnimator != null)
{
_diapasonAnimator.enabled = true;
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
}
}));
}
Debug.Log($"[Diapason] Playing breath in, waiting {_breathInDuration}s");
PlayBreathIn();
yield return new WaitForSeconds(_breathInDuration);
Debug.Log($"[Diapason] Concentrating, waiting {_concentrationDuration - _breathInDuration}s");
yield return new WaitForSeconds(_concentrationDuration - _breathInDuration);
Debug.Log("[Diapason] Setting IsListening = TRUE");
IsConcentrating = false;
IsListening = true;
EnableVignette(true);
float currentPitch = _playerCamera.transform.localEulerAngles.x;
if (currentPitch > 180f)
currentPitch -= 360f;
_originalCameraRotation = new Vector2(transform.eulerAngles.y, currentPitch);
AttenuateAmbientSound(true);
EnableVignette(true);
Cursor.lockState = CursorLockMode.Confined;
Cursor.visible = false;
Debug.Log("[Diapason] ListeningSequence COMPLETE");
}
/// <summary>
/// Stops the listening mode and returns to normal state:
/// - Stops vibration animation
/// - Tweens diapason back to hidden position
/// - Stops emitter listening
/// - Hides vignette and cursor
/// - Re-enables player movement
/// </summary>
private void StopListeningMode()
{
if (_listeningCoroutine != null)
@@ -184,28 +302,46 @@ namespace Unheimlich.Gameplay.Player
if (_diapasonObject != null)
{
if (_diapasonAnimator != null)
_diapasonAnimator.SetBool(ANIM_BOOL_EQUIPPED, false);
{
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
_diapasonAnimator.enabled = false;
}
if (_tweenCoroutine != null)
StopCoroutine(_tweenCoroutine);
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonHiddenPosition, _diapasonTweenDuration, () => _diapasonObject.SetActive(false)));
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonHiddenPosition, _diapasonTweenDuration));
}
if (_diapasonParticles != null)
{
_diapasonParticles.Stop();
var emission = _diapasonParticles.emission;
emission.enabled = false;
}
if (_handTouchFeedback != null)
{
_handTouchFeedback.SetActive(false);
}
AttenuateAmbientSound(false);
EnableVignette(false);
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
DisablePlayerMovement(false);
InputManager.SetContext(InputManager.InputContext.Normal);
if (_currentTargetEmitter != null)
{
_currentTargetEmitter.StopListening();
_currentTargetEmitter = null;
}
_canUseDiapason = false;
}
/// <summary>
/// Updates cursor position and camera panning during listening mode.
/// Supports both gamepad (look input) and mouse (screen edges).
/// </summary>
private void UpdateCursorAndCameraPan()
{
if (_playerCamera == null)
@@ -257,6 +393,10 @@ namespace Unheimlich.Gameplay.Player
_playerCamera.transform.localEulerAngles = cameraRotation;
}
/// <summary>
/// Raycasts from camera center to detect listenable objects.
/// Handles tap input and prevents tapping the same emitter multiple times.
/// </summary>
private void CheckForListenableObject()
{
if (_playerCamera == null)
@@ -266,48 +406,46 @@ namespace Unheimlich.Gameplay.Player
if (Physics.Raycast(ray, out RaycastHit hit, _listeningRange, _listenableLayers))
{
Debug.Log($"Raycast hit: {hit.collider.gameObject.name}");
AudioListenable listenable = hit.collider.GetComponent<AudioListenable>();
if (listenable != null)
{
Debug.Log($"AudioListenable found on {hit.collider.gameObject.name}!");
if (listenable != _currentTargetEmitter)
{
if (_currentTargetEmitter != null)
if (_currentTargetEmitter != null && !_currentTargetEmitter.HasBeenTapped)
_currentTargetEmitter.StopListening();
_currentTargetEmitter = listenable;
Debug.Log($"_currentTargetEmitter set to {hit.collider.gameObject.name}");
}
bool firePressed = false;
if (_fireAction != null && _fireAction.action != null && _fireAction.action.WasPressedThisFrame())
{
firePressed = true;
}
else if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
{
firePressed = true;
}
// Only allow tap if emitter hasn't been tapped yet
if (firePressed && _currentTargetEmitter != null && !_currentTargetEmitter.HasBeenTapped)
{
TriggerDiapasonTap();
}
}
else
{
Debug.LogWarning($"Raycast hit {hit.collider.gameObject.name} but NO AudioListenable found!");
}
bool firePressed = false;
if (_fireAction != null && _fireAction.action != null && _fireAction.action.WasPressedThisFrame())
{
firePressed = true;
}
else if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
{
firePressed = true;
}
if (firePressed)
{
Debug.Log($"Fire/Click detected! _currentTargetEmitter = {(_currentTargetEmitter != null ? _currentTargetEmitter.gameObject.name : "NULL")}");
if (_currentTargetEmitter != null)
if (_currentTargetEmitter != null && !_currentTargetEmitter.HasBeenTapped)
{
TriggerDiapasonTap();
_currentTargetEmitter.StopListening();
_currentTargetEmitter = null;
}
}
}
else
{
if (_currentTargetEmitter != null)
if (_currentTargetEmitter != null && !_currentTargetEmitter.HasBeenTapped)
{
_currentTargetEmitter.StopListening();
_currentTargetEmitter = null;
@@ -315,25 +453,35 @@ namespace Unheimlich.Gameplay.Player
}
}
/// <summary>
/// Triggers the diapason tap action:
/// - Plays tap animation and vibration loop
/// - Starts listening sequence on the target emitter
/// - Plays diapason particles
/// - Plays tap sound
/// </summary>
private void TriggerDiapasonTap()
{
Debug.Log("[Diapason] TriggerDiapasonTap called!");
if (_diapasonAnimator != null)
{
_diapasonAnimator.SetTrigger(ANIM_TRIGGER_TAP);
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, true);
}
if (_diapasonTapClip != null && _playerAudioSource != null)
_playerAudioSource.PlayOneShot(_diapasonTapClip);
_playerAudioSource.PlayOneShot(_diapasonTapClip, _diapasonTapVolume);
if (_diapasonParticles != null)
{
var emission = _diapasonParticles.emission;
emission.enabled = true;
_diapasonParticles.Play();
}
if (_currentTargetEmitter != null)
{
Debug.Log($"[Diapason] Starting listening on {_currentTargetEmitter.gameObject.name}");
_currentTargetEmitter.StartListening(transform.position);
}
else
{
Debug.LogWarning("[Diapason] TriggerDiapasonTap called but _currentTargetEmitter is NULL!");
}
}
private void PlayBreathIn()
@@ -341,7 +489,7 @@ namespace Unheimlich.Gameplay.Player
if (_breathInClip != null && _playerAudioSource != null)
_playerAudioSource.PlayOneShot(_breathInClip);
}
#region If needed
private void AttenuateAmbientSound(bool enable)
{
if (_audioFadeCoroutine != null)
@@ -352,6 +500,7 @@ namespace Unheimlich.Gameplay.Player
private IEnumerator FadeAmbientSound(bool fadeOut)
{
//To do : Create audio tracks
AudioListener listener = FindFirstObjectByType<AudioListener>();
if (listener == null)
yield break;
@@ -375,7 +524,7 @@ namespace Unheimlich.Gameplay.Player
AudioListener.volume = targetVolume;
}
#endregion
private IEnumerator FadeOutAudio(AudioSource source, float duration)
{
float startVolume = source.volume;
@@ -419,26 +568,6 @@ namespace Unheimlich.Gameplay.Player
onComplete?.Invoke();
}
private void DisablePlayerMovement(bool disable)
{
if (_movementDisabled == disable)
return;
_movementDisabled = disable;
PlayerMovement playerMovement = GetComponent<PlayerMovement>();
if (playerMovement != null)
playerMovement.enabled = !disable;
PlayerLook playerLook = GetComponent<PlayerLook>();
if (playerLook != null)
playerLook.enabled = !disable;
CameraHeadBob headBob = GetComponentInChildren<CameraHeadBob>();
if (headBob != null)
headBob.enabled = !disable;
}
private void OnDrawGizmosSelected()
{
if (_playerCamera != null && IsListening)
@@ -0,0 +1,42 @@
using UnityEngine;
using UnityEngine.UI;
namespace Unheimlich.Gameplay.Player
{
public class HandTouchFeedback : MonoBehaviour
{
[Header("Visual Settings")]
[SerializeField] private Image _feedbackImage;
[SerializeField] private Color _readyColor = Color.green;
[SerializeField] private float _pulseSpeed = 2f;
[SerializeField] private float _pulseIntensity = 0.3f;
private float _pulseTimer;
private Color _originalColor;
private void Awake()
{
if (_feedbackImage != null)
{
_originalColor = _feedbackImage.color;
}
}
private void OnEnable()
{
_pulseTimer = 0f;
}
private void Update()
{
if (_feedbackImage == null)
return;
_pulseTimer += Time.deltaTime * _pulseSpeed;
float pulse = (Mathf.Sin(_pulseTimer) + 1f) * 0.5f;
Color targetColor = Color.Lerp(_originalColor, _readyColor, pulse * _pulseIntensity);
_feedbackImage.color = targetColor;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 39b78978d656e894ea455342398ef8ba