using UnityEngine; using System.Collections.Generic; using Unheimlich.Data.Dialogues; using Unheimlich.Gameplay.Player; using OctoCore.Localization; namespace Unheimlich.Interactions.FloatingWords { public class ObjectDescriptionTrigger : MonoBehaviour { [Header("Description")] [Tooltip("Localized description text (recommended)")] [SerializeField] private OctoLocalizedTextData _descriptionTextLocalized; [Tooltip("Fallback description text if localized version is not set")] [SerializeField] private string _descriptionText = "Mysterious Object"; [Header("Settings")] [SerializeField] private ObjectDescriptionSettings _settings; [Header("Anchor")] [SerializeField] private Transform _descriptionAnchor; [Header("Prefab")] [SerializeField] private ChoiceFocusHandler _wordPrefab; private Transform _playerTransform; private Camera _mainCamera; private List _spawnedWords = new List(); private GameObject _wordsContainer; private GameObject _wordsRotator; private bool _isActive = false; private bool _isMerged = false; private bool _hasBeenMergedOnce = false; private bool _hasCompletedMergeOnce = false; private Vector3[] _originalSpawnPositions; private Vector3[] _basePositions; private float _currentConvergenceRadius; public bool IsAnimationActive => _isActive && (!_settings.MergeOnce || !_hasCompletedMergeOnce); public bool IsInMergeOnceMode => _settings != null && _settings.MergeOnce; void Start() { _playerTransform = Player.Instance.transform; _mainCamera = Camera.main; if (_playerTransform == null) Debug.LogWarning($"[ObjectDescription] No player found for {gameObject.name}"); if (_descriptionAnchor == null) _descriptionAnchor = transform; if (_settings == null) { Debug.LogError($"[ObjectDescription] No settings assigned for {gameObject.name}"); enabled = false; } } void Update() { if (_playerTransform == null || _settings == null) return; float distanceToPlayer = Vector3.Distance(_playerTransform.position, transform.position); if (!_isActive && distanceToPlayer <= _settings.ActivationDistance) { if (!_settings.MergeOnce || !_hasCompletedMergeOnce) SpawnWords(); } else if (_isActive && distanceToPlayer >= _settings.DeactivationDistance) DespawnWords(); if (_isActive) { if (!_settings.MergeOnce || !_hasBeenMergedOnce) { UpdateWordConvergence(); UpdateContainerRotation(); } } } private void SpawnWords() { if (_wordPrefab == null) { Debug.LogError($"[ObjectDescription] No word prefab assigned for {gameObject.name}"); return; } _isActive = true; _isMerged = false; _currentConvergenceRadius = _settings.SpawnRadius; _wordsContainer = new GameObject($"{_descriptionText}_Container"); _wordsContainer.transform.position = _descriptionAnchor.position; _wordsContainer.transform.rotation = _descriptionAnchor.rotation; _wordsRotator = FloatingWordsSpawner.CreateWordsContainer(_descriptionText, _descriptionAnchor, _settings); _wordsRotator.transform.SetParent(_wordsContainer.transform, false); ObjectDescriptionTriggerReference triggerRef = _wordsRotator.AddComponent(); triggerRef.Trigger = this; string descText = _descriptionTextLocalized != null ? OctoLocalization.GetLocalizedText(_descriptionTextLocalized) : _descriptionText; List dummyChoices = new List(); for (int i = 0; i < _settings.WordCount; i++) { dummyChoices.Add(new DialogueChoiceData { ChoiceText = _descriptionTextLocalized, NextNode = null }); } _spawnedWords = FloatingWordsSpawner.SpawnByMode( dummyChoices, _wordPrefab, _wordsRotator.transform, _settings, out _originalSpawnPositions ); _basePositions = new Vector3[_spawnedWords.Count]; for (int i = 0; i < _spawnedWords.Count; i++) _basePositions[i] = _spawnedWords[i].transform.localPosition; } private void DespawnWords() { _isActive = false; _isMerged = false; foreach (var word in _spawnedWords) { if (word != null) Destroy(word.gameObject); } _spawnedWords.Clear(); if (_wordsContainer != null) Destroy(_wordsContainer); // Debug.Log($"[ObjectDescription] Despawned words for '{descriptionText}'"); } private void UpdateWordConvergence() { if (_mainCamera == null || _spawnedWords.Count == 0) return; Vector2 screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f); Ray ray = _mainCamera.ScreenPointToRay(screenCenter); RaycastHit hit; int layerMask = ~LayerMask.GetMask("Words", "UI"); bool isHoveringObject = false; if (Physics.Raycast(ray, out hit, 100f, layerMask)) { if (hit.collider.gameObject == gameObject || hit.collider.transform.IsChildOf(transform)) isHoveringObject = true; } float targetRadius = isHoveringObject ? _settings.MinConvergenceRadius : _settings.SpawnRadius; _currentConvergenceRadius = Mathf.Lerp(_currentConvergenceRadius, targetRadius, Time.deltaTime * _settings.ConvergenceSpeed); if (isHoveringObject && !_isMerged && _currentConvergenceRadius <= _settings.MinConvergenceRadius * 5f) MergeWords(); else if (!isHoveringObject && _isMerged) { if (!_settings.MergeOnce || !_hasBeenMergedOnce) UnmergeWords(); } if (_isMerged) return; float convergenceFactor = Mathf.InverseLerp(0f, _settings.SpawnRadius, _currentConvergenceRadius); for (int i = 0; i < _spawnedWords.Count; i++) { if (_spawnedWords[i] == null) continue; Vector3 targetLocalPos = GetConvergenceTargetPosition(_originalSpawnPositions[i], _currentConvergenceRadius); _basePositions[i] = Vector3.Lerp( _basePositions[i], targetLocalPos, Time.deltaTime * _settings.ConvergenceSpeed ); _spawnedWords[i].transform.localPosition = _basePositions[i] + _spawnedWords[i].GetShakeOffset(convergenceFactor); } } private Vector3 GetConvergenceTargetPosition(Vector3 originalPosition, float radius) { if (radius < 0.01f) return Vector3.zero; switch (_settings.RotationAxis) { case RotationAxis.Forward: Vector2 xyNormalized = new Vector2(originalPosition.x, originalPosition.y - _settings.SpawnHeightOffset).normalized; return new Vector3( xyNormalized.x * radius, xyNormalized.y * radius + _settings.SpawnHeightOffset, originalPosition.z ); case RotationAxis.Up: Vector2 xzNormalized = new Vector2(originalPosition.x, originalPosition.z).normalized; return new Vector3( xzNormalized.x * radius, originalPosition.y, xzNormalized.y * radius ); case RotationAxis.Right: Vector2 yzNormalized = new Vector2(originalPosition.y - _settings.SpawnHeightOffset, originalPosition.z).normalized; return new Vector3( originalPosition.x, yzNormalized.x * radius + _settings.SpawnHeightOffset, yzNormalized.y * radius ); default: return originalPosition.normalized * radius; } } private void MergeWords() { if (_isMerged) return; _isMerged = true; _hasBeenMergedOnce = true; FloatingWordsSpawner.MergeWords(_spawnedWords, _settings, () => { if (_settings.MergeOnce) { LeanTween.delayedCall(_settings.MergeOnceDisplayDuration, () => { FadeOutAndComplete(); }); } }); } private void FadeOutAndComplete() { if (_spawnedWords.Count > 0 && _spawnedWords[0] != null) { CanvasGroup canvasGroup = _spawnedWords[0].GetComponent(); if (canvasGroup != null) { LeanTween.value(_spawnedWords[0].gameObject, 1f, 0f, _settings.MergeDuration) .setOnUpdate((float val) => { canvasGroup.alpha = val; }) .setEaseInQuad() .setOnComplete(() => { _hasCompletedMergeOnce = true; DespawnWords(); }); } } } private void UnmergeWords() { if (!_isMerged) return; _isMerged = false; FloatingWordsSpawner.UnmergeWords(_spawnedWords, _settings); } private void UpdateContainerRotation() { FloatingWordsSpawner.UpdateContainerRotation(_wordsContainer, _wordsRotator, _playerTransform, _settings); } public ObjectDescriptionSettings GetSettings() { return _settings; } void OnDrawGizmosSelected() { if (_settings == null) return; Transform anchor = _descriptionAnchor != null ? _descriptionAnchor : transform; Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, _settings.ActivationDistance); Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, _settings.DeactivationDistance); Gizmos.color = Color.green; Gizmos.DrawWireSphere(anchor.position, _settings.SpawnRadius); Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(anchor.position, _settings.MinConvergenceRadius); } } }