using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// /// Точка маршрута для UI-элемента /// [Serializable] public class UIPathPoint { public Vector2 position; public float duration = 1f; public string animationStateName = ""; // Имя состояния анимации (walkk, stop, Idle, victory, defeat) [Range(0f, 5f)] public float delayAtPoint = 0f; } /// /// Менеджер для управления движущимися UI-персонажами /// Создаёт экземпляры и запускает их по маршрутам /// public class UIAnimatedMover : MonoBehaviour { [Header("Конфигурации персонажей")] [SerializeField] private UICharacterPathConfig[] characterConfigs; // Массив конфигураций разных персонажей [Header("Настройки")] [SerializeField] private Transform spawnParent; // Родитель для создаваемых экземпляров private List activeInstances = new List(); private void Awake() { if (spawnParent == null) spawnParent = transform; } /// /// Инициализировать и запустить персонажа по маршруту /// /// Конфигурация персонажа с маршрутами /// Тип маршрута (Default по умолчанию) /// Созданный экземпляр персонажа public UICharacterInstance InitializeCharacter(UICharacterPathConfig config, PathType pathType = PathType.Default) { if (config == null) { Debug.LogError("UIAnimatedMover: config is null!"); return null; } if (config.characterPrefab == null) { Debug.LogError($"UIAnimatedMover: characterPrefab is null in config {config.name}!"); return null; } // Получаем маршрут по ключу PathData pathData = config.GetPath(pathType); if (pathData == null || pathData.points.Count == 0) { Debug.LogError($"UIAnimatedMover: path not found for type {pathType} in config {config.name}!"); return null; } // Создаём экземпляр персонажа GameObject instanceObj = Instantiate(config.characterPrefab, spawnParent); RectTransform rectTransform = instanceObj.GetComponent(); if (rectTransform == null) { Debug.LogError($"UIAnimatedMover: characterPrefab must have RectTransform!"); Destroy(instanceObj); return null; } // Получаем или добавляем UIAnimationStateSwitcher UIAnimationStateSwitcher animationSwitcher = instanceObj.GetComponent(); if (animationSwitcher == null) { animationSwitcher = instanceObj.AddComponent(); } // Устанавливаем Override Controller if (config.overrideController != null) { animationSwitcher.SetOverrideController(config.overrideController); } // Устанавливаем в первую точку маршрута rectTransform.anchoredPosition = pathData.points[0].position; // Создаём экземпляр управления UICharacterInstance instance = new UICharacterInstance { gameObject = instanceObj, rectTransform = rectTransform, animationSwitcher = animationSwitcher, pathData = pathData, config = config }; activeInstances.Add(instance); // Запускаем движение StartCoroutine(MoveAlongPath(instance)); return instance; } /// /// Инициализировать персонажа по индексу из массива конфигураций /// /// Индекс конфигурации в массиве (0, 1, 2...) /// Тип маршрута (Default по умолчанию) public UICharacterInstance InitializeByIndex(int configIndex, PathType pathType = PathType.Default) { if (characterConfigs == null || characterConfigs.Length == 0) { Debug.LogError("UIAnimatedMover: characterConfigs массив пуст!"); return null; } if (configIndex < 0 || configIndex >= characterConfigs.Length) { Debug.LogError($"UIAnimatedMover: индекс {configIndex} вне диапазона массива (0-{characterConfigs.Length - 1})"); return null; } return InitializeCharacter(characterConfigs[configIndex], pathType); } /// /// Инициализировать первого персонажа из массива (индекс 0) /// public UICharacterInstance InitializeDefault() { return InitializeByIndex(0, PathType.Default); } /// /// Получить конфигурацию персонажа по индексу /// public UICharacterPathConfig GetConfig(int index) { if (characterConfigs == null || index < 0 || index >= characterConfigs.Length) return null; return characterConfigs[index]; } /// /// Количество доступных конфигураций персонажей /// public int GetConfigCount() { return characterConfigs != null ? characterConfigs.Length : 0; } /// /// Остановить и удалить экземпляр персонажа /// public void RemoveCharacter(UICharacterInstance instance) { if (instance == null) return; if (instance.moveCoroutine != null) { StopCoroutine(instance.moveCoroutine); } activeInstances.Remove(instance); if (instance.gameObject != null) { Destroy(instance.gameObject); } } /// /// Удалить все активные экземпляры /// public void RemoveAllCharacters() { for (int i = activeInstances.Count - 1; i >= 0; i--) { RemoveCharacter(activeInstances[i]); } activeInstances.Clear(); } private IEnumerator MoveAlongPath(UICharacterInstance instance) { instance.isMoving = true; PathData pathData = instance.pathData; do { for (int i = 0; i < pathData.points.Count; i++) { UIPathPoint point = pathData.points[i]; // Переключаем анимацию если указана if (!string.IsNullOrEmpty(point.animationStateName) && instance.animationSwitcher != null) { instance.animationSwitcher.SetState(point.animationStateName); } // Движение к точке (пропускаем первую точку если это начало) if (i > 0 || instance.hasStartedMoving) { yield return MoveToTargetPoint(instance, point); } else { instance.hasStartedMoving = true; } // Достигли точки instance.InvokeReachPoint(i); // Задержка в точке if (point.delayAtPoint > 0) yield return new WaitForSeconds(point.delayAtPoint); } } while (pathData.loop && instance.gameObject != null); instance.isMoving = false; instance.InvokePathComplete(); } private IEnumerator MoveToTargetPoint(UICharacterInstance instance, UIPathPoint point) { Vector2 startPos = instance.rectTransform.anchoredPosition; Vector2 targetPos = point.position; float elapsed = 0f; while (elapsed < point.duration) { if (instance.gameObject == null) yield break; float t = elapsed / point.duration; instance.rectTransform.anchoredPosition = Vector2.Lerp(startPos, targetPos, t); elapsed += Time.deltaTime; yield return null; } // Финальная позиция if (instance.rectTransform != null) { instance.rectTransform.anchoredPosition = targetPos; } } private void OnDestroy() { RemoveAllCharacters(); } } /// /// Экземпляр движущегося персонажа /// public class UICharacterInstance { public GameObject gameObject; public RectTransform rectTransform; public UIAnimationStateSwitcher animationSwitcher; public PathData pathData; public UICharacterPathConfig config; public bool isMoving; public bool hasStartedMoving; public Coroutine moveCoroutine; // События public event Action OnPathComplete; public event Action OnReachPoint; // Внутренние методы для вызова событий internal void InvokePathComplete() => OnPathComplete?.Invoke(); internal void InvokeReachPoint(int pointIndex) => OnReachPoint?.Invoke(pointIndex); }