using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; using UnityEngine.UI; namespace Unheimlich.UI { [RequireComponent(typeof(TextMeshProUGUI))] public class PerLetterFontShifter : MonoBehaviour { #region Serialized Fields [Header("Fonts")] [SerializeField] private TMP_FontAsset[] _fonts; [Header("Timing")] [SerializeField] private float _shiftInterval = 3f; [SerializeField] private float _minLetterDelay = 0.05f; [SerializeField] private float _maxLetterDelay = 0.25f; [Header("Style")] [SerializeField] private float _fontSize = 36f; [SerializeField] private Color _textColor = Color.white; #endregion #region Private Fields private TextMeshProUGUI _originalTmp; private string _originalText; private readonly List _letterObjects = new List(); private Coroutine _shiftLoopRoutineHandle; #endregion #region Unity Lifecycle Methods private void Awake() { _originalTmp = GetComponent(); _originalText = _originalTmp.text; _originalTmp.enabled = false; } private void OnEnable() { BuildLetterObjects(); _shiftLoopRoutineHandle = StartCoroutine(ShiftLoopRoutine()); } private void OnDisable() { if (_shiftLoopRoutineHandle != null) StopCoroutine(_shiftLoopRoutineHandle); } private void OnDestroy() { DestroyLetterObjects(); } #endregion #region Public Methods public void SetText(string newText) { _originalText = newText; BuildLetterObjects(); } #endregion #region Private Methods private void BuildLetterObjects() { DestroyLetterObjects(); foreach (char character in _originalText) { GameObject letterGo = new GameObject($"Letter_{character}"); letterGo.transform.SetParent(transform, false); TextMeshProUGUI letterTmp = letterGo.AddComponent(); letterTmp.text = character.ToString(); letterTmp.fontSize = _fontSize; letterTmp.color = _textColor; letterTmp.alignment = TextAlignmentOptions.Center; _letterObjects.Add(letterTmp); } ArrangeLetters(); ShuffleFonts(); } private void DestroyLetterObjects() { foreach (TextMeshProUGUI letter in _letterObjects) { if (letter != null) Destroy(letter.gameObject); } _letterObjects.Clear(); } private void ArrangeLetters() { HorizontalLayoutGroup layout = gameObject.GetComponent(); if (layout == null) layout = gameObject.AddComponent(); layout.childAlignment = TextAnchor.MiddleCenter; layout.childForceExpandWidth = false; layout.childForceExpandHeight = false; layout.childControlWidth = true; layout.childControlHeight = true; layout.spacing = 0f; foreach (TextMeshProUGUI letter in _letterObjects) { ContentSizeFitter fitter = letter.gameObject.GetComponent(); if (fitter == null) fitter = letter.gameObject.AddComponent(); fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; } } private void ShuffleFonts() { if (_fonts == null || _fonts.Length == 0) { Debug.LogWarning($"[{nameof(PerLetterFontShifter)}] No fonts assigned.", this); return; } StartCoroutine(ShuffleStaggeredRoutine()); } private List GetShuffledIndices() { List indices = new List(); for (int i = 0; i < _letterObjects.Count; i++) indices.Add(i); for (int i = indices.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); (indices[i], indices[j]) = (indices[j], indices[i]); } return indices; } #endregion #region Coroutines private IEnumerator ShiftLoopRoutine() { while (true) { yield return new WaitForSeconds(_shiftInterval); ShuffleFonts(); } } private IEnumerator ShuffleStaggeredRoutine() { List shuffledIndices = GetShuffledIndices(); foreach (int index in shuffledIndices) { TextMeshProUGUI letter = _letterObjects[index]; if (letter.text == " " || letter.text == "\n") continue; letter.font = _fonts[Random.Range(0, _fonts.Length)]; yield return new WaitForSeconds(Random.Range(_minLetterDelay, _maxLetterDelay)); } } #endregion #if UNITY_EDITOR [ContextMenu("Test Shuffle Now")] private void TestShuffle() => ShuffleFonts(); #endif } }