256 lines
9.1 KiB
C#
256 lines
9.1 KiB
C#
using UnityEngine;
|
|
using System;
|
|
using Unheimlich.Features.Player;
|
|
using Unheimlich.Features.Dialogue;
|
|
using Unheimlich.Features.Dialogue.UI;
|
|
using Unheimlich.Features.Dialogue.Data;
|
|
using Unheimlich.Core.Utils;
|
|
|
|
namespace Unheimlich.Features.Dialogue
|
|
{
|
|
/// <summary>
|
|
/// Manages gaze-based focus detection for dialogue choices using raycasts.
|
|
/// </summary>
|
|
public class DialogueFocusSystem
|
|
{
|
|
private readonly DialogueManager _manager;
|
|
private readonly Camera _mainCamera;
|
|
private readonly CameraEffects _cameraEffects;
|
|
|
|
private ChoiceFocusHandler _currentFocusedHandler;
|
|
private float _focusTimer = 0f;
|
|
private float _retentionTimer = 0f;
|
|
private float _raycastTimer = 0f;
|
|
private bool _isFocusLocked = false;
|
|
|
|
private readonly LayerMask _choiceLayer;
|
|
private readonly float _focusRange;
|
|
private readonly float _focusTime;
|
|
private readonly float _raycastInterval;
|
|
private readonly float _recoveryDuration;
|
|
private readonly float _focusRetentionTime;
|
|
private readonly float _fallbackShakeIntensity;
|
|
|
|
public event Action<DialogueChoiceData> OnChoiceMade;
|
|
|
|
public DialogueFocusSystem(
|
|
DialogueManager manager,
|
|
Camera mainCamera,
|
|
CameraEffects cameraEffects,
|
|
LayerMask choiceLayer,
|
|
float focusRange,
|
|
float focusTime,
|
|
float raycastInterval,
|
|
float recoveryDuration,
|
|
float focusRetentionTime,
|
|
float fallbackShakeIntensity)
|
|
{
|
|
_manager = manager;
|
|
_mainCamera = mainCamera;
|
|
_cameraEffects = cameraEffects;
|
|
_choiceLayer = choiceLayer;
|
|
_focusRange = focusRange;
|
|
_focusTime = focusTime;
|
|
_raycastInterval = raycastInterval;
|
|
_recoveryDuration = recoveryDuration;
|
|
_focusRetentionTime = focusRetentionTime;
|
|
_fallbackShakeIntensity = fallbackShakeIntensity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the focus detection system. Should be called every frame during active dialogue.
|
|
/// Performs raycasts at configured intervals to detect choice focus.
|
|
/// </summary>
|
|
public void Update()
|
|
{
|
|
if (!_manager.IsInDialogue || !_manager.CurrentDialogueUI.AreChoicesVisible())
|
|
return;
|
|
|
|
_raycastTimer += Time.deltaTime;
|
|
if (_raycastTimer >= _raycastInterval)
|
|
{
|
|
CheckForChoiceFocus();
|
|
_raycastTimer = 0f;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs raycast from camera center to detect focused dialogue choices.
|
|
/// Manages focus state transitions, retention timers, visual feedback, and choice selection.
|
|
/// Implements stabilization to prevent rapid focus switching between choices.
|
|
/// </summary>
|
|
private void CheckForChoiceFocus()
|
|
{
|
|
if (_isFocusLocked)
|
|
{
|
|
if (_retentionTimer > 0f)
|
|
_retentionTimer -= Time.deltaTime;
|
|
else
|
|
_isFocusLocked = false;
|
|
return;
|
|
}
|
|
|
|
if (_mainCamera == null)
|
|
return;
|
|
|
|
ChoiceFocusHandler hitHandler = null;
|
|
|
|
if (RaycastHelper.RaycastFromCameraCenter(_mainCamera, _focusRange, _choiceLayer, out RaycastHit hit))
|
|
{
|
|
hitHandler = hit.collider.GetComponent<ChoiceFocusHandler>();
|
|
|
|
if (_cameraEffects != null && hitHandler != null)
|
|
_cameraEffects.ApplyAimAssist(hit.collider.bounds.center);
|
|
}
|
|
|
|
if (hitHandler != _currentFocusedHandler)
|
|
{
|
|
_retentionTimer -= Time.deltaTime;
|
|
|
|
if (hitHandler != null && _retentionTimer <= 0f)
|
|
{
|
|
if (_currentFocusedHandler != null)
|
|
{
|
|
_currentFocusedHandler.ResetFeedback(_recoveryDuration);
|
|
ResetAllUnfocusEffects();
|
|
}
|
|
|
|
_currentFocusedHandler = hitHandler;
|
|
_focusTimer = 0f;
|
|
_retentionTimer = _focusRetentionTime;
|
|
|
|
if (_cameraEffects != null)
|
|
_cameraEffects.LockAim(true);
|
|
|
|
ApplyUnfocusEffectToOthers(hitHandler);
|
|
|
|
DialogueAnimationSettings settings = _manager.CurrentAnimationSettings;
|
|
bool shouldRotate = settings == null || settings.EnableRotation;
|
|
if (shouldRotate && _manager.CurrentDialogueUI != null)
|
|
{
|
|
float focusSpeed = settings != null ? settings.FocusRotationSpeed : 60f;
|
|
_manager.CurrentDialogueUI.RotationAnimation(focusSpeed);
|
|
}
|
|
}
|
|
}
|
|
else if (hitHandler != null)
|
|
{
|
|
_retentionTimer = _focusRetentionTime;
|
|
}
|
|
|
|
bool isFocusActive = _currentFocusedHandler != null && (_retentionTimer > 0f);
|
|
|
|
if (isFocusActive)
|
|
{
|
|
_focusTimer += Time.deltaTime;
|
|
float progress = _focusTimer / _focusTime;
|
|
|
|
_currentFocusedHandler.ApplyFocusFeedback(progress);
|
|
|
|
DialogueAnimationSettings currentSettings = _manager.CurrentAnimationSettings;
|
|
bool shouldShake = currentSettings == null || currentSettings.EnableCameraShakeOnFocus;
|
|
|
|
if (shouldShake && _cameraEffects != null)
|
|
{
|
|
float shakeIntensity = currentSettings != null ? currentSettings.CameraShakeIntensity : _fallbackShakeIntensity;
|
|
float shakeSmoothness = currentSettings != null ? currentSettings.CameraShakeSmoothness : 10f;
|
|
_cameraEffects.ShakeCamera(progress / shakeIntensity, shakeSmoothness);
|
|
}
|
|
|
|
if (_focusTimer >= _focusTime)
|
|
{
|
|
ChoiceFocusHandler selectedHandler = _currentFocusedHandler;
|
|
|
|
if (selectedHandler.DialogueData != null)
|
|
{
|
|
OnChoiceMade?.Invoke(selectedHandler.DialogueData);
|
|
DialogueNode nextNode = selectedHandler.GetNextNode();
|
|
_manager.GoToNextNode(nextNode);
|
|
}
|
|
|
|
DialogueAnimationSettings settings = _manager.CurrentAnimationSettings;
|
|
bool shouldRotate = settings == null || settings.EnableRotation;
|
|
if (shouldRotate && _manager.CurrentDialogueUI != null)
|
|
{
|
|
float normalSpeed = settings != null ? settings.NormalRotationSpeed : 36f;
|
|
_manager.CurrentDialogueUI.RotationAnimation(normalSpeed);
|
|
}
|
|
|
|
ResetAnimationTimers();
|
|
ResetAllUnfocusEffects();
|
|
|
|
selectedHandler.ResetFeedback(_recoveryDuration);
|
|
|
|
if (_cameraEffects != null)
|
|
_cameraEffects.LockAim(false);
|
|
}
|
|
}
|
|
else if (_currentFocusedHandler != null && _retentionTimer <= 0f)
|
|
{
|
|
_currentFocusedHandler.ResetFeedback(_recoveryDuration);
|
|
ResetAllUnfocusEffects();
|
|
|
|
DialogueAnimationSettings settings = _manager.CurrentAnimationSettings;
|
|
bool shouldRotate = settings == null || settings.EnableRotation;
|
|
if (shouldRotate && _manager.CurrentDialogueUI != null)
|
|
{
|
|
float normalSpeed = settings != null ? settings.NormalRotationSpeed : 36f;
|
|
_manager.CurrentDialogueUI.RotationAnimation(normalSpeed);
|
|
}
|
|
|
|
ResetAnimationTimers();
|
|
|
|
if (_cameraEffects != null)
|
|
_cameraEffects.LockAim(false);
|
|
}
|
|
}
|
|
|
|
private void ApplyUnfocusEffectToOthers(ChoiceFocusHandler focusedHandler)
|
|
{
|
|
if (_manager.CurrentDialogueUI == null || _manager.CurrentDialogueUI.ChoiceContainer == null)
|
|
return;
|
|
|
|
DialogueAnimationSettings settings = _manager.CurrentAnimationSettings;
|
|
float shakeSlowdown = settings != null ? settings.UnfocusedShakeSpeedMultiplier : 0.3f;
|
|
|
|
foreach (Transform child in _manager.CurrentDialogueUI.ChoiceContainer.transform)
|
|
{
|
|
ChoiceFocusHandler handler = child.GetComponent<ChoiceFocusHandler>();
|
|
if (handler != null && handler != focusedHandler)
|
|
{
|
|
handler.SetShakeSpeedMultiplier(shakeSlowdown);
|
|
handler.ApplySketchFade();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetAllUnfocusEffects()
|
|
{
|
|
if (_manager.CurrentDialogueUI == null || _manager.CurrentDialogueUI.ChoiceContainer == null)
|
|
return;
|
|
|
|
foreach (Transform child in _manager.CurrentDialogueUI.ChoiceContainer.transform)
|
|
{
|
|
ChoiceFocusHandler handler = child.GetComponent<ChoiceFocusHandler>();
|
|
if (handler != null)
|
|
{
|
|
handler.SetShakeSpeedMultiplier(1f);
|
|
handler.ResetSketchFade();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetAnimationTimers()
|
|
{
|
|
_currentFocusedHandler = null;
|
|
_focusTimer = 0f;
|
|
_retentionTimer = 0f;
|
|
}
|
|
|
|
public void UnlockFocus()
|
|
{
|
|
_isFocusLocked = false;
|
|
}
|
|
}
|
|
}
|