using System; using System.Collections; using TMPro; using UnityEngine; using UnityEngine.UI; public class TextInput : MonoBehaviour { [Header("Ключ prefs")] [SerializeField] private string key; [SerializeField] private bool loadData; [SerializeField] private bool saveDataOnClose; [Header("Настройки генерации поля")] [Tooltip("Якорь лево-низ")][SerializeField] private GameObject SlotPrefab; [SerializeField] private RectTransform slotsRoot; [SerializeField] private int slotsCount = 10; [SerializeField] private float rootPaddingX = 10; [SerializeField] private float rootPaddingY = 10; [SerializeField] private float gapSize = 10; [Header("Настройки анимации")] [SerializeField] private float animationSpeed; [SerializeField] private float fontSizeNormal = 20; [SerializeField] private float fontSizeHighlighted = 25; [SerializeField] private Color fontColorNormal; [SerializeField] private Color fontColorHighlighted; [SerializeField] private float slotSizeMultiplier = 1.1f; [SerializeField] private Color slotColorNormal; [SerializeField] private Color slotColorHighlighted; [Header("Звуки")] [SerializeField] private AudioClip switchSlotSound; [SerializeField] private AudioClip switchLetterSound; [SerializeField] private AudioClip acceptSound; [SerializeField] private AudioClip openSound; private Vector2 slotSizeNormal; //расчет при генерации [Header("Алфавит")] [SerializeField] private string alphabetString = "0123456789"; //из него генерирует массив символов [Header("Скорость переключения букв")] [SerializeField] private float MaxSpeedLetterPerSecond = 8; [SerializeField] private float MinSpeedLetterPerSecond = 3; [SerializeField] private float MaxSpeedTime = 3; [SerializeField] private float MinSpeedTime = 0.5f; private float switchButtonPressedTime = 0; private float letterSwitchSpeed = 0; private float switchDelayRest = 1; //тут массивы для доступа private string[] alphabet; // не забыь добавить пустую строку при генерации (пробел лучше) private RectTransform[] slotRects; private Graphic[] slotGraphics; private TextMeshProUGUI[] slotLabels; private Coroutine animationCoroutine; private int currentSlotIndex = 0; private int currentLetterindex = 0; private TextMeshProUGUI currentLabel => slotLabels[currentSlotIndex]; public void Initialize() { Generate(); LoadSavedData(); SoundManager.instance.playClip(openSound); animationCoroutine = StartCoroutine(SlotsAnimation()); } public string AcceptAndClose() { string result = GetText(); if (!string.IsNullOrEmpty(key) && saveDataOnClose) { PlayerPrefs.SetString(key, result); SettingsManager.OnSettingsChanged?.Invoke(); } StopCoroutine(animationCoroutine); SoundManager.instance.playClip(acceptSound); Clear(); return result; } private void Generate() { //Считаем размер одного слота Vector2 spaceForSlotsWithGaps = slotsRoot.sizeDelta - Vector2.right * rootPaddingX * 2 - Vector2.up * rootPaddingY * 2; int gapsCount = slotsCount - 1; float horizontalSpaceForSlots = spaceForSlotsWithGaps.x - gapsCount * gapSize; float slotWidth = horizontalSpaceForSlots / slotsCount; slotSizeNormal = new Vector2(slotWidth, spaceForSlotsWithGaps.y); //Расставляем слоты в root //у префаба будет якорь лево/низ Vector2 startLocalPoint = new Vector2(rootPaddingX, rootPaddingY); slotRects = new RectTransform[slotsCount]; slotGraphics = new Graphic[slotsCount]; slotLabels = new TextMeshProUGUI[slotsCount]; Vector2 slotOffset = (slotSizeNormal.x + gapSize) * Vector2.right;//на сколько сдвигать каждый след. слот for (int i = 0; i < slotsCount; i++) { //Создаем слот GameObject slot = Instantiate(SlotPrefab, slotsRoot); //филлим массивы ссылок slotRects[i] = slot.GetComponent(); slotGraphics[i] = slot.GetComponent(); slotLabels[i] = slot.transform.GetComponentInChildren(); //Выставляем размеры, координаты, цвета, шрифты slotRects[i].sizeDelta = slotSizeNormal; slotRects[i].anchoredPosition = startLocalPoint + slotOffset * i + slotSizeNormal / 2; slotGraphics[i].color = slotColorNormal; slotLabels[i].color = fontColorNormal; slotLabels[i].text = ""; slotLabels[i].fontSize = fontSizeNormal; } //Собираем алфавит alphabet = new string[alphabetString.Length + 1];//+1 для пробела for (int i = 0; i < alphabetString.Length; i++) { alphabet[i] = alphabetString[i].ToString(); } alphabet[^1] = " "; //Настраиваем индексы currentSlotIndex = 0; } private void LoadSavedData() { string data = ""; if (loadData) data = PlayerPrefs.GetString(key, ""); // Очищаем все слоты for (int i = 0; i < slotsCount; i++) { slotLabels[i].text = " "; } // Заполняем сохраненными данными if (!string.IsNullOrEmpty(data)) { int minLength = Mathf.Min(data.Length, slotsCount); for (int i = 0; i < minLength; i++) { slotLabels[i].text = data[i].ToString(); } } } public void SwitchLetter(int continiousDirection) { continiousDirection = Mathf.Clamp(continiousDirection, -1, 1); if (continiousDirection == 0)//сброс переключения { switchDelayRest = 0; switchButtonPressedTime = 0; return; } if (switchDelayRest < 0) { //переключение currentLetterindex = (currentLetterindex + continiousDirection + alphabet.Length) % alphabet.Length; currentLabel.text = alphabet[currentLetterindex]; recalculateRest(); Debug.Log($"Переключение остаток: {switchDelayRest}"); } switchDelayRest -= Time.deltaTime; switchButtonPressedTime += Time.deltaTime; void recalculateRest() { float k = (MaxSpeedLetterPerSecond - MinSpeedLetterPerSecond) / (MaxSpeedTime - MinSpeedTime); float b = MinSpeedLetterPerSecond - k * MinSpeedTime; letterSwitchSpeed = k * switchButtonPressedTime + b; letterSwitchSpeed = Mathf.Clamp(letterSwitchSpeed, MinSpeedLetterPerSecond, MaxSpeedLetterPerSecond); switchDelayRest = 1 / letterSwitchSpeed; } } public void SwitchSlot(int discreteDirection) { if (discreteDirection == 0) return; currentSlotIndex = (currentSlotIndex + discreteDirection + slotsCount) % slotsCount; string letter = currentLabel.text; currentLetterindex = Array.IndexOf(alphabet, letter); } public void Clear() { foreach (RectTransform rect in slotRects) { Destroy(rect.gameObject); } } public string GetText() { System.Text.StringBuilder sb = new(); foreach (TextMeshProUGUI label in slotLabels) { // Если текст пустой строка, считаем это пробелом if (!string.IsNullOrEmpty(label.text)) { sb.Append(label.text); } } return sb.ToString().Trim(); } IEnumerator SlotsAnimation() { float scaleSpeed = (slotSizeMultiplier - 1) / animationSpeed; float slotColorSpeed = Vector4.Distance(slotColorNormal, slotColorHighlighted) / animationSpeed; float fontColorSpeed = Vector4.Distance(fontColorNormal, fontColorHighlighted) / animationSpeed; float fontSizeSpeed = (fontSizeHighlighted - fontSizeNormal) / animationSpeed; while (true) { for (int i = 0; i < slotsCount; i++) { if (i == currentSlotIndex) //подсвечиваем { slotRects[i].localScale = Vector3.MoveTowards(slotRects[i].localScale, Vector3.one * slotSizeMultiplier, scaleSpeed); slotGraphics[i].color = Vector4.MoveTowards(slotGraphics[i].color, slotColorHighlighted, slotColorSpeed); slotLabels[i].color = Vector4.MoveTowards(slotLabels[i].color, fontColorHighlighted, fontColorSpeed); slotLabels[i].fontSize = Mathf.MoveTowards(slotLabels[i].fontSize, fontSizeHighlighted, fontSizeSpeed); } else //убираем подсветку { slotRects[i].localScale = Vector3.MoveTowards(slotRects[i].localScale, Vector3.one, scaleSpeed); slotGraphics[i].color = Vector4.MoveTowards(slotGraphics[i].color, slotColorNormal, slotColorSpeed); slotLabels[i].color = Vector4.MoveTowards(slotLabels[i].color, fontColorNormal, fontColorSpeed); slotLabels[i].fontSize = Mathf.MoveTowards(slotLabels[i].fontSize, fontSizeNormal, fontSizeSpeed); } } yield return null; } } }