Fully functional diapason + upgrade input manager
This commit is contained in:
+216
-87
@@ -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
|
||||
Reference in New Issue
Block a user