Add utils + refacto

This commit is contained in:
2026-01-30 17:39:26 +01:00
parent 9ff17b5305
commit 23d3cb5d2c
29 changed files with 675 additions and 461 deletions
@@ -2498,6 +2498,10 @@ PrefabInstance:
propertyPath: _diapasonParticles propertyPath: _diapasonParticles
value: value:
objectReference: {fileID: 5090177222934034460} 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} - target: {fileID: 3142789394009655648, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: _handTouchFeedback propertyPath: _handTouchFeedback
value: value:
@@ -2536,7 +2540,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3} - target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: m_LocalPosition.z propertyPath: m_LocalPosition.z
value: 23.7 value: 25.9
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3} - target: {fileID: 5383576672370031505, guid: 92d3cde0c88c75f48a98d082c573338b, type: 3}
propertyPath: m_LocalRotation.w propertyPath: m_LocalRotation.w
@@ -2649,8 +2653,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7093688fd39800645bbe00b6d9a3f27b, type: 3} m_Script: {fileID: 11500000, guid: 7093688fd39800645bbe00b6d9a3f27b, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Unheimlich.Audio.DiapasonDebugger m_EditorClassIdentifier: Assembly-CSharp::Unheimlich.Audio.DiapasonDebugger
_showDebugInfo: 1 _showDebugInfo: 0
_showGizmos: 1 _showGizmos: 0
_listeningRangeColor: {r: 0, g: 1, b: 1, a: 1} _listeningRangeColor: {r: 0, g: 1, b: 1, a: 1}
_activeTargetColor: {r: 0, g: 1, b: 0, a: 1} _activeTargetColor: {r: 0, g: 1, b: 0, a: 1}
--- !u!1660057539 &9223372036854775807 --- !u!1660057539 &9223372036854775807
@@ -1,6 +1,7 @@
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Unheimlich.Utility;
namespace Unheimlich.Audio namespace Unheimlich.Audio
{ {
@@ -24,8 +25,8 @@ namespace Unheimlich.Audio
private const string CONTINUOUS_AUDIO_SOURCE_NAME = "Continuous Audio Source"; private const string CONTINUOUS_AUDIO_SOURCE_NAME = "Continuous Audio Source";
// Volume multipliers for concentration system // Volume multipliers for concentration system
private const float BASELINE_VOLUME_MULTIPLIER = 0.3f; private const float BASELINE_VOLUME_MULTIPLIER = 0.1f;
private const float FULL_VOLUME_MULTIPLIER = 1f; private const float FULL_VOLUME_MULTIPLIER = 1.5f;
private const float CONCENTRATION_FADE_DURATION = 1f; private const float CONCENTRATION_FADE_DURATION = 1f;
private const float RELEASE_FADE_DURATION = 0.5f; private const float RELEASE_FADE_DURATION = 0.5f;
@@ -78,7 +79,8 @@ namespace Unheimlich.Audio
private AudioClip _continuousClip; private AudioClip _continuousClip;
private Coroutine _volumeFadeCoroutine; private Coroutine _volumeFadeCoroutine;
private Coroutine _startListeningCoroutine; private Coroutine _startListeningCoroutine;
private Coroutine _concentrationFadeCoroutine; private int _concentrationFadeTweenId = -1;
private int _crossfadeTweenId = -1;
/// <summary> /// <summary>
/// Returns true if this emitter has been tapped during the current listening session. /// Returns true if this emitter has been tapped during the current listening session.
@@ -147,18 +149,34 @@ namespace Unheimlich.Audio
/// </summary> /// </summary>
public void SetConcentrating(bool concentrating) public void SetConcentrating(bool concentrating)
{ {
TweenHelper.CancelTween(ref _concentrationFadeTweenId);
if (_concentrationFadeCoroutine != null) if (_continuousAudioSource == null)
StopCoroutine(_concentrationFadeCoroutine); return;
if (concentrating) float baseVolume = _continuousBaseVolume;
{ float fromMultiplier = concentrating ? BASELINE_VOLUME_MULTIPLIER : FULL_VOLUME_MULTIPLIER;
_concentrationFadeCoroutine = StartCoroutine(FadeConcentrationVolume(BASELINE_VOLUME_MULTIPLIER, FULL_VOLUME_MULTIPLIER, CONCENTRATION_FADE_DURATION)); float toMultiplier = concentrating ? FULL_VOLUME_MULTIPLIER : BASELINE_VOLUME_MULTIPLIER;
} float duration = concentrating ? CONCENTRATION_FADE_DURATION : RELEASE_FADE_DURATION;
else
{ float fromVolume = baseVolume * fromMultiplier;
_concentrationFadeCoroutine = StartCoroutine(FadeConcentrationVolume(FULL_VOLUME_MULTIPLIER, BASELINE_VOLUME_MULTIPLIER, RELEASE_FADE_DURATION)); 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;
} }
/// <summary> /// <summary>
@@ -197,8 +215,22 @@ namespace Unheimlich.Audio
if (_volumeFadeCoroutine != null) if (_volumeFadeCoroutine != null)
StopCoroutine(_volumeFadeCoroutine); StopCoroutine(_volumeFadeCoroutine);
TweenHelper.CancelTween(ref _crossfadeTweenId);
float crossfadeDuration = 1f; float crossfadeDuration = 1f;
_volumeFadeCoroutine = StartCoroutine(CrossfadeSounds(crossfadeDuration));
if (_continuousAudioSource != null)
{
_crossfadeTweenId = TweenHelper.FadeVolume(
_continuousAudioSource,
0f,
crossfadeDuration,
() =>
{
if (_continuousAudioSource != null)
_continuousAudioSource.Stop();
});
}
if (_emitterParticles != null) if (_emitterParticles != null)
{ {
@@ -231,7 +263,7 @@ namespace Unheimlich.Audio
/// This is the only place where _hasBeenTapped is reset to false, /// This is the only place where _hasBeenTapped is reset to false,
/// allowing the emitter to be used again in a future listening session. /// allowing the emitter to be used again in a future listening session.
/// </summary> /// </summary>
public void StopListening() public void StopListening(bool cancelConcentrationFade = true)
{ {
bool wasBeenTapped = _hasBeenTapped; bool wasBeenTapped = _hasBeenTapped;
@@ -244,8 +276,8 @@ namespace Unheimlich.Audio
if (_startListeningCoroutine != null) if (_startListeningCoroutine != null)
StopCoroutine(_startListeningCoroutine); StopCoroutine(_startListeningCoroutine);
if (_concentrationFadeCoroutine != null) if (cancelConcentrationFade)
StopCoroutine(_concentrationFadeCoroutine); TweenHelper.CancelTween(ref _concentrationFadeTweenId);
if (_audioSource != null && _audioSource.isPlaying) if (_audioSource != null && _audioSource.isPlaying)
_audioSource.Stop(); _audioSource.Stop();
@@ -400,59 +432,6 @@ namespace Unheimlich.Audio
Gamepad.current.SetMotorSpeeds(intensity, intensity); 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() private void UpdateVibration()
{ {
if (!_enableVibration || Gamepad.current == null) if (!_enableVibration || Gamepad.current == null)
@@ -50,6 +50,8 @@ namespace Unheimlich.Audio
private void RefreshListenablesList() private void RefreshListenablesList()
{ {
_allListenables = FindObjectsByType<AudioListenable>(FindObjectsSortMode.None); _allListenables = FindObjectsByType<AudioListenable>(FindObjectsSortMode.None);
if (!_showDebugInfo)
return;
Debug.Log($"Found {_allListenables.Length} AudioListenable objects in scene"); Debug.Log($"Found {_allListenables.Length} AudioListenable objects in scene");
} }
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a469312c19fe2fb40baa7642b1e73dcd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEngine;
namespace Unheimlich.Core
{
public abstract class Singleton<T> : 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() { }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 654e642c73c2b954fa86e0caac3f08c1
@@ -1,5 +1,6 @@
using UnityEngine; using UnityEngine;
using Unheimlich.Interactions.Core; using Unheimlich.Interactions.Core;
using Unheimlich.Utility;
namespace Unheimlich.Gameplay.Environment namespace Unheimlich.Gameplay.Environment
{ {
@@ -29,21 +30,17 @@ namespace Unheimlich.Gameplay.Environment
public void ToggleDoor() public void ToggleDoor()
{ {
LeanTween.cancel(gameObject); gameObject.CancelAllTweens();
if (!IsOpen) if (!IsOpen)
{ {
LeanTween.rotate(gameObject, _openRotation.eulerAngles, _animationDuration) TweenHelper.RotateLocal(gameObject, _openRotation.eulerAngles, _animationDuration, null, _easeType);
.setEase(_easeType);
IsOpen = true; IsOpen = true;
InteractionText = "Close"; InteractionText = "Close";
} }
else else
{ {
LeanTween.rotate(gameObject, _closedRotation.eulerAngles, _animationDuration) TweenHelper.RotateLocal(gameObject, _closedRotation.eulerAngles, _animationDuration, null, _easeType);
.setEase(_easeType);
IsOpen = false; IsOpen = false;
InteractionText = "Open"; InteractionText = "Open";
} }
@@ -2,6 +2,8 @@ using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Unheimlich.Audio; using Unheimlich.Audio;
using Unheimlich.Core;
using Unheimlich.Utility;
using InputManager = Unheimlich.Managers.PlayerInputManager; using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Gameplay.Player namespace Unheimlich.Gameplay.Player
@@ -16,14 +18,11 @@ namespace Unheimlich.Gameplay.Player
/// - Visual feedback (vignette, cursor, particles) /// - Visual feedback (vignette, cursor, particles)
/// - Preventing multiple taps on the same emitter /// - Preventing multiple taps on the same emitter
/// </summary> /// </summary>
public class DiapasonListeningHandler : MonoBehaviour public class DiapasonListeningHandler : Singleton<DiapasonListeningHandler>
{ {
// Animator parameter names for diapason animations
private const string ANIM_TRIGGER_TAP = "Tap"; private const string ANIM_TRIGGER_TAP = "Tap";
private const string ANIM_BOOL_VIBRATING = "IsVibrating"; private const string ANIM_BOOL_VIBRATING = "IsVibrating";
public static DiapasonListeningHandler Instance { get; private set; }
[Header("References")] [Header("References")]
[SerializeField] private GameObject _diapasonObject; [SerializeField] private GameObject _diapasonObject;
[SerializeField] private Animator _diapasonAnimator; [SerializeField] private Animator _diapasonAnimator;
@@ -72,23 +71,18 @@ namespace Unheimlich.Gameplay.Player
private AudioListenable _currentTargetEmitter; private AudioListenable _currentTargetEmitter;
private AudioSource _playerAudioSource; private AudioSource _playerAudioSource;
private Coroutine _listeningCoroutine; private Coroutine _listeningCoroutine;
private Coroutine _tweenCoroutine; private int _diapasonTweenId = -1;
private Coroutine _audioFadeCoroutine; private int _audioListenerFadeTweenId = -1;
private int _playerAudioFadeTweenId = -1;
private Vector2 _originalCameraRotation; private Vector2 _originalCameraRotation;
private float _originalAmbientVolume; private float _originalAmbientVolume;
private Vector3 _diapasonEquippedPosition; private Vector3 _diapasonEquippedPosition;
private Vector3 _diapasonHiddenPosition; private Vector3 _diapasonHiddenPosition;
private bool _canUseDiapason; 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<AudioSource>(); _playerAudioSource = GetComponent<AudioSource>();
if (_playerAudioSource == null) if (_playerAudioSource == null)
_playerAudioSource = gameObject.AddComponent<AudioSource>(); _playerAudioSource = gameObject.AddComponent<AudioSource>();
@@ -217,6 +211,9 @@ namespace Unheimlich.Gameplay.Player
/// </summary> /// </summary>
private void StartListeningMode() private void StartListeningMode()
{ {
if (_isFadingOutConcentration)
return;
if (_listeningCoroutine != null) if (_listeningCoroutine != null)
StopCoroutine(_listeningCoroutine); StopCoroutine(_listeningCoroutine);
@@ -240,17 +237,21 @@ namespace Unheimlich.Gameplay.Player
if (_diapasonObject != null) if (_diapasonObject != null)
{ {
if (_tweenCoroutine != null) TweenHelper.CancelTween(ref _diapasonTweenId);
StopCoroutine(_tweenCoroutine);
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonEquippedPosition, _diapasonTweenDuration, () => _diapasonTweenId = TweenHelper.MoveLocalWithCurve(
{ _diapasonObject,
if (_diapasonAnimator != null) _diapasonEquippedPosition,
_diapasonTweenDuration,
_diapasonTweenCurve,
() =>
{ {
_diapasonAnimator.enabled = true; if (_diapasonAnimator != null)
_diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false); {
} _diapasonAnimator.enabled = true;
})); _diapasonAnimator.SetBool(ANIM_BOOL_VIBRATING, false);
}
});
} }
PlayBreathIn(); PlayBreathIn();
@@ -294,9 +295,21 @@ namespace Unheimlich.Gameplay.Player
if (_playerAudioSource != null && _playerAudioSource.isPlaying) if (_playerAudioSource != null && _playerAudioSource.isPlaying)
{ {
if (_audioFadeCoroutine != null) TweenHelper.CancelTween(ref _playerAudioFadeTweenId);
StopCoroutine(_audioFadeCoroutine);
_audioFadeCoroutine = StartCoroutine(FadeOutAudio(_playerAudioSource, _audioFadeDuration)); float startVolume = _playerAudioSource.volume;
_playerAudioFadeTweenId = TweenHelper.FadeVolume(
_playerAudioSource,
0f,
_audioFadeDuration,
() =>
{
if (_playerAudioSource != null)
{
_playerAudioSource.Stop();
_playerAudioSource.volume = startVolume;
}
});
} }
if (_diapasonObject != null) if (_diapasonObject != null)
@@ -307,10 +320,13 @@ namespace Unheimlich.Gameplay.Player
_diapasonAnimator.enabled = false; _diapasonAnimator.enabled = false;
} }
if (_tweenCoroutine != null) TweenHelper.CancelTween(ref _diapasonTweenId);
StopCoroutine(_tweenCoroutine);
_tweenCoroutine = StartCoroutine(TweenDiapasonPosition(_diapasonHiddenPosition, _diapasonTweenDuration)); _diapasonTweenId = TweenHelper.MoveLocalWithCurve(
_diapasonObject,
_diapasonHiddenPosition,
_diapasonTweenDuration,
_diapasonTweenCurve);
} }
if (_diapasonParticles != null) if (_diapasonParticles != null)
@@ -331,13 +347,23 @@ namespace Unheimlich.Gameplay.Player
if (_currentTargetEmitter != null) if (_currentTargetEmitter != null)
{ {
_currentTargetEmitter.StopListening(); _currentTargetEmitter.SetConcentrating(false);
_currentTargetEmitter.StopListening(cancelConcentrationFade: false);
_currentTargetEmitter = null; _currentTargetEmitter = null;
StartCoroutine(ResetFadingFlagAfterDelay());
} }
_canUseDiapason = false; _canUseDiapason = false;
} }
private IEnumerator ResetFadingFlagAfterDelay()
{
_isFadingOutConcentration = true;
yield return new WaitForSeconds(0.5f);
_isFadingOutConcentration = false;
}
/// <summary> /// <summary>
/// Updates cursor position and camera panning during listening mode. /// Updates cursor position and camera panning during listening mode.
/// Supports both gamepad (look input) and mouse (screen edges). /// Supports both gamepad (look input) and mouse (screen edges).
@@ -492,55 +518,20 @@ namespace Unheimlich.Gameplay.Player
#region If needed #region If needed
private void AttenuateAmbientSound(bool enable) private void AttenuateAmbientSound(bool enable)
{ {
if (_audioFadeCoroutine != null) TweenHelper.CancelTween(ref _audioListenerFadeTweenId);
StopCoroutine(_audioFadeCoroutine);
_audioFadeCoroutine = StartCoroutine(FadeAmbientSound(enable));
}
private IEnumerator FadeAmbientSound(bool fadeOut)
{
//To do : Create audio tracks
AudioListener listener = FindFirstObjectByType<AudioListener>(); AudioListener listener = FindFirstObjectByType<AudioListener>();
if (listener == null) if (listener == null)
yield break; return;
float startVolume = AudioListener.volume; float targetVolume = enable ? _ambientAttenuationAmount : _originalAmbientVolume;
float targetVolume = fadeOut ? _ambientAttenuationAmount : _originalAmbientVolume;
if (!fadeOut) if (!enable)
_originalAmbientVolume = startVolume;
else
_originalAmbientVolume = AudioListener.volume; _originalAmbientVolume = AudioListener.volume;
float elapsed = 0f; _audioListenerFadeTweenId = TweenHelper.FadeVolume(gameObject, targetVolume, _audioFadeDuration);
while (elapsed < _audioFadeDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / _audioFadeDuration;
AudioListener.volume = Mathf.Lerp(startVolume, targetVolume, t);
yield return null;
}
AudioListener.volume = targetVolume;
} }
#endregion #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) 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() private void OnDrawGizmosSelected()
{ {
if (_playerCamera != null && IsListening) if (_playerCamera != null && IsListening)
@@ -1,22 +1,9 @@
using UnityEngine; using UnityEngine;
using Unheimlich.Core;
namespace Unheimlich.Gameplay.Player namespace Unheimlich.Gameplay.Player
{ {
/// <summary> public class Player : Singleton<Player>
/// Singleton player reference accessible globally throughout the game.
/// </summary>
public class Player : MonoBehaviour
{ {
public static Player Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
}
} }
} }
@@ -1,5 +1,6 @@
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Unheimlich.Core;
using Unheimlich.Gameplay.Portals; using Unheimlich.Gameplay.Portals;
using InputManager = Unheimlich.Managers.PlayerInputManager; using InputManager = Unheimlich.Managers.PlayerInputManager;
@@ -10,13 +11,11 @@ namespace Unheimlich.Gameplay.Player
/// Supports both mouse and gamepad input with adaptive sensitivity. /// Supports both mouse and gamepad input with adaptive sensitivity.
/// Includes aim assist for dialogue choices and camera shake effects. /// Includes aim assist for dialogue choices and camera shake effects.
/// </summary> /// </summary>
public class PlayerLook : MonoBehaviour public class PlayerLook : Singleton<PlayerLook>
{ {
private const float INPUT_DEADZONE = 0.1f; private const float INPUT_DEADZONE = 0.1f;
private const float MOUSE_DELTA_MULTIPLIER = 0.001f; private const float MOUSE_DELTA_MULTIPLIER = 0.001f;
public static PlayerLook Instance { get; private set; }
[Header("Components")] [Header("Components")]
[SerializeField] private Transform _playerCamera, _playerCameraPivot; [SerializeField] private Transform _playerCamera, _playerCameraPivot;
@@ -46,15 +45,8 @@ namespace Unheimlich.Gameplay.Player
private float _verticalRotation; private float _verticalRotation;
private bool _isUsingGamepad = false; private bool _isUsingGamepad = false;
private void Awake() protected override void OnAwake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
QualitySettings.vSyncCount = 0; QualitySettings.vSyncCount = 0;
Application.targetFrameRate = _frameRate; Application.targetFrameRate = _frameRate;
@@ -1,16 +1,12 @@
using UnityEngine; using UnityEngine;
using Unheimlich.Core;
using Unheimlich.Managers; using Unheimlich.Managers;
namespace Unheimlich.Gameplay.Player namespace Unheimlich.Gameplay.Player
{ {
/// <summary>
/// Handles player movement, jumping, and gravity using CharacterController.
/// Provides movement state information for other systems like camera head bob.
/// </summary>
[RequireComponent(typeof(CharacterController))] [RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour public class PlayerMovement : Singleton<PlayerMovement>
{ {
public static PlayerMovement Instance { get; private set; }
[Header("Settings")] [Header("Settings")]
[SerializeField] private float _moveSpeed = 5f; [SerializeField] private float _moveSpeed = 5f;
@@ -24,15 +20,8 @@ namespace Unheimlich.Gameplay.Player
private float _ySpeed; private float _ySpeed;
private Vector3 _moveDirection; private Vector3 _moveDirection;
private void Awake() protected override void OnAwake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
CharacterController = GetComponent<CharacterController>(); CharacterController = GetComponent<CharacterController>();
} }
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using Unheimlich.Data.Dialogues; using Unheimlich.Data.Dialogues;
using Unheimlich.Managers; using Unheimlich.Managers;
using Unheimlich.Gameplay.Player; using Unheimlich.Gameplay.Player;
using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords namespace Unheimlich.Interactions.FloatingWords
{ {
@@ -219,12 +220,15 @@ namespace Unheimlich.Interactions.FloatingWords
if (_choiceTextComponent == null) if (_choiceTextComponent == null)
return; return;
if (_textScaleTweenId >= 0) TweenHelper.CancelTween(ref _textScaleTweenId);
LeanTween.cancel(_textScaleTweenId);
_textScaleTweenId = LeanTween.scale(_choiceTextComponent.gameObject, Vector3.one * targetScale, duration) _textScaleTweenId = TweenHelper.ScaleLocal(
.setEase(LeanTweenType.easeOutQuad) _choiceTextComponent.gameObject,
.id; Vector3.one * targetScale,
duration,
null,
LeanTweenType.easeOutQuad
);
} }
public void ResetTextScale(float duration = 0.3f) public void ResetTextScale(float duration = 0.3f)
@@ -510,23 +514,20 @@ namespace Unheimlich.Interactions.FloatingWords
{ {
LeanTween.cancel(gameObject); LeanTween.cancel(gameObject);
if (_textScaleTweenId >= 0) TweenHelper.CancelTween(ref _textScaleTweenId);
LeanTween.cancel(_textScaleTweenId);
StopOrganicShake(); StopOrganicShake();
ResetSketchFade(); ResetSketchFade();
LeanTween.moveLocal(gameObject, _startLocalPos, duration) TweenHelper.MoveLocal(gameObject, _startLocalPos, duration, () => {
.setEase(LeanTweenType.easeOutQuad) _positionCaptured = false;
.setOnComplete(() => {
_positionCaptured = false;
if (DialogueManager.Instance != null) if (DialogueManager.Instance != null)
DialogueManager.Instance.UnlockFocus(); DialogueManager.Instance.UnlockFocus();
StartOrganicShake(); StartOrganicShake();
}); }, LeanTweenType.easeOutQuad);
transform.localScale = Vector3.one * _originalScale; transform.localScale = Vector3.one * _originalScale;
@@ -2,14 +2,10 @@ using UnityEngine;
using System.Collections.Generic; using System.Collections.Generic;
using Unheimlich.Data.Dialogues; using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player; using Unheimlich.Gameplay.Player;
using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords namespace Unheimlich.Interactions.FloatingWords
{ {
/// <summary>
/// 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.
/// </summary>
public static class FloatingWordsSpawner public static class FloatingWordsSpawner
{ {
private const float ROTATOR_SCALE = 0.01f; private const float ROTATOR_SCALE = 0.01f;
@@ -249,26 +245,32 @@ namespace Unheimlich.Interactions.FloatingWords
CanvasGroup canvasGroup = words[i].GetComponent<CanvasGroup>(); CanvasGroup canvasGroup = words[i].GetComponent<CanvasGroup>();
if (canvasGroup != null) if (canvasGroup != null)
{ {
LeanTween.value(words[i].gameObject, 1f, 0f, config.MergeDuration) TweenHelper.FadeCanvasGroup(canvasGroup, 0f, config.MergeDuration, null, LeanTweenType.easeInQuad);
.setOnUpdate((float val) => { canvasGroup.alpha = val; })
.setEaseInQuad();
} }
} }
} }
if (words.Count > 0 && words[0] != null) if (words.Count > 0 && words[0] != null)
{ {
LeanTween.scale(words[0].gameObject, Vector3.one * config.MergeScalePulse, config.MergeDuration / 2f) TweenHelper.ScaleLocal(
.setEaseOutBack() words[0].gameObject,
.setOnComplete(() => Vector3.one * config.MergeScalePulse,
config.MergeDuration / 2f,
() =>
{ {
if (words.Count > 0 && words[0] != null) if (words.Count > 0 && words[0] != null)
{ {
LeanTween.scale(words[0].gameObject, Vector3.one, config.MergeDuration / 2f) TweenHelper.ScaleLocal(
.setEaseInBack() words[0].gameObject,
.setOnComplete(() => onMergeComplete?.Invoke()); Vector3.one,
config.MergeDuration / 2f,
() => onMergeComplete.SafeInvoke(),
LeanTweenType.easeInBack
);
} }
}); },
LeanTweenType.easeOutBack
);
} }
} }
@@ -292,13 +294,11 @@ namespace Unheimlich.Interactions.FloatingWords
{ {
if (words[i] != null) if (words[i] != null)
{ {
LeanTween.cancel(words[i].gameObject); words[i].gameObject.CancelAllTweens();
CanvasGroup canvasGroup = words[i].GetComponent<CanvasGroup>(); CanvasGroup canvasGroup = words[i].GetComponent<CanvasGroup>();
if (canvasGroup != null) if (canvasGroup != null)
{ {
LeanTween.value(words[i].gameObject, canvasGroup.alpha, 1f, config.MergeDuration) TweenHelper.FadeCanvasGroup(canvasGroup, 1f, config.MergeDuration, null, LeanTweenType.easeOutQuad);
.setOnUpdate((float val) => { canvasGroup.alpha = val; })
.setEaseOutQuad();
} }
words[i].transform.localScale = Vector3.one; words[i].transform.localScale = Vector3.one;
} }
@@ -306,7 +306,7 @@ namespace Unheimlich.Interactions.FloatingWords
if (words.Count > 0 && words[0] != null) if (words.Count > 0 && words[0] != null)
{ {
LeanTween.cancel(words[0].gameObject); words[0].gameObject.CancelAllTweens();
words[0].transform.localScale = Vector3.one; words[0].transform.localScale = Vector3.one;
} }
} }
@@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using TMPro; using TMPro;
using Unheimlich.Utility;
namespace Unheimlich.Interactions.FloatingWords namespace Unheimlich.Interactions.FloatingWords
{ {
@@ -93,42 +94,41 @@ namespace Unheimlich.Interactions.FloatingWords
_isEffectActive = true; _isEffectActive = true;
_scribbleTweenId = LeanTween.value(gameObject, 0f, 1f, scribbleDuration) _scribbleTweenId = TweenHelper.AnimateMaterialFloat(
.setEase(LeanTweenType.easeInOutQuad) gameObject,
.setOnUpdate((float val) => { _scribbleMaterialInstance,
if (_scribbleMaterialInstance != null) ProgressID,
_scribbleMaterialInstance.SetFloat(ProgressID, val); 0f,
}) 1f,
.setOnComplete(() => { scribbleDuration,
() => {
_scribbleTweenId = -1; _scribbleTweenId = -1;
float ghostAlphaRatio = _ghostTextComponent != null ? (_originalGhostColor.a / _originalTextColor.a) : 0f;
_fadeTweenId = LeanTween.value(gameObject, _originalTextColor.a, finalAlpha, fadeDuration) _fadeTweenId = LeanTween.value(gameObject, _originalTextColor.a, finalAlpha, fadeDuration)
.setEase(LeanTweenType.easeInQuad) .setEase(LeanTweenType.easeInQuad)
.setOnUpdate((float val) => { .setOnUpdate((float val) => {
if (_textComponent != null) if (_textComponent != null)
{ {
Color textColor = _textComponent.color; _textComponent.color = _textComponent.color.WithAlpha(val);
textColor.a = val;
_textComponent.color = textColor;
} }
if (_ghostTextComponent != null) if (_ghostTextComponent != null)
{ {
Color ghostColor = _ghostTextComponent.color; _ghostTextComponent.color = _ghostTextComponent.color.WithAlpha(val * ghostAlphaRatio);
ghostColor.a = val * (_originalGhostColor.a / _originalTextColor.a);
_ghostTextComponent.color = ghostColor;
} }
if (_scribbleOverlay != null) if (_scribbleOverlay != null)
{ {
Color scribbleColor = _scribbleOverlay.color; _scribbleOverlay.color = _scribbleOverlay.color.WithAlpha(val);
scribbleColor.a = val;
_scribbleOverlay.color = scribbleColor;
} }
}) })
.setOnComplete(() => { .setOnComplete(() => {
_fadeTweenId = -1; _fadeTweenId = -1;
}).id; }).id;
}).id; },
LeanTweenType.easeInOutQuad);
} }
public void ResetEffect() public void ResetEffect()
@@ -141,17 +141,19 @@ namespace Unheimlich.Interactions.FloatingWords
if (currentProgress > 0f) if (currentProgress > 0f)
{ {
_scribbleTweenId = LeanTween.value(gameObject, currentProgress, 0f, 0.3f) _scribbleTweenId = TweenHelper.AnimateMaterialFloat(
.setEase(LeanTweenType.easeOutQuad) gameObject,
.setOnUpdate((float val) => { _scribbleMaterialInstance,
if (_scribbleMaterialInstance != null) ProgressID,
_scribbleMaterialInstance.SetFloat(ProgressID, val); currentProgress,
}) 0f,
.setOnComplete(() => { 0.3f,
() => {
_scribbleTweenId = -1; _scribbleTweenId = -1;
if (_scribbleOverlay != null) if (_scribbleOverlay != null)
_scribbleOverlay.gameObject.SetActive(false); _scribbleOverlay.gameObject.SetActive(false);
}).id; },
LeanTweenType.easeOutQuad);
} }
} }
@@ -161,34 +163,23 @@ namespace Unheimlich.Interactions.FloatingWords
if (currentAlpha < _originalTextColor.a) if (currentAlpha < _originalTextColor.a)
{ {
_fadeTweenId = LeanTween.value(gameObject, currentAlpha, _originalTextColor.a, 0.3f) float ghostAlphaRatio = _ghostTextComponent != null ? (_originalGhostColor.a / _originalTextColor.a) : 0f;
.setEase(LeanTweenType.easeOutQuad)
.setOnUpdate((float val) => {
if (_textComponent != null)
{
Color c = _textComponent.color;
c.a = val;
_textComponent.color = c;
}
if (_ghostTextComponent != null) _fadeTweenId = TweenHelper.FadeText(
{ gameObject,
Color c = _ghostTextComponent.color; _textComponent,
c.a = val * (_originalGhostColor.a / _originalTextColor.a); _ghostTextComponent,
_ghostTextComponent.color = c; _originalTextColor.a,
} 0.3f,
}) ghostAlphaRatio,
.setOnComplete(() => { () => { _fadeTweenId = -1; },
_fadeTweenId = -1; LeanTweenType.easeOutQuad);
}).id;
} }
} }
if (_scribbleOverlay != null) if (_scribbleOverlay != null)
{ {
Color c = _scribbleOverlay.color; _scribbleOverlay.color = _scribbleOverlay.color.WithAlpha(1f);
c.a = 1f;
_scribbleOverlay.color = c;
} }
_isEffectActive = false; _isEffectActive = false;
@@ -196,17 +187,8 @@ namespace Unheimlich.Interactions.FloatingWords
private void CancelActiveTweens() private void CancelActiveTweens()
{ {
if (_scribbleTweenId != -1) TweenHelper.CancelTween(ref _scribbleTweenId);
{ TweenHelper.CancelTween(ref _fadeTweenId);
LeanTween.cancel(_scribbleTweenId);
_scribbleTweenId = -1;
}
if (_fadeTweenId != -1)
{
LeanTween.cancel(_fadeTweenId);
_fadeTweenId = -1;
}
} }
private void OnDestroy() private void OnDestroy()
@@ -1,15 +1,12 @@
using UnityEngine; using UnityEngine;
using Unheimlich.Core;
using Unheimlich.Managers; using Unheimlich.Managers;
using Unheimlich.Utility;
namespace Unheimlich.Interactions.Core namespace Unheimlich.Interactions.Core
{ {
/// <summary> public class InteractionHandler : Singleton<InteractionHandler>
/// Detects interactable objects in front of the player camera using raycasts. {
/// Triggers interaction when player presses interact button while looking at an interactable.
/// </summary>
public class InteractionHandler : MonoBehaviour
{
public static InteractionHandler Instance { get; private set; }
[Header("Settings")] [Header("Settings")]
[SerializeField] private float _interactionRange = 1f; [SerializeField] private float _interactionRange = 1f;
@@ -20,49 +17,31 @@ namespace Unheimlich.Interactions.Core
private Camera _playerCamera; private Camera _playerCamera;
private float _raycastTimer; private float _raycastTimer;
private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f); protected override void OnAwake()
private void Awake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
_playerCamera = GetComponent<Camera>(); _playerCamera = GetComponent<Camera>();
} }
private void Update() private void Update()
{ {
_raycastTimer += Time.deltaTime; if (RaycastHelper.ThrottledRaycastComponent(
ref _raycastTimer,
if (_raycastTimer >= _raycastInterval) _raycastInterval,
_playerCamera,
_interactionRange,
_interactableLayers,
out InteractableBase interactable))
{ {
CheckForInteractable(); _currentInteractable = interactable;
_raycastTimer = 0f; }
else if (_raycastTimer == 0f)
{
_currentInteractable = null;
} }
HandleInteractionInput(); HandleInteractionInput();
} }
/// <summary>
/// Performs raycast from camera center to detect interactable objects within range.
/// Updates the current interactable reference for input handling.
/// </summary>
private void CheckForInteractable()
{
Ray ray = _playerCamera.ViewportPointToRay(ViewportCenter);
if (Physics.Raycast(ray, out RaycastHit hit, _interactionRange, _interactableLayers))
{
InteractableBase hitInteractable = hit.collider.GetComponent<InteractableBase>();
_currentInteractable = hitInteractable;
}
else
_currentInteractable = null;
}
private void HandleInteractionInput() private void HandleInteractionInput()
{ {
if (PlayerInputManager.Instance != null && if (PlayerInputManager.Instance != null &&
@@ -79,7 +58,8 @@ namespace Unheimlich.Interactions.Core
{ {
Gizmos.color = _currentInteractable != null ? Color.green : Color.red; 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); Gizmos.DrawRay(ray.origin, ray.direction * _interactionRange);
} }
} }
@@ -1,14 +1,18 @@
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Unheimlich.Core;
using Unheimlich.Gameplay.Items; using Unheimlich.Gameplay.Items;
using InputManager = Unheimlich.Managers.PlayerInputManager; using InputManager = Unheimlich.Managers.PlayerInputManager;
namespace Unheimlich.Interactions.Core namespace Unheimlich.Interactions.Core
{ {
public class PlayerInspectionHandler : MonoBehaviour /// <summary>
/// Handles player item inspection mode.
/// When inspecting, disables player movement and allows item rotation using look input.
/// </summary>
public class PlayerInspectionHandler : Singleton<PlayerInspectionHandler>
{ {
public static PlayerInspectionHandler Instance { get; private set; }
[Header("Components")] [Header("Components")]
[SerializeField] private Transform _inspectionHoldPoint; [SerializeField] private Transform _inspectionHoldPoint;
@@ -20,20 +24,6 @@ namespace Unheimlich.Interactions.Core
private Coroutine _interactionListener; private Coroutine _interactionListener;
private bool _isUsingGamepad = false; private bool _isUsingGamepad = false;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
}
/// <summary>
/// Toggles inspection mode for an item. When inspecting, disables player movement
/// and allows item rotation using look input until interaction key is pressed again.
/// </summary>
public void ToggleInspection(InspectableItem item) public void ToggleInspection(InspectableItem item)
{ {
if (!IsInspecting) if (!IsInspecting)
@@ -4,6 +4,7 @@ using Unheimlich.Gameplay.Player;
using Unheimlich.Interactions.Dialogues; using Unheimlich.Interactions.Dialogues;
using Unheimlich.Interactions.FloatingWords; using Unheimlich.Interactions.FloatingWords;
using Unheimlich.Data.Dialogues; using Unheimlich.Data.Dialogues;
using Unheimlich.Utility;
namespace Unheimlich.Managers namespace Unheimlich.Managers
{ {
@@ -32,8 +33,6 @@ namespace Unheimlich.Managers
private readonly float _focusRetentionTime; private readonly float _focusRetentionTime;
private readonly float _fallbackShakeIntensity; private readonly float _fallbackShakeIntensity;
private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
public event Action<DialogueChoiceData> OnChoiceMade; public event Action<DialogueChoiceData> OnChoiceMade;
public DialogueFocusSystem( public DialogueFocusSystem(
@@ -96,16 +95,13 @@ namespace Unheimlich.Managers
if (_mainCamera == null) if (_mainCamera == null)
return; return;
Ray ray = _mainCamera.ViewportPointToRay(ViewportCenter);
ChoiceFocusHandler hitHandler = null; ChoiceFocusHandler hitHandler = null;
bool raycastHitChoice = Physics.Raycast(ray, out RaycastHit hit, _focusRange, _choiceLayer);
if (raycastHitChoice) if (RaycastHelper.RaycastFromCameraCenter(_mainCamera, _focusRange, _choiceLayer, out RaycastHit hit))
{ {
hitHandler = hit.collider.GetComponent<ChoiceFocusHandler>(); hitHandler = hit.collider.GetComponent<ChoiceFocusHandler>();
if (_playerLook != null) if (_playerLook != null && hitHandler != null)
_playerLook.ApplyAimAssist(hit.collider.bounds.center); _playerLook.ApplyAimAssist(hit.collider.bounds.center);
} }
@@ -1,5 +1,6 @@
using UnityEngine; using UnityEngine;
using System; using System;
using Unheimlich.Core;
using Unheimlich.Data.Dialogues; using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player; using Unheimlich.Gameplay.Player;
using Unheimlich.UI; using Unheimlich.UI;
@@ -8,9 +9,8 @@ using System.Collections.Generic;
namespace Unheimlich.Managers namespace Unheimlich.Managers
{ {
public class DialogueManager : MonoBehaviour public class DialogueManager : Singleton<DialogueManager>
{ {
public static DialogueManager Instance { get; private set; }
[Header("State")] [Header("State")]
public bool IsInDialogue = false; public bool IsInDialogue = false;
@@ -51,14 +51,8 @@ namespace Unheimlich.Managers
private float _autoCloseTimer = 0f; private float _autoCloseTimer = 0f;
private bool _isWaitingForAutoClose = false; private bool _isWaitingForAutoClose = false;
private void Awake() protected override void OnAwake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
} }
private void Start() private void Start()
@@ -1,5 +1,6 @@
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Unheimlich.Core;
using Unheimlich.Gameplay.Player; using Unheimlich.Gameplay.Player;
namespace Unheimlich.Managers namespace Unheimlich.Managers
@@ -10,7 +11,7 @@ namespace Unheimlich.Managers
/// Supports different input contexts (Normal, Dialogue, Listening) to control which inputs are active. /// Supports different input contexts (Normal, Dialogue, Listening) to control which inputs are active.
/// </summary> /// </summary>
[RequireComponent(typeof(PlayerInput))] [RequireComponent(typeof(PlayerInput))]
public class PlayerInputManager : MonoBehaviour public class PlayerInputManager : Singleton<PlayerInputManager>
{ {
/// <summary> /// <summary>
/// Defines different input contexts that control which player actions are enabled. /// 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) Listening // Only look enabled, movement disabled (for diapason camera pan)
} }
public static PlayerInputManager Instance { get; private set; }
[Header("Input Values (Read only)")] [Header("Input Values (Read only)")]
public Vector2 MoveInput; public Vector2 MoveInput;
public Vector2 LookInput; public Vector2 LookInput;
@@ -44,15 +43,8 @@ namespace Unheimlich.Managers
private PlayerLook _playerLook; private PlayerLook _playerLook;
private CameraHeadBob _headBob; private CameraHeadBob _headBob;
private void Awake() protected override void OnAwake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
IsInputEnabled = true; IsInputEnabled = true;
PlayerInput playerInput = GetComponent<PlayerInput>(); PlayerInput playerInput = GetComponent<PlayerInput>();
@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Unheimlich.Core;
using Unheimlich.Gameplay.Portals; using Unheimlich.Gameplay.Portals;
namespace Unheimlich.Managers namespace Unheimlich.Managers
@@ -9,9 +10,8 @@ namespace Unheimlich.Managers
/// Monitors player distance to portals and loads/unloads room instances accordingly. /// Monitors player distance to portals and loads/unloads room instances accordingly.
/// Supports room pooling (keeping inactive rooms in memory) and distance-based activation. /// Supports room pooling (keeping inactive rooms in memory) and distance-based activation.
/// </summary> /// </summary>
public class PortalRoomManager : MonoBehaviour public class PortalRoomManager : Singleton<PortalRoomManager>
{ {
public static PortalRoomManager Instance { get; private set; }
[Header("Settings")] [Header("Settings")]
[SerializeField] private float _loadDistance = 15f; [SerializeField] private float _loadDistance = 15f;
@@ -44,16 +44,6 @@ namespace Unheimlich.Managers
public bool BoundsCalculated = false; public bool BoundsCalculated = false;
} }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
}
private void Start() private void Start()
{ {
_player = GameObject.FindGameObjectWithTag("Player")?.transform; _player = GameObject.FindGameObjectWithTag("Player")?.transform;
@@ -1,13 +1,13 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal; using UnityEngine.Rendering.Universal;
using Unheimlich.Core;
namespace Unheimlich.UI namespace Unheimlich.UI
{ {
[RequireComponent(typeof(Camera))] [RequireComponent(typeof(Camera))]
public class DialogueBlurEffectURP : MonoBehaviour public class DialogueBlurEffectURP : Singleton<DialogueBlurEffectURP>
{ {
public static DialogueBlurEffectURP Instance { get; private set; }
[Header("Volume Settings")] [Header("Volume Settings")]
[SerializeField] private Volume _globalVolume; [SerializeField] private Volume _globalVolume;
@@ -43,15 +43,8 @@ namespace Unheimlich.UI
private Camera _baseCam; private Camera _baseCam;
private Camera _dialogueCam; private Camera _dialogueCam;
void Awake() protected override void OnAwake()
{ {
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
_baseCam = GetComponent<Camera>(); _baseCam = GetComponent<Camera>();
if (_fadeCurve == null || _fadeCurve.keys.Length == 0) if (_fadeCurve == null || _fadeCurve.keys.Length == 0)
@@ -1,11 +1,12 @@
using UnityEngine; using UnityEngine;
using TMPro; using TMPro;
using Unheimlich.Core;
using Unheimlich.Utility;
namespace Unheimlich.UI namespace Unheimlich.UI
{ {
public class DialogueScreenUI : MonoBehaviour public class DialogueScreenUI : Singleton<DialogueScreenUI>
{ {
public static DialogueScreenUI Instance { get; private set; }
[Header("UI References")] [Header("UI References")]
[SerializeField] private GameObject _dialoguePanel; [SerializeField] private GameObject _dialoguePanel;
@@ -17,16 +18,8 @@ namespace Unheimlich.UI
private CanvasGroup _canvasGroup; 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) 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) public void Show(string speaker, string dialogue)
{ {
if (_speakerText != null) if (_speakerText != null)
@@ -73,18 +60,19 @@ namespace Unheimlich.UI
{ {
if (_canvasGroup != null && _dialoguePanel != null) if (_canvasGroup != null && _dialoguePanel != null)
{ {
LeanTween.cancel(_dialoguePanel); TweenHelper.FadeCanvasGroup(
LeanTween.alphaCanvas(_canvasGroup, 0f, _fadeOutDuration) _canvasGroup,
.setOnComplete(() => 0f,
{ _fadeOutDuration,
() => {
Hide(); Hide();
onComplete?.Invoke(); onComplete.SafeInvoke();
}); });
} }
else else
{ {
Hide(); Hide();
onComplete?.Invoke(); onComplete.SafeInvoke();
} }
} }
} }
+26 -28
View File
@@ -4,6 +4,7 @@ using TMPro;
using Unheimlich.Data.Dialogues; using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player; using Unheimlich.Gameplay.Player;
using Unheimlich.Interactions.FloatingWords; using Unheimlich.Interactions.FloatingWords;
using Unheimlich.Utility;
namespace Unheimlich.UI namespace Unheimlich.UI
{ {
@@ -43,6 +44,10 @@ namespace Unheimlich.UI
private DialogueDisplayMode _currentDisplayMode = DialogueDisplayMode.World; private DialogueDisplayMode _currentDisplayMode = DialogueDisplayMode.World;
private int _fadeCanvasTweenId = -1;
private int _rotationTweenId = -1;
private List<int> _floatingTweenIds = new List<int>();
private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f); private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
private void Awake() private void Awake()
@@ -91,7 +96,7 @@ namespace Unheimlich.UI
if (_dialogueCanvasGroup != null) if (_dialogueCanvasGroup != null)
{ {
LeanTween.cancel(_dialoguePanel); TweenHelper.CancelTween(ref _fadeCanvasTweenId);
_dialogueCanvasGroup.alpha = 1f; _dialogueCanvasGroup.alpha = 1f;
} }
} }
@@ -125,9 +130,12 @@ namespace Unheimlich.UI
{ {
if (_dialogueCanvasGroup != null && _dialoguePanel != null) if (_dialogueCanvasGroup != null && _dialoguePanel != null)
{ {
LeanTween.cancel(_dialoguePanel); TweenHelper.CancelTween(ref _fadeCanvasTweenId);
LeanTween.alphaCanvas(_dialogueCanvasGroup, 0f, _fadeOutDuration) _fadeCanvasTweenId = TweenHelper.FadeCanvasGroup(
.setOnComplete(() => _dialogueCanvasGroup,
0f,
_fadeOutDuration,
() =>
{ {
HideDialogueBox(); HideDialogueBox();
onComplete?.Invoke(); onComplete?.Invoke();
@@ -247,35 +255,18 @@ namespace Unheimlich.UI
float amplitude = _currentAnimationSettings.FloatingAmplitude; float amplitude = _currentAnimationSettings.FloatingAmplitude;
float duration = _currentAnimationSettings.FloatingDuration; float duration = _currentAnimationSettings.FloatingDuration;
float offset = index * 0.2f; float delay = index * 0.2f;
Vector3 startPos = choice.transform.localPosition; Vector3 startPos = choice.transform.localPosition;
LeanTween.value(choice, 0f, Mathf.PI * 2f, duration) int tweenId = TweenHelper.FloatAnimation(choice, startPos, amplitude, duration, delay, Axis.Y);
.setDelay(offset) _floatingTweenIds.Add(tweenId);
.setOnUpdate((float val) => {
if (choice != null)
{
Vector3 newPos = startPos;
newPos.y += Mathf.Sin(val) * amplitude;
choice.transform.localPosition = newPos;
}
})
.setLoopClamp()
.setEase(LeanTweenType.linear);
} }
public void RotationAnimation(float degreesPerSecond = 60f) public void RotationAnimation(float degreesPerSecond = 60f)
{ {
if (degreesPerSecond <= 0f) TweenHelper.CancelTween(ref _rotationTweenId);
return; _rotationTweenId = TweenHelper.RotateAround(ChoiceContainer, _currentRotationAxis, degreesPerSecond);
float duration = 360f / degreesPerSecond;
LeanTween.cancel(ChoiceContainer);
LeanTween.rotateAroundLocal(ChoiceContainer, _currentRotationAxis, 360f, duration)
.setLoopClamp()
.setEase(LeanTweenType.linear);
} }
/// <summary> /// <summary>
@@ -318,10 +309,17 @@ namespace Unheimlich.UI
_currentAnchor = null; _currentAnchor = null;
_currentAnimationSettings = null; _currentAnimationSettings = null;
_activeChoices.Clear(); _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) foreach (Transform child in ChoiceContainer.transform)
{ {
LeanTween.cancel(child.gameObject);
Destroy(child.gameObject); Destroy(child.gameObject);
} }
} }
@@ -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<T>(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<T>();
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<T>(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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f37b9866154109148b3f7d2d3c121c36
@@ -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
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f70df97fd94f86a4aa2cb00353624ba4
@@ -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<T>(this System.Action<T> 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);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b5c8821940a7678458f26015ee167fdf