diff --git a/Assets/Unheimlich_POC/Scenes/MainScene/MainScene.unity b/Assets/Unheimlich_POC/Scenes/MainScene/MainScene.unity
index 5f9e85f7..21f6ed8b 100644
--- a/Assets/Unheimlich_POC/Scenes/MainScene/MainScene.unity
+++ b/Assets/Unheimlich_POC/Scenes/MainScene/MainScene.unity
@@ -2498,6 +2498,10 @@ PrefabInstance:
propertyPath: _diapasonParticles
value:
objectReference: {fileID: 5090177222934034460}
+ - target: {fileID: 3142789394009655648, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
+ propertyPath: _diapasonTapVolume
+ value: 0.3
+ objectReference: {fileID: 0}
- target: {fileID: 3142789394009655648, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: _handTouchFeedback
value:
@@ -2536,7 +2540,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: m_LocalPosition.z
- value: 23.7
+ value: 25.9
objectReference: {fileID: 0}
- target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: m_LocalRotation.w
@@ -2649,8 +2653,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7093688fd39800645bbe00b6d9a3f27b, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Unheimlich.Audio.DiapasonDebugger
- _showDebugInfo: 1
- _showGizmos: 1
+ _showDebugInfo: 0
+ _showGizmos: 0
_listeningRangeColor: {r: 0, g: 1, b: 1, a: 1}
_activeTargetColor: {r: 0, g: 1, b: 0, a: 1}
--- !u!1660057539 &9223372036854775807
diff --git a/Assets/Unheimlich_POC/Scripts/Audio/AudioListenable.cs b/Assets/Unheimlich_POC/Scripts/Audio/AudioListenable.cs
index 45e52d11..23721561 100644
--- a/Assets/Unheimlich_POC/Scripts/Audio/AudioListenable.cs
+++ b/Assets/Unheimlich_POC/Scripts/Audio/AudioListenable.cs
@@ -1,6 +1,7 @@
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
+using Unheimlich.Utility;
namespace Unheimlich.Audio
{
@@ -24,8 +25,8 @@ namespace Unheimlich.Audio
private const string CONTINUOUS_AUDIO_SOURCE_NAME = "Continuous Audio Source";
// Volume multipliers for concentration system
- private const float BASELINE_VOLUME_MULTIPLIER = 0.3f;
- private const float FULL_VOLUME_MULTIPLIER = 1f;
+ private const float BASELINE_VOLUME_MULTIPLIER = 0.1f;
+ private const float FULL_VOLUME_MULTIPLIER = 1.5f;
private const float CONCENTRATION_FADE_DURATION = 1f;
private const float RELEASE_FADE_DURATION = 0.5f;
@@ -78,7 +79,8 @@ namespace Unheimlich.Audio
private AudioClip _continuousClip;
private Coroutine _volumeFadeCoroutine;
private Coroutine _startListeningCoroutine;
- private Coroutine _concentrationFadeCoroutine;
+ private int _concentrationFadeTweenId = -1;
+ private int _crossfadeTweenId = -1;
///
/// Returns true if this emitter has been tapped during the current listening session.
@@ -147,18 +149,34 @@ namespace Unheimlich.Audio
///
public void SetConcentrating(bool concentrating)
{
+ TweenHelper.CancelTween(ref _concentrationFadeTweenId);
- if (_concentrationFadeCoroutine != null)
- StopCoroutine(_concentrationFadeCoroutine);
+ if (_continuousAudioSource == null)
+ return;
- if (concentrating)
- {
- _concentrationFadeCoroutine = StartCoroutine(FadeConcentrationVolume(BASELINE_VOLUME_MULTIPLIER, FULL_VOLUME_MULTIPLIER, CONCENTRATION_FADE_DURATION));
- }
- else
- {
- _concentrationFadeCoroutine = StartCoroutine(FadeConcentrationVolume(FULL_VOLUME_MULTIPLIER, BASELINE_VOLUME_MULTIPLIER, RELEASE_FADE_DURATION));
- }
+ float baseVolume = _continuousBaseVolume;
+ float fromMultiplier = concentrating ? BASELINE_VOLUME_MULTIPLIER : FULL_VOLUME_MULTIPLIER;
+ float toMultiplier = concentrating ? FULL_VOLUME_MULTIPLIER : BASELINE_VOLUME_MULTIPLIER;
+ float duration = concentrating ? CONCENTRATION_FADE_DURATION : RELEASE_FADE_DURATION;
+
+ float fromVolume = baseVolume * fromMultiplier;
+ float toVolume = baseVolume * toMultiplier;
+
+ _concentrationFadeTweenId = TweenHelper.FadeVolume(
+ _continuousAudioSource,
+ toVolume,
+ duration);
+ }
+
+ public void ResetConcentrationVolume()
+ {
+ TweenHelper.CancelTween(ref _concentrationFadeTweenId);
+
+ if (_continuousAudioSource == null)
+ return;
+
+ float baselineVolume = _continuousBaseVolume * BASELINE_VOLUME_MULTIPLIER;
+ _continuousAudioSource.volume = baselineVolume;
}
///
@@ -197,8 +215,22 @@ namespace Unheimlich.Audio
if (_volumeFadeCoroutine != null)
StopCoroutine(_volumeFadeCoroutine);
+ TweenHelper.CancelTween(ref _crossfadeTweenId);
+
float crossfadeDuration = 1f;
- _volumeFadeCoroutine = StartCoroutine(CrossfadeSounds(crossfadeDuration));
+
+ if (_continuousAudioSource != null)
+ {
+ _crossfadeTweenId = TweenHelper.FadeVolume(
+ _continuousAudioSource,
+ 0f,
+ crossfadeDuration,
+ () =>
+ {
+ if (_continuousAudioSource != null)
+ _continuousAudioSource.Stop();
+ });
+ }
if (_emitterParticles != null)
{
@@ -231,7 +263,7 @@ namespace Unheimlich.Audio
/// This is the only place where _hasBeenTapped is reset to false,
/// allowing the emitter to be used again in a future listening session.
///
- public void StopListening()
+ public void StopListening(bool cancelConcentrationFade = true)
{
bool wasBeenTapped = _hasBeenTapped;
@@ -244,8 +276,8 @@ namespace Unheimlich.Audio
if (_startListeningCoroutine != null)
StopCoroutine(_startListeningCoroutine);
- if (_concentrationFadeCoroutine != null)
- StopCoroutine(_concentrationFadeCoroutine);
+ if (cancelConcentrationFade)
+ TweenHelper.CancelTween(ref _concentrationFadeTweenId);
if (_audioSource != null && _audioSource.isPlaying)
_audioSource.Stop();
@@ -400,59 +432,6 @@ namespace Unheimlich.Audio
Gamepad.current.SetMotorSpeeds(intensity, intensity);
}
- private IEnumerator FadeConcentrationVolume(float fromMultiplier, float toMultiplier, float duration)
- {
- float elapsed = 0f;
- float baseVolume = _continuousBaseVolume;
-
- while (elapsed < duration)
- {
- elapsed += Time.deltaTime;
- float t = elapsed / duration;
- float currentMultiplier = Mathf.Lerp(fromMultiplier, toMultiplier, t);
-
- if (_continuousAudioSource != null)
- {
- _continuousAudioSource.volume = baseVolume * currentMultiplier;
- }
-
- yield return null;
- }
-
- if (_continuousAudioSource != null)
- {
- _continuousAudioSource.volume = baseVolume * toMultiplier;
- }
- }
-
- private IEnumerator CrossfadeSounds(float fadeDuration)
- {
- if (_continuousAudioSource == null)
- yield break;
-
- float startVolume = _continuousAudioSource.volume;
- float elapsed = 0f;
-
- while (elapsed < fadeDuration)
- {
- elapsed += Time.deltaTime;
- float t = elapsed / fadeDuration;
-
- if (_continuousAudioSource != null)
- {
- _continuousAudioSource.volume = Mathf.Lerp(startVolume, 0f, t);
- }
-
- yield return null;
- }
-
- if (_continuousAudioSource != null)
- {
- _continuousAudioSource.volume = 0f;
- _continuousAudioSource.Stop();
- }
- }
-
private void UpdateVibration()
{
if (!_enableVibration || Gamepad.current == null)
diff --git a/Assets/Unheimlich_POC/Scripts/Audio/DiapasonDebugger.cs b/Assets/Unheimlich_POC/Scripts/Audio/DiapasonDebugger.cs
index 7832c2f3..bbea0318 100644
--- a/Assets/Unheimlich_POC/Scripts/Audio/DiapasonDebugger.cs
+++ b/Assets/Unheimlich_POC/Scripts/Audio/DiapasonDebugger.cs
@@ -50,6 +50,8 @@ namespace Unheimlich.Audio
private void RefreshListenablesList()
{
_allListenables = FindObjectsByType(FindObjectsSortMode.None);
+ if (!_showDebugInfo)
+ return;
Debug.Log($"Found {_allListenables.Length} AudioListenable objects in scene");
}
diff --git a/Assets/Unheimlich_POC/Scripts/Core.meta b/Assets/Unheimlich_POC/Scripts/Core.meta
new file mode 100644
index 00000000..b93f1288
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a469312c19fe2fb40baa7642b1e73dcd
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs b/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs
new file mode 100644
index 00000000..00d01af1
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs
@@ -0,0 +1,33 @@
+using UnityEngine;
+
+namespace Unheimlich.Core
+{
+ public abstract class Singleton : MonoBehaviour where T : MonoBehaviour
+ {
+ public static T Instance { get; private set; }
+
+ protected virtual void Awake()
+ {
+ if (Instance != null && Instance != this)
+ {
+ Debug.LogWarning($"[{typeof(T).Name}] Multiple instances detected! Destroying duplicate on GameObject '{gameObject.name}'.");
+ Destroy(this);
+ return;
+ }
+ Instance = this as T;
+ OnAwake();
+ }
+
+ protected virtual void OnDestroy()
+ {
+ if (Instance == this)
+ {
+ Instance = null;
+ OnDestroyInstance();
+ }
+ }
+
+ protected virtual void OnAwake() { }
+ protected virtual void OnDestroyInstance() { }
+ }
+}
diff --git a/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs.meta b/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs.meta
new file mode 100644
index 00000000..dc09d0af
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Core/Singleton.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 654e642c73c2b954fa86e0caac3f08c1
\ No newline at end of file
diff --git a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Environment/DoorInteractable.cs b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Environment/DoorInteractable.cs
index 4132e147..88b2379d 100644
--- a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Environment/DoorInteractable.cs
+++ b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Environment/DoorInteractable.cs
@@ -1,5 +1,6 @@
using UnityEngine;
using Unheimlich.Interactions.Core;
+using Unheimlich.Utility;
namespace Unheimlich.Gameplay.Environment
{
@@ -29,21 +30,17 @@ namespace Unheimlich.Gameplay.Environment
public void ToggleDoor()
{
- LeanTween.cancel(gameObject);
+ gameObject.CancelAllTweens();
if (!IsOpen)
{
- LeanTween.rotate(gameObject, _openRotation.eulerAngles, _animationDuration)
- .setEase(_easeType);
-
+ TweenHelper.RotateLocal(gameObject, _openRotation.eulerAngles, _animationDuration, null, _easeType);
IsOpen = true;
InteractionText = "Close";
}
else
{
- LeanTween.rotate(gameObject, _closedRotation.eulerAngles, _animationDuration)
- .setEase(_easeType);
-
+ TweenHelper.RotateLocal(gameObject, _closedRotation.eulerAngles, _animationDuration, null, _easeType);
IsOpen = false;
InteractionText = "Open";
}
diff --git a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/DiapasonListeningHandler.cs b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/DiapasonListeningHandler.cs
index 15da7b21..33b56ba3 100644
--- a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/DiapasonListeningHandler.cs
+++ b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/DiapasonListeningHandler.cs
@@ -2,6 +2,8 @@ using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using Unheimlich.Audio;
+using Unheimlich.Core;
+using Unheimlich.Utility;
using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Gameplay.Player
@@ -16,14 +18,11 @@ namespace Unheimlich.Gameplay.Player
/// - Visual feedback (vignette, cursor, particles)
/// - Preventing multiple taps on the same emitter
///
- public class DiapasonListeningHandler : MonoBehaviour
+ public class DiapasonListeningHandler : Singleton
{
- // 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;
@@ -72,23 +71,18 @@ namespace Unheimlich.Gameplay.Player
private AudioListenable _currentTargetEmitter;
private AudioSource _playerAudioSource;
private Coroutine _listeningCoroutine;
- private Coroutine _tweenCoroutine;
- private Coroutine _audioFadeCoroutine;
+ private int _diapasonTweenId = -1;
+ private int _audioListenerFadeTweenId = -1;
+ private int _playerAudioFadeTweenId = -1;
private Vector2 _originalCameraRotation;
private float _originalAmbientVolume;
private Vector3 _diapasonEquippedPosition;
private Vector3 _diapasonHiddenPosition;
private bool _canUseDiapason;
+ private bool _isFadingOutConcentration;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
-
_playerAudioSource = GetComponent();
if (_playerAudioSource == null)
_playerAudioSource = gameObject.AddComponent();
@@ -217,6 +211,9 @@ namespace Unheimlich.Gameplay.Player
///
private void StartListeningMode()
{
+ if (_isFadingOutConcentration)
+ return;
+
if (_listeningCoroutine != null)
StopCoroutine(_listeningCoroutine);
@@ -240,17 +237,21 @@ namespace Unheimlich.Gameplay.Player
if (_diapasonObject != null)
{
- if (_tweenCoroutine != null)
- StopCoroutine(_tweenCoroutine);
+ TweenHelper.CancelTween(ref _diapasonTweenId);
- _tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonEquippedPosition, _diapasonTweenDuration, () =>
- {
- if (_diapasonAnimator != null)
+ _diapasonTweenId = TweenHelper.MoveLocalWithCurve(
+ _diapasonObject,
+ _diapasonEquippedPosition,
+ _diapasonTweenDuration,
+ _diapasonTweenCurve,
+ () =>
{
- _diapasonAnimator.enabled = true;
- _diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
- }
- }));
+ if (_diapasonAnimator != null)
+ {
+ _diapasonAnimator.enabled = true;
+ _diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
+ }
+ });
}
PlayBreathIn();
@@ -294,9 +295,21 @@ namespace Unheimlich.Gameplay.Player
if (_playerAudioSource != null && _playerAudioSource.isPlaying)
{
- if (_audioFadeCoroutine != null)
- StopCoroutine(_audioFadeCoroutine);
- _audioFadeCoroutine = StartCoroutine(FadeOutAudio(_playerAudioSource, _audioFadeDuration));
+ TweenHelper.CancelTween(ref _playerAudioFadeTweenId);
+
+ float startVolume = _playerAudioSource.volume;
+ _playerAudioFadeTweenId = TweenHelper.FadeVolume(
+ _playerAudioSource,
+ 0f,
+ _audioFadeDuration,
+ () =>
+ {
+ if (_playerAudioSource != null)
+ {
+ _playerAudioSource.Stop();
+ _playerAudioSource.volume = startVolume;
+ }
+ });
}
if (_diapasonObject != null)
@@ -307,10 +320,13 @@ namespace Unheimlich.Gameplay.Player
_diapasonAnimator.enabled = false;
}
- if (_tweenCoroutine != null)
- StopCoroutine(_tweenCoroutine);
+ TweenHelper.CancelTween(ref _diapasonTweenId);
- _tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonHiddenPosition, _diapasonTweenDuration));
+ _diapasonTweenId = TweenHelper.MoveLocalWithCurve(
+ _diapasonObject,
+ _diapasonHiddenPosition,
+ _diapasonTweenDuration,
+ _diapasonTweenCurve);
}
if (_diapasonParticles != null)
@@ -331,13 +347,23 @@ namespace Unheimlich.Gameplay.Player
if (_currentTargetEmitter != null)
{
- _currentTargetEmitter.StopListening();
+ _currentTargetEmitter.SetConcentrating(false);
+ _currentTargetEmitter.StopListening(cancelConcentrationFade: false);
_currentTargetEmitter = null;
+
+ StartCoroutine(ResetFadingFlagAfterDelay());
}
_canUseDiapason = false;
}
+ private IEnumerator ResetFadingFlagAfterDelay()
+ {
+ _isFadingOutConcentration = true;
+ yield return new WaitForSeconds(0.5f);
+ _isFadingOutConcentration = false;
+ }
+
///
/// Updates cursor position and camera panning during listening mode.
/// Supports both gamepad (look input) and mouse (screen edges).
@@ -492,55 +518,20 @@ namespace Unheimlich.Gameplay.Player
#region If needed
private void AttenuateAmbientSound(bool enable)
{
- if (_audioFadeCoroutine != null)
- StopCoroutine(_audioFadeCoroutine);
+ TweenHelper.CancelTween(ref _audioListenerFadeTweenId);
- _audioFadeCoroutine = StartCoroutine(FadeAmbientSound(enable));
- }
-
- private IEnumerator FadeAmbientSound(bool fadeOut)
- {
- //To do : Create audio tracks
AudioListener listener = FindFirstObjectByType();
if (listener == null)
- yield break;
+ return;
- float startVolume = AudioListener.volume;
- float targetVolume = fadeOut ? _ambientAttenuationAmount : _originalAmbientVolume;
+ float targetVolume = enable ? _ambientAttenuationAmount : _originalAmbientVolume;
- if (!fadeOut)
- _originalAmbientVolume = startVolume;
- else
+ if (!enable)
_originalAmbientVolume = AudioListener.volume;
- float elapsed = 0f;
- while (elapsed < _audioFadeDuration)
- {
- elapsed += Time.deltaTime;
- float t = elapsed / _audioFadeDuration;
- AudioListener.volume = Mathf.Lerp(startVolume, targetVolume, t);
- yield return null;
- }
-
- AudioListener.volume = targetVolume;
+ _audioListenerFadeTweenId = TweenHelper.FadeVolume(gameObject, targetVolume, _audioFadeDuration);
}
#endregion
- private IEnumerator FadeOutAudio(AudioSource source, float duration)
- {
- float startVolume = source.volume;
- float elapsed = 0f;
-
- while (elapsed < duration)
- {
- elapsed += Time.deltaTime;
- float t = elapsed / duration;
- source.volume = Mathf.Lerp(startVolume, 0f, t);
- yield return null;
- }
-
- source.Stop();
- source.volume = startVolume;
- }
private void EnableVignette(bool enable)
{
@@ -551,23 +542,6 @@ namespace Unheimlich.Gameplay.Player
}
}
- private IEnumerator TweenDiapasonPosition(Vector3 targetPosition, float duration, System.Action onComplete = null)
- {
- Vector3 startPosition = _diapasonObject.transform.localPosition;
- float elapsed = 0f;
-
- while (elapsed < duration)
- {
- elapsed += Time.deltaTime;
- float t = _diapasonTweenCurve.Evaluate(elapsed / duration);
- _diapasonObject.transform.localPosition = Vector3.Lerp(startPosition, targetPosition, t);
- yield return null;
- }
-
- _diapasonObject.transform.localPosition = targetPosition;
- onComplete?.Invoke();
- }
-
private void OnDrawGizmosSelected()
{
if (_playerCamera != null && IsListening)
diff --git a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/Player.cs b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/Player.cs
index 454b7603..19e9904b 100644
--- a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/Player.cs
+++ b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/Player.cs
@@ -1,22 +1,9 @@
using UnityEngine;
+using Unheimlich.Core;
namespace Unheimlich.Gameplay.Player
{
- ///
- /// Singleton player reference accessible globally throughout the game.
- ///
- public class Player : MonoBehaviour
+ public class Player : Singleton
{
- public static Player Instance { get; private set; }
-
- private void Awake()
- {
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
- }
}
}
diff --git a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerLook.cs b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerLook.cs
index 3059821b..3ffc187a 100644
--- a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerLook.cs
+++ b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerLook.cs
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.InputSystem;
+using Unheimlich.Core;
using Unheimlich.Gameplay.Portals;
using InputManager = Unheimlich.Managers.PlayerInputManager;
@@ -10,12 +11,10 @@ namespace Unheimlich.Gameplay.Player
/// Supports both mouse and gamepad input with adaptive sensitivity.
/// Includes aim assist for dialogue choices and camera shake effects.
///
- public class PlayerLook : MonoBehaviour
+ public class PlayerLook : Singleton
{
private const float INPUT_DEADZONE = 0.1f;
private const float MOUSE_DELTA_MULTIPLIER = 0.001f;
-
- public static PlayerLook Instance { get; private set; }
[Header("Components")]
[SerializeField] private Transform _playerCamera, _playerCameraPivot;
@@ -46,15 +45,8 @@ namespace Unheimlich.Gameplay.Player
private float _verticalRotation;
private bool _isUsingGamepad = false;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
-
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = _frameRate;
diff --git a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerMovement.cs b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerMovement.cs
index 4859c3fa..9d01b3ba 100644
--- a/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerMovement.cs
+++ b/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerMovement.cs
@@ -1,16 +1,12 @@
using UnityEngine;
+using Unheimlich.Core;
using Unheimlich.Managers;
namespace Unheimlich.Gameplay.Player
{
- ///
- /// Handles player movement, jumping, and gravity using CharacterController.
- /// Provides movement state information for other systems like camera head bob.
- ///
[RequireComponent(typeof(CharacterController))]
- public class PlayerMovement : MonoBehaviour
+ public class PlayerMovement : Singleton
{
- public static PlayerMovement Instance { get; private set; }
[Header("Settings")]
[SerializeField] private float _moveSpeed = 5f;
@@ -24,15 +20,8 @@ namespace Unheimlich.Gameplay.Player
private float _ySpeed;
private Vector3 _moveDirection;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
-
CharacterController = GetComponent();
}
diff --git a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/ChoiceFocusHandler.cs b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/ChoiceFocusHandler.cs
index 2261f2f0..1913693f 100644
--- a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/ChoiceFocusHandler.cs
+++ b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/ChoiceFocusHandler.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using Unheimlich.Data.Dialogues;
using Unheimlich.Managers;
using Unheimlich.Gameplay.Player;
+using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords
{
@@ -219,12 +220,15 @@ namespace Unheimlich.Interactions.FloatingWords
if (_choiceTextComponent == null)
return;
- if (_textScaleTweenId >= 0)
- LeanTween.cancel(_textScaleTweenId);
+ TweenHelper.CancelTween(ref _textScaleTweenId);
- _textScaleTweenId = LeanTween.scale(_choiceTextComponent.gameObject, Vector3.one * targetScale, duration)
- .setEase(LeanTweenType.easeOutQuad)
- .id;
+ _textScaleTweenId = TweenHelper.ScaleLocal(
+ _choiceTextComponent.gameObject,
+ Vector3.one * targetScale,
+ duration,
+ null,
+ LeanTweenType.easeOutQuad
+ );
}
public void ResetTextScale(float duration = 0.3f)
@@ -510,23 +514,20 @@ namespace Unheimlich.Interactions.FloatingWords
{
LeanTween.cancel(gameObject);
- if (_textScaleTweenId >= 0)
- LeanTween.cancel(_textScaleTweenId);
+ TweenHelper.CancelTween(ref _textScaleTweenId);
StopOrganicShake();
ResetSketchFade();
-
- LeanTween.moveLocal(gameObject, _startLocalPos, duration)
- .setEase(LeanTweenType.easeOutQuad)
- .setOnComplete(() => {
- _positionCaptured = false;
+
+ TweenHelper.MoveLocal(gameObject, _startLocalPos, duration, () => {
+ _positionCaptured = false;
+
+ if (DialogueManager.Instance != null)
+ DialogueManager.Instance.UnlockFocus();
- if (DialogueManager.Instance != null)
- DialogueManager.Instance.UnlockFocus();
-
- StartOrganicShake();
- });
+ StartOrganicShake();
+ }, LeanTweenType.easeOutQuad);
transform.localScale = Vector3.one * _originalScale;
diff --git a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/FloatingWordsSpawner.cs b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/FloatingWordsSpawner.cs
index 5a400c43..da79f853 100644
--- a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/FloatingWordsSpawner.cs
+++ b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/FloatingWordsSpawner.cs
@@ -2,14 +2,10 @@ using UnityEngine;
using System.Collections.Generic;
using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player;
+using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords
{
- ///
- /// Utility class for spawning floating 3D text choices in world space.
- /// Used for both dialogue choices and object descriptions. Handles positioning,
- /// rotation, animations (spawn, merge, floating) using LeanTween for smooth effects.
- ///
public static class FloatingWordsSpawner
{
private const float ROTATOR_SCALE = 0.01f;
@@ -249,26 +245,32 @@ namespace Unheimlich.Interactions.FloatingWords
CanvasGroup canvasGroup = words[i].GetComponent();
if (canvasGroup != null)
{
- LeanTween.value(words[i].gameObject, 1f, 0f, config.MergeDuration)
- .setOnUpdate((float val) => { canvasGroup.alpha = val; })
- .setEaseInQuad();
+ TweenHelper.FadeCanvasGroup(canvasGroup, 0f, config.MergeDuration, null, LeanTweenType.easeInQuad);
}
}
}
if (words.Count > 0 && words[0] != null)
{
- LeanTween.scale(words[0].gameObject, Vector3.one * config.MergeScalePulse, config.MergeDuration / 2f)
- .setEaseOutBack()
- .setOnComplete(() =>
+ TweenHelper.ScaleLocal(
+ words[0].gameObject,
+ Vector3.one * config.MergeScalePulse,
+ config.MergeDuration / 2f,
+ () =>
{
if (words.Count > 0 && words[0] != null)
{
- LeanTween.scale(words[0].gameObject, Vector3.one, config.MergeDuration / 2f)
- .setEaseInBack()
- .setOnComplete(() => onMergeComplete?.Invoke());
+ TweenHelper.ScaleLocal(
+ words[0].gameObject,
+ Vector3.one,
+ config.MergeDuration / 2f,
+ () => onMergeComplete.SafeInvoke(),
+ LeanTweenType.easeInBack
+ );
}
- });
+ },
+ LeanTweenType.easeOutBack
+ );
}
}
@@ -292,13 +294,11 @@ namespace Unheimlich.Interactions.FloatingWords
{
if (words[i] != null)
{
- LeanTween.cancel(words[i].gameObject);
+ words[i].gameObject.CancelAllTweens();
CanvasGroup canvasGroup = words[i].GetComponent();
if (canvasGroup != null)
{
- LeanTween.value(words[i].gameObject, canvasGroup.alpha, 1f, config.MergeDuration)
- .setOnUpdate((float val) => { canvasGroup.alpha = val; })
- .setEaseOutQuad();
+ TweenHelper.FadeCanvasGroup(canvasGroup, 1f, config.MergeDuration, null, LeanTweenType.easeOutQuad);
}
words[i].transform.localScale = Vector3.one;
}
@@ -306,7 +306,7 @@ namespace Unheimlich.Interactions.FloatingWords
if (words.Count > 0 && words[0] != null)
{
- LeanTween.cancel(words[0].gameObject);
+ words[0].gameObject.CancelAllTweens();
words[0].transform.localScale = Vector3.one;
}
}
diff --git a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/SketchFadeEffect.cs b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/SketchFadeEffect.cs
index 6ef33419..096c6464 100644
--- a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/SketchFadeEffect.cs
+++ b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/FloatingWords/SketchFadeEffect.cs
@@ -1,6 +1,7 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
+using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords
{
@@ -93,42 +94,41 @@ namespace Unheimlich.Interactions.FloatingWords
_isEffectActive = true;
- _scribbleTweenId = LeanTween.value(gameObject, 0f, 1f, scribbleDuration)
- .setEase(LeanTweenType.easeInOutQuad)
- .setOnUpdate((float val) => {
- if (_scribbleMaterialInstance != null)
- _scribbleMaterialInstance.SetFloat(ProgressID, val);
- })
- .setOnComplete(() => {
+ _scribbleTweenId = TweenHelper.AnimateMaterialFloat(
+ gameObject,
+ _scribbleMaterialInstance,
+ ProgressID,
+ 0f,
+ 1f,
+ scribbleDuration,
+ () => {
_scribbleTweenId = -1;
+
+ float ghostAlphaRatio = _ghostTextComponent != null ? (_originalGhostColor.a / _originalTextColor.a) : 0f;
+
_fadeTweenId = LeanTween.value(gameObject, _originalTextColor.a, finalAlpha, fadeDuration)
.setEase(LeanTweenType.easeInQuad)
.setOnUpdate((float val) => {
if (_textComponent != null)
{
- Color textColor = _textComponent.color;
- textColor.a = val;
- _textComponent.color = textColor;
+ _textComponent.color = _textComponent.color.WithAlpha(val);
}
if (_ghostTextComponent != null)
{
- Color ghostColor = _ghostTextComponent.color;
- ghostColor.a = val * (_originalGhostColor.a / _originalTextColor.a);
- _ghostTextComponent.color = ghostColor;
+ _ghostTextComponent.color = _ghostTextComponent.color.WithAlpha(val * ghostAlphaRatio);
}
if (_scribbleOverlay != null)
{
- Color scribbleColor = _scribbleOverlay.color;
- scribbleColor.a = val;
- _scribbleOverlay.color = scribbleColor;
+ _scribbleOverlay.color = _scribbleOverlay.color.WithAlpha(val);
}
})
.setOnComplete(() => {
_fadeTweenId = -1;
}).id;
- }).id;
+ },
+ LeanTweenType.easeInOutQuad);
}
public void ResetEffect()
@@ -141,17 +141,19 @@ namespace Unheimlich.Interactions.FloatingWords
if (currentProgress > 0f)
{
- _scribbleTweenId = LeanTween.value(gameObject, currentProgress, 0f, 0.3f)
- .setEase(LeanTweenType.easeOutQuad)
- .setOnUpdate((float val) => {
- if (_scribbleMaterialInstance != null)
- _scribbleMaterialInstance.SetFloat(ProgressID, val);
- })
- .setOnComplete(() => {
+ _scribbleTweenId = TweenHelper.AnimateMaterialFloat(
+ gameObject,
+ _scribbleMaterialInstance,
+ ProgressID,
+ currentProgress,
+ 0f,
+ 0.3f,
+ () => {
_scribbleTweenId = -1;
if (_scribbleOverlay != null)
_scribbleOverlay.gameObject.SetActive(false);
- }).id;
+ },
+ LeanTweenType.easeOutQuad);
}
}
@@ -161,34 +163,23 @@ namespace Unheimlich.Interactions.FloatingWords
if (currentAlpha < _originalTextColor.a)
{
- _fadeTweenId = LeanTween.value(gameObject, currentAlpha, _originalTextColor.a, 0.3f)
- .setEase(LeanTweenType.easeOutQuad)
- .setOnUpdate((float val) => {
- if (_textComponent != null)
- {
- Color c = _textComponent.color;
- c.a = val;
- _textComponent.color = c;
- }
-
- if (_ghostTextComponent != null)
- {
- Color c = _ghostTextComponent.color;
- c.a = val * (_originalGhostColor.a / _originalTextColor.a);
- _ghostTextComponent.color = c;
- }
- })
- .setOnComplete(() => {
- _fadeTweenId = -1;
- }).id;
+ float ghostAlphaRatio = _ghostTextComponent != null ? (_originalGhostColor.a / _originalTextColor.a) : 0f;
+
+ _fadeTweenId = TweenHelper.FadeText(
+ gameObject,
+ _textComponent,
+ _ghostTextComponent,
+ _originalTextColor.a,
+ 0.3f,
+ ghostAlphaRatio,
+ () => { _fadeTweenId = -1; },
+ LeanTweenType.easeOutQuad);
}
}
if (_scribbleOverlay != null)
{
- Color c = _scribbleOverlay.color;
- c.a = 1f;
- _scribbleOverlay.color = c;
+ _scribbleOverlay.color = _scribbleOverlay.color.WithAlpha(1f);
}
_isEffectActive = false;
@@ -196,17 +187,8 @@ namespace Unheimlich.Interactions.FloatingWords
private void CancelActiveTweens()
{
- if (_scribbleTweenId != -1)
- {
- LeanTween.cancel(_scribbleTweenId);
- _scribbleTweenId = -1;
- }
-
- if (_fadeTweenId != -1)
- {
- LeanTween.cancel(_fadeTweenId);
- _fadeTweenId = -1;
- }
+ TweenHelper.CancelTween(ref _scribbleTweenId);
+ TweenHelper.CancelTween(ref _fadeTweenId);
}
private void OnDestroy()
diff --git a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/InteracionHandler.cs b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/InteracionHandler.cs
index 7520e028..b7d12c92 100644
--- a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/InteracionHandler.cs
+++ b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/InteracionHandler.cs
@@ -1,15 +1,12 @@
using UnityEngine;
+using Unheimlich.Core;
using Unheimlich.Managers;
+using Unheimlich.Utility;
namespace Unheimlich.Interactions.Core
{
- ///
- /// Detects interactable objects in front of the player camera using raycasts.
- /// Triggers interaction when player presses interact button while looking at an interactable.
- ///
- public class InteractionHandler : MonoBehaviour
-{
- public static InteractionHandler Instance { get; private set; }
+ public class InteractionHandler : Singleton
+ {
[Header("Settings")]
[SerializeField] private float _interactionRange = 1f;
@@ -19,50 +16,32 @@ namespace Unheimlich.Interactions.Core
private InteractableBase _currentInteractable;
private Camera _playerCamera;
private float _raycastTimer;
-
- private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
_playerCamera = GetComponent();
}
private void Update()
{
- _raycastTimer += Time.deltaTime;
-
- if (_raycastTimer >= _raycastInterval)
+ if (RaycastHelper.ThrottledRaycastComponent(
+ ref _raycastTimer,
+ _raycastInterval,
+ _playerCamera,
+ _interactionRange,
+ _interactableLayers,
+ out InteractableBase interactable))
{
- CheckForInteractable();
- _raycastTimer = 0f;
+ _currentInteractable = interactable;
+ }
+ else if (_raycastTimer == 0f)
+ {
+ _currentInteractable = null;
}
HandleInteractionInput();
}
- ///
- /// Performs raycast from camera center to detect interactable objects within range.
- /// Updates the current interactable reference for input handling.
- ///
- private void CheckForInteractable()
- {
- Ray ray = _playerCamera.ViewportPointToRay(ViewportCenter);
-
- if (Physics.Raycast(ray, out RaycastHit hit, _interactionRange, _interactableLayers))
- {
- InteractableBase hitInteractable = hit.collider.GetComponent();
- _currentInteractable = hitInteractable;
- }
- else
- _currentInteractable = null;
- }
-
private void HandleInteractionInput()
{
if (PlayerInputManager.Instance != null &&
@@ -79,7 +58,8 @@ namespace Unheimlich.Interactions.Core
{
Gizmos.color = _currentInteractable != null ? Color.green : Color.red;
- Ray ray = _playerCamera.ViewportPointToRay(ViewportCenter);
+ Vector3 viewportCenter = new Vector3(0.5f, 0.5f, 0f);
+ Ray ray = _playerCamera.ViewportPointToRay(viewportCenter);
Gizmos.DrawRay(ray.origin, ray.direction * _interactionRange);
}
}
diff --git a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/PlayerInspectionHandler.cs b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/PlayerInspectionHandler.cs
index efe60152..1c93724e 100644
--- a/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/PlayerInspectionHandler.cs
+++ b/Assets/Unheimlich_POC/Scripts/Handlers/Interactions/PlayerInspectionHandler.cs
@@ -1,14 +1,18 @@
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
+using Unheimlich.Core;
using Unheimlich.Gameplay.Items;
using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Interactions.Core
{
- public class PlayerInspectionHandler : MonoBehaviour
+ ///
+ /// Handles player item inspection mode.
+ /// When inspecting, disables player movement and allows item rotation using look input.
+ ///
+ public class PlayerInspectionHandler : Singleton
{
- public static PlayerInspectionHandler Instance { get; private set; }
[Header("Components")]
[SerializeField] private Transform _inspectionHoldPoint;
@@ -19,21 +23,7 @@ namespace Unheimlich.Interactions.Core
private Coroutine _interactionListener;
private bool _isUsingGamepad = false;
-
- private void Awake()
- {
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
- }
- ///
- /// Toggles inspection mode for an item. When inspecting, disables player movement
- /// and allows item rotation using look input until interaction key is pressed again.
- ///
public void ToggleInspection(InspectableItem item)
{
if (!IsInspecting)
diff --git a/Assets/Unheimlich_POC/Scripts/Managers/DialogueFocusSystem.cs b/Assets/Unheimlich_POC/Scripts/Managers/DialogueFocusSystem.cs
index 2ad25339..f43bb8c9 100644
--- a/Assets/Unheimlich_POC/Scripts/Managers/DialogueFocusSystem.cs
+++ b/Assets/Unheimlich_POC/Scripts/Managers/DialogueFocusSystem.cs
@@ -4,6 +4,7 @@ using Unheimlich.Gameplay.Player;
using Unheimlich.Interactions.Dialogues;
using Unheimlich.Interactions.FloatingWords;
using Unheimlich.Data.Dialogues;
+using Unheimlich.Utility;
namespace Unheimlich.Managers
{
@@ -32,8 +33,6 @@ namespace Unheimlich.Managers
private readonly float _focusRetentionTime;
private readonly float _fallbackShakeIntensity;
- private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
-
public event Action OnChoiceMade;
public DialogueFocusSystem(
@@ -96,16 +95,13 @@ namespace Unheimlich.Managers
if (_mainCamera == null)
return;
- Ray ray = _mainCamera.ViewportPointToRay(ViewportCenter);
-
- ChoiceFocusHandler hitHandler = null;
- bool raycastHitChoice = Physics.Raycast(ray, out RaycastHit hit, _focusRange, _choiceLayer);
-
- if (raycastHitChoice)
+ ChoiceFocusHandler hitHandler = null;
+
+ if (RaycastHelper.RaycastFromCameraCenter(_mainCamera, _focusRange, _choiceLayer, out RaycastHit hit))
{
hitHandler = hit.collider.GetComponent();
- if (_playerLook != null)
+ if (_playerLook != null && hitHandler != null)
_playerLook.ApplyAimAssist(hit.collider.bounds.center);
}
diff --git a/Assets/Unheimlich_POC/Scripts/Managers/DialogueManager.cs b/Assets/Unheimlich_POC/Scripts/Managers/DialogueManager.cs
index d4a9cd07..d66fb90e 100644
--- a/Assets/Unheimlich_POC/Scripts/Managers/DialogueManager.cs
+++ b/Assets/Unheimlich_POC/Scripts/Managers/DialogueManager.cs
@@ -1,5 +1,6 @@
using UnityEngine;
using System;
+using Unheimlich.Core;
using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player;
using Unheimlich.UI;
@@ -8,9 +9,8 @@ using System.Collections.Generic;
namespace Unheimlich.Managers
{
- public class DialogueManager : MonoBehaviour
-{
- public static DialogueManager Instance { get; private set; }
+ public class DialogueManager : Singleton
+ {
[Header("State")]
public bool IsInDialogue = false;
@@ -51,14 +51,8 @@ namespace Unheimlich.Managers
private float _autoCloseTimer = 0f;
private bool _isWaitingForAutoClose = false;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
}
private void Start()
diff --git a/Assets/Unheimlich_POC/Scripts/Managers/PlayerInputManager.cs b/Assets/Unheimlich_POC/Scripts/Managers/PlayerInputManager.cs
index 46d6e722..61770f5d 100644
--- a/Assets/Unheimlich_POC/Scripts/Managers/PlayerInputManager.cs
+++ b/Assets/Unheimlich_POC/Scripts/Managers/PlayerInputManager.cs
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.InputSystem;
+using Unheimlich.Core;
using Unheimlich.Gameplay.Player;
namespace Unheimlich.Managers
@@ -10,7 +11,7 @@ namespace Unheimlich.Managers
/// Supports different input contexts (Normal, Dialogue, Listening) to control which inputs are active.
///
[RequireComponent(typeof(PlayerInput))]
- public class PlayerInputManager : MonoBehaviour
+ public class PlayerInputManager : Singleton
{
///
/// Defines different input contexts that control which player actions are enabled.
@@ -22,8 +23,6 @@ namespace Unheimlich.Managers
Listening // Only look enabled, movement disabled (for diapason camera pan)
}
- public static PlayerInputManager Instance { get; private set; }
-
[Header("Input Values (Read only)")]
public Vector2 MoveInput;
public Vector2 LookInput;
@@ -44,15 +43,8 @@ namespace Unheimlich.Managers
private PlayerLook _playerLook;
private CameraHeadBob _headBob;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
-
IsInputEnabled = true;
PlayerInput playerInput = GetComponent();
diff --git a/Assets/Unheimlich_POC/Scripts/Managers/PortalRoomManager.cs b/Assets/Unheimlich_POC/Scripts/Managers/PortalRoomManager.cs
index 98afd473..988e5e16 100644
--- a/Assets/Unheimlich_POC/Scripts/Managers/PortalRoomManager.cs
+++ b/Assets/Unheimlich_POC/Scripts/Managers/PortalRoomManager.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
+using Unheimlich.Core;
using Unheimlich.Gameplay.Portals;
namespace Unheimlich.Managers
@@ -9,9 +10,8 @@ namespace Unheimlich.Managers
/// Monitors player distance to portals and loads/unloads room instances accordingly.
/// Supports room pooling (keeping inactive rooms in memory) and distance-based activation.
///
- public class PortalRoomManager : MonoBehaviour
+ public class PortalRoomManager : Singleton
{
- public static PortalRoomManager Instance { get; private set; }
[Header("Settings")]
[SerializeField] private float _loadDistance = 15f;
@@ -44,16 +44,6 @@ namespace Unheimlich.Managers
public bool BoundsCalculated = false;
}
- private void Awake()
- {
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
- }
-
private void Start()
{
_player = GameObject.FindGameObjectWithTag("Player")?.transform;
diff --git a/Assets/Unheimlich_POC/Scripts/UI/DialogueBlurEffectURP.cs b/Assets/Unheimlich_POC/Scripts/UI/DialogueBlurEffectURP.cs
index c6c9c617..699f9234 100644
--- a/Assets/Unheimlich_POC/Scripts/UI/DialogueBlurEffectURP.cs
+++ b/Assets/Unheimlich_POC/Scripts/UI/DialogueBlurEffectURP.cs
@@ -1,13 +1,13 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
+using Unheimlich.Core;
namespace Unheimlich.UI
{
[RequireComponent(typeof(Camera))]
- public class DialogueBlurEffectURP : MonoBehaviour
+ public class DialogueBlurEffectURP : Singleton
{
- public static DialogueBlurEffectURP Instance { get; private set; }
[Header("Volume Settings")]
[SerializeField] private Volume _globalVolume;
@@ -43,15 +43,8 @@ namespace Unheimlich.UI
private Camera _baseCam;
private Camera _dialogueCam;
- void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Destroy(this);
- return;
- }
- Instance = this;
-
_baseCam = GetComponent();
if (_fadeCurve == null || _fadeCurve.keys.Length == 0)
diff --git a/Assets/Unheimlich_POC/Scripts/UI/DialogueScreenUI.cs b/Assets/Unheimlich_POC/Scripts/UI/DialogueScreenUI.cs
index c8178ae9..742a36bd 100644
--- a/Assets/Unheimlich_POC/Scripts/UI/DialogueScreenUI.cs
+++ b/Assets/Unheimlich_POC/Scripts/UI/DialogueScreenUI.cs
@@ -1,11 +1,12 @@
using UnityEngine;
using TMPro;
+using Unheimlich.Core;
+using Unheimlich.Utility;
namespace Unheimlich.UI
{
- public class DialogueScreenUI : MonoBehaviour
+ public class DialogueScreenUI : Singleton
{
- public static DialogueScreenUI Instance { get; private set; }
[Header("UI References")]
[SerializeField] private GameObject _dialoguePanel;
@@ -17,16 +18,8 @@ namespace Unheimlich.UI
private CanvasGroup _canvasGroup;
- private void Awake()
+ protected override void OnAwake()
{
- if (Instance != null && Instance != this)
- {
- Debug.LogWarning("[DialogueScreenUI] Multiple instances detected! Destroying duplicate.");
- Destroy(gameObject);
- return;
- }
-
- Instance = this;
if (_dialoguePanel != null)
{
@@ -37,12 +30,6 @@ namespace Unheimlich.UI
}
}
- private void OnDestroy()
- {
- if (Instance == this)
- Instance = null;
- }
-
public void Show(string speaker, string dialogue)
{
if (_speakerText != null)
@@ -73,18 +60,19 @@ namespace Unheimlich.UI
{
if (_canvasGroup != null && _dialoguePanel != null)
{
- LeanTween.cancel(_dialoguePanel);
- LeanTween.alphaCanvas(_canvasGroup, 0f, _fadeOutDuration)
- .setOnComplete(() =>
- {
+ TweenHelper.FadeCanvasGroup(
+ _canvasGroup,
+ 0f,
+ _fadeOutDuration,
+ () => {
Hide();
- onComplete?.Invoke();
+ onComplete.SafeInvoke();
});
}
else
{
Hide();
- onComplete?.Invoke();
+ onComplete.SafeInvoke();
}
}
}
diff --git a/Assets/Unheimlich_POC/Scripts/UI/DialogueUI.cs b/Assets/Unheimlich_POC/Scripts/UI/DialogueUI.cs
index 4dfd69b9..b5ea013f 100644
--- a/Assets/Unheimlich_POC/Scripts/UI/DialogueUI.cs
+++ b/Assets/Unheimlich_POC/Scripts/UI/DialogueUI.cs
@@ -4,6 +4,7 @@ using TMPro;
using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player;
using Unheimlich.Interactions.FloatingWords;
+using Unheimlich.Utility;
namespace Unheimlich.UI
{
@@ -43,6 +44,10 @@ namespace Unheimlich.UI
private DialogueDisplayMode _currentDisplayMode = DialogueDisplayMode.World;
+ private int _fadeCanvasTweenId = -1;
+ private int _rotationTweenId = -1;
+ private List _floatingTweenIds = new List();
+
private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
private void Awake()
@@ -91,7 +96,7 @@ namespace Unheimlich.UI
if (_dialogueCanvasGroup != null)
{
- LeanTween.cancel(_dialoguePanel);
+ TweenHelper.CancelTween(ref _fadeCanvasTweenId);
_dialogueCanvasGroup.alpha = 1f;
}
}
@@ -125,9 +130,12 @@ namespace Unheimlich.UI
{
if (_dialogueCanvasGroup != null && _dialoguePanel != null)
{
- LeanTween.cancel(_dialoguePanel);
- LeanTween.alphaCanvas(_dialogueCanvasGroup, 0f, _fadeOutDuration)
- .setOnComplete(() =>
+ TweenHelper.CancelTween(ref _fadeCanvasTweenId);
+ _fadeCanvasTweenId = TweenHelper.FadeCanvasGroup(
+ _dialogueCanvasGroup,
+ 0f,
+ _fadeOutDuration,
+ () =>
{
HideDialogueBox();
onComplete?.Invoke();
@@ -247,35 +255,18 @@ namespace Unheimlich.UI
float amplitude = _currentAnimationSettings.FloatingAmplitude;
float duration = _currentAnimationSettings.FloatingDuration;
- float offset = index * 0.2f;
+ float delay = index * 0.2f;
Vector3 startPos = choice.transform.localPosition;
- LeanTween.value(choice, 0f, Mathf.PI * 2f, duration)
- .setDelay(offset)
- .setOnUpdate((float val) => {
- if (choice != null)
- {
- Vector3 newPos = startPos;
- newPos.y += Mathf.Sin(val) * amplitude;
- choice.transform.localPosition = newPos;
- }
- })
- .setLoopClamp()
- .setEase(LeanTweenType.linear);
+ int tweenId = TweenHelper.FloatAnimation(choice, startPos, amplitude, duration, delay, Axis.Y);
+ _floatingTweenIds.Add(tweenId);
}
public void RotationAnimation(float degreesPerSecond = 60f)
{
- if (degreesPerSecond <= 0f)
- return;
-
- float duration = 360f / degreesPerSecond;
-
- LeanTween.cancel(ChoiceContainer);
- LeanTween.rotateAroundLocal(ChoiceContainer, _currentRotationAxis, 360f, duration)
- .setLoopClamp()
- .setEase(LeanTweenType.linear);
+ TweenHelper.CancelTween(ref _rotationTweenId);
+ _rotationTweenId = TweenHelper.RotateAround(ChoiceContainer, _currentRotationAxis, degreesPerSecond);
}
///
@@ -318,10 +309,17 @@ namespace Unheimlich.UI
_currentAnchor = null;
_currentAnimationSettings = null;
_activeChoices.Clear();
- LeanTween.cancel(ChoiceContainer);
+
+ TweenHelper.CancelTween(ref _rotationTweenId);
+ foreach (int tweenId in _floatingTweenIds)
+ {
+ int id = tweenId;
+ TweenHelper.CancelTween(ref id);
+ }
+ _floatingTweenIds.Clear();
+
foreach (Transform child in ChoiceContainer.transform)
{
- LeanTween.cancel(child.gameObject);
Destroy(child.gameObject);
}
}
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs b/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs
new file mode 100644
index 00000000..593ce407
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs
@@ -0,0 +1,66 @@
+using UnityEngine;
+
+namespace Unheimlich.Utility
+{
+ public static class RaycastHelper
+ {
+ private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
+
+ public static bool RaycastFromCameraCenter(Camera camera, float maxDistance,
+ LayerMask layerMask, out RaycastHit hit)
+ {
+ if (camera == null)
+ {
+ hit = default;
+ return false;
+ }
+
+ Ray ray = camera.ViewportPointToRay(ViewportCenter);
+ return Physics.Raycast(ray, out hit, maxDistance, layerMask);
+ }
+
+ public static bool RaycastFromCameraCenter(Camera camera, float maxDistance,
+ LayerMask layerMask, out T component) where T : Component
+ {
+ component = null;
+
+ if (RaycastFromCameraCenter(camera, maxDistance, layerMask, out RaycastHit hit))
+ {
+ component = hit.collider.GetComponent();
+ return component != null;
+ }
+
+ return false;
+ }
+
+ public static bool ThrottledRaycast(ref float timer, float interval,
+ Camera camera, float maxDistance, LayerMask layerMask, out RaycastHit hit)
+ {
+ timer += Time.deltaTime;
+
+ if (timer >= interval)
+ {
+ timer = 0f;
+ return RaycastFromCameraCenter(camera, maxDistance, layerMask, out hit);
+ }
+
+ hit = default;
+ return false;
+ }
+
+ public static bool ThrottledRaycastComponent(ref float timer, float interval,
+ Camera camera, float maxDistance, LayerMask layerMask, out T component) where T : Component
+ {
+ timer += Time.deltaTime;
+
+ if (timer >= interval)
+ {
+ timer = 0f;
+ return RaycastFromCameraCenter(camera, maxDistance, layerMask, out component);
+ }
+
+ component = null;
+ return false;
+ }
+ }
+}
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs.meta b/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs.meta
new file mode 100644
index 00000000..30dc0c96
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/RaycastHelper.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f37b9866154109148b3f7d2d3c121c36
\ No newline at end of file
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs b/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs
new file mode 100644
index 00000000..947914b9
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs
@@ -0,0 +1,224 @@
+using UnityEngine;
+using TMPro;
+
+namespace Unheimlich.Utility
+{
+ public static class TweenHelper
+ {
+ public static int FadeCanvasGroup(CanvasGroup canvasGroup, float targetAlpha,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInQuad)
+ {
+ if (canvasGroup == null) return -1;
+
+ LeanTween.cancel(canvasGroup.gameObject);
+ return LeanTween.alphaCanvas(canvasGroup, targetAlpha, duration)
+ .setEase(easeType)
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int FadeText(TextMeshProUGUI text, float targetAlpha,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInQuad)
+ {
+ return FadeText(text?.gameObject, text, null, targetAlpha, duration, 1f, onComplete, easeType);
+ }
+
+ public static int FadeText(GameObject targetObject, TextMeshProUGUI mainText,
+ TextMeshProUGUI ghostText, float targetAlpha, float duration,
+ float ghostAlphaRatio, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInQuad)
+ {
+ if (mainText == null || targetObject == null) return -1;
+
+ float startAlpha = mainText.color.a;
+ LeanTween.cancel(targetObject);
+
+ return LeanTween.value(targetObject, startAlpha, targetAlpha, duration)
+ .setEase(easeType)
+ .setOnUpdate((float val) => {
+ if (mainText != null)
+ {
+ Color c = mainText.color;
+ c.a = val;
+ mainText.color = c;
+ }
+
+ if (ghostText != null)
+ {
+ Color gc = ghostText.color;
+ gc.a = val * ghostAlphaRatio;
+ ghostText.color = gc;
+ }
+ })
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int ScaleLocal(GameObject target, Vector3 targetScale,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInOutQuad)
+ {
+ if (target == null) return -1;
+
+ LeanTween.cancel(target);
+ return LeanTween.scale(target, targetScale, duration)
+ .setEase(easeType)
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int MoveLocal(GameObject target, Vector3 targetPosition,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInOutQuad)
+ {
+ if (target == null) return -1;
+
+ LeanTween.cancel(target);
+ return LeanTween.moveLocal(target, targetPosition, duration)
+ .setEase(easeType)
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int RotateLocal(GameObject target, Vector3 targetRotation,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInOutQuad)
+ {
+ if (target == null) return -1;
+
+ LeanTween.cancel(target);
+ return LeanTween.rotate(target, targetRotation, duration)
+ .setEase(easeType)
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int MoveLocalWithCurve(GameObject target, Vector3 targetPosition,
+ float duration, AnimationCurve curve, System.Action onComplete = null)
+ {
+ if (target == null) return -1;
+
+ LeanTween.cancel(target);
+ return LeanTween.moveLocal(target, targetPosition, duration)
+ .setEase(curve)
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int FadeVolume(AudioSource source, float targetVolume,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInOutQuad)
+ {
+ if (source == null) return -1;
+
+ GameObject sourceObject = source.gameObject;
+ LeanTween.cancel(sourceObject);
+
+ return LeanTween.value(sourceObject, source.volume, targetVolume, duration)
+ .setEase(easeType)
+ .setOnUpdate((float val) => {
+ if (source != null)
+ source.volume = val;
+ })
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int FadeVolume(GameObject target, float targetVolume,
+ float duration, System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.linear)
+ {
+ if (target == null) return -1;
+
+ float startVolume = AudioListener.volume;
+ LeanTween.cancel(target);
+
+ return LeanTween.value(target, startVolume, targetVolume, duration)
+ .setEase(easeType)
+ .setOnUpdate((float val) => {
+ AudioListener.volume = val;
+ })
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static int FloatAnimation(GameObject target, Vector3 basePosition,
+ float amplitude, float duration, float delay = 0f, Axis axis = Axis.Y)
+ {
+ if (target == null) return -1;
+
+ LeanTween.cancel(target);
+
+ return LeanTween.value(target, 0f, Mathf.PI * 2f, duration)
+ .setDelay(delay)
+ .setOnUpdate((float val) => {
+ if (target != null)
+ {
+ Vector3 newPos = basePosition;
+ float offset = Mathf.Sin(val) * amplitude;
+
+ switch (axis)
+ {
+ case Axis.X: newPos.x += offset; break;
+ case Axis.Y: newPos.y += offset; break;
+ case Axis.Z: newPos.z += offset; break;
+ }
+
+ target.transform.localPosition = newPos;
+ }
+ })
+ .setLoopClamp()
+ .setEase(LeanTweenType.linear)
+ .id;
+ }
+
+ public static int RotateAround(GameObject target, Vector3 axis, float degreesPerSecond)
+ {
+ if (target == null || degreesPerSecond <= 0f) return -1;
+
+ float duration = 360f / degreesPerSecond;
+ LeanTween.cancel(target);
+
+ return LeanTween.rotateAroundLocal(target, axis, 360f, duration)
+ .setLoopClamp()
+ .setEase(LeanTweenType.linear)
+ .id;
+ }
+
+ public static int AnimateMaterialFloat(GameObject target, Material material,
+ int propertyID, float fromValue, float toValue, float duration,
+ System.Action onComplete = null,
+ LeanTweenType easeType = LeanTweenType.easeInOutQuad)
+ {
+ if (target == null || material == null) return -1;
+
+ LeanTween.cancel(target);
+
+ return LeanTween.value(target, fromValue, toValue, duration)
+ .setEase(easeType)
+ .setOnUpdate((float val) => {
+ if (material != null)
+ material.SetFloat(propertyID, val);
+ })
+ .setOnComplete(onComplete)
+ .id;
+ }
+
+ public static void CancelTween(ref int tweenId)
+ {
+ if (tweenId != -1)
+ {
+ LeanTween.cancel(tweenId);
+ tweenId = -1;
+ }
+ }
+
+ }
+
+ public enum Axis
+ {
+ X, Y, Z
+ }
+}
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs.meta b/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs.meta
new file mode 100644
index 00000000..4d62b7b8
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/TweenHelper.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f70df97fd94f86a4aa2cb00353624ba4
\ No newline at end of file
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs b/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs
new file mode 100644
index 00000000..148976a0
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs
@@ -0,0 +1,47 @@
+using UnityEngine;
+
+namespace Unheimlich.Utility
+{
+ public static class UnityExtensions
+ {
+ public static void SafeInvoke(this System.Action action)
+ {
+ action?.Invoke();
+ }
+
+ public static void SafeInvoke(this System.Action action, T arg)
+ {
+ action?.Invoke(arg);
+ }
+
+ public static Color WithAlpha(this Color color, float alpha)
+ {
+ color.a = alpha;
+ return color;
+ }
+
+ public static Vector3 WithY(this Vector3 vector, float y)
+ {
+ vector.y = y;
+ return vector;
+ }
+
+ public static Vector3 WithX(this Vector3 vector, float x)
+ {
+ vector.x = x;
+ return vector;
+ }
+
+ public static Vector3 WithZ(this Vector3 vector, float z)
+ {
+ vector.z = z;
+ return vector;
+ }
+
+ public static void CancelAllTweens(this GameObject gameObject)
+ {
+ if (gameObject != null)
+ LeanTween.cancel(gameObject);
+ }
+ }
+}
diff --git a/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs.meta b/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs.meta
new file mode 100644
index 00000000..04814e07
--- /dev/null
+++ b/Assets/Unheimlich_POC/Scripts/Utility/UnityExtensions.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b5c8821940a7678458f26015ee167fdf
\ No newline at end of file