using CharacterSystem; using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Serialization; public class DualAvatarController : MonoBehaviour { [SerializeField, FormerlySerializedAs("_characterPrefabs")] private List _characterDefinitions; [SerializeField, FormerlySerializedAs("AvatarsParent")] private Transform _avatarsParent; [SerializeField, FormerlySerializedAs("baseHiddenLocalPosition")] private Vector2 _player1HiddenLocalPosition; [SerializeField, FormerlySerializedAs("baseShownLocalPosition")] private Vector2 _player1ShownLocalPosition; [SerializeField] private Vector2 _player2HiddenLocalPosition = new(27f, -5.86f); [SerializeField] private Vector2 _player2ShownLocalPosition = new(11.4f, -5.86f); [SerializeField] private float _characterScale = 10f; [SerializeField, FormerlySerializedAs("CharacterMovementSpeed")] private float _characterMovementSpeed = 15f; private const string AppearTrigger = "Appear"; private const string SleepTrigger = "Sleep"; private const string WinTrigger = "Win"; private readonly Dictionary _characters = new(); private Coroutine _player1Movement; private Coroutine _player2Movement; private bool _isShowing; private bool _isVisible; private bool _hideRequested; private int _loadGeneration; public static DualAvatarController Instance { get; private set; } private void OnDestroy() { _loadGeneration++; StopMovement(); ReleaseCharacters(); if (Instance == this) Instance = null; } public void Initialize() { Instance = this; if (_characterDefinitions == null || _characterDefinitions.Count < 2 || _characterDefinitions[0] == null || _characterDefinitions[1] == null) { Debug.LogError("Для PvP не назначены определения персонажей 6 и 7.", this); } } public async void ShowCharacters(Action onComplete = null) { if (_isVisible) { onComplete?.Invoke(); return; } if (_isShowing) return; _isShowing = true; _hideRequested = false; int generation = ++_loadGeneration; try { await LoadCharactersAsync(); } catch (Exception exception) { _isShowing = false; Debug.LogException(exception, this); return; } if (generation != _loadGeneration || _hideRequested) { _isShowing = false; ReleaseCharacters(); return; } StopMovement(); PrepareForAppearance(_characters[Team.Player1]); PrepareForAppearance(_characters[Team.Player2]); int appearedCharacters = 0; Action onAppeared = () => { appearedCharacters++; if (appearedCharacters < 2) return; _isShowing = false; _isVisible = true; onComplete?.Invoke(); }; _player1Movement = StartCoroutine(MoveLocal( _characters[Team.Player1], GetShownPosition(Team.Player1), true, onAppeared)); _player2Movement = StartCoroutine(MoveLocal( _characters[Team.Player2], GetShownPosition(Team.Player2), true, onAppeared)); } public void PlayWin(Team winner) { if (!_characters.TryGetValue(winner, out PvpCharacter character) || !character.Animator.enabled) return; character.Animator.ResetTrigger(WinTrigger); character.Animator.SetTrigger(WinTrigger); } public void HideCharacters() { _hideRequested = true; _loadGeneration++; if (_characters.Count < 2) { _isShowing = false; _isVisible = false; ReleaseCharacters(); return; } StopMovement(); _isShowing = false; _isVisible = false; int hiddenCharacters = 0; Action onHidden = () => { hiddenCharacters++; if (hiddenCharacters >= 2) ReleaseCharacters(); }; _player1Movement = StartCoroutine(MoveLocal( _characters[Team.Player1], GetHiddenPosition(Team.Player1), false, onHidden)); _player2Movement = StartCoroutine(MoveLocal( _characters[Team.Player2], GetHiddenPosition(Team.Player2), false, onHidden)); } private async Task LoadCharactersAsync() { if (_characters.Count == 2) return; ReleaseCharacters(); Task player1Task = LoadCharacterAsync(_characterDefinitions[0], Team.Player1); Task player2Task = LoadCharacterAsync(_characterDefinitions[1], Team.Player2); PvpCharacter[] characters; try { characters = await Task.WhenAll(player1Task, player2Task); } catch { ReleaseCompletedCharacter(player1Task); ReleaseCompletedCharacter(player2Task); throw; } _characters[Team.Player1] = characters[0]; _characters[Team.Player2] = characters[1]; } private async Task LoadCharacterAsync(CharacterDefinition definition, Team team) { AsyncOperationHandle handle = Addressables.InstantiateAsync( definition.Prefab.RuntimeKey, _avatarsParent, false, true); await handle.Task; if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null) { if (handle.IsValid()) Addressables.Release(handle); throw new InvalidOperationException($"Не удалось загрузить PvP-персонажа {definition.CharacterName}."); } GameObject characterObject = handle.Result; Animator animator = characterObject.GetComponentInChildren(true); if (animator == null) { Addressables.ReleaseInstance(handle); throw new InvalidOperationException($"У PvP-персонажа {definition.CharacterName} отсутствует Animator."); } MessageBox messageBox = characterObject.GetComponentInChildren(true); if (messageBox != null) messageBox.gameObject.SetActive(false); characterObject.transform.localScale = new Vector3(_characterScale, _characterScale, 1f); characterObject.transform.localPosition = GetHiddenPosition(team); animator.enabled = false; return new PvpCharacter(characterObject.transform, animator, handle); } private void PrepareForAppearance(PvpCharacter character) { character.Animator.enabled = true; character.Animator.ResetTrigger(AppearTrigger); character.Animator.ResetTrigger(WinTrigger); character.Animator.ResetTrigger(SleepTrigger); character.Animator.SetTrigger(SleepTrigger); } private void StopMovement() { if (_player1Movement != null) StopCoroutine(_player1Movement); if (_player2Movement != null) StopCoroutine(_player2Movement); _player1Movement = null; _player2Movement = null; } private void ReleaseCharacters() { foreach (PvpCharacter character in _characters.Values) { if (character.Handle.IsValid()) Addressables.ReleaseInstance(character.Handle); } _characters.Clear(); } private static void ReleaseCompletedCharacter(Task loadingTask) { if (!loadingTask.IsCompletedSuccessfully || !loadingTask.Result.Handle.IsValid()) return; Addressables.ReleaseInstance(loadingTask.Result.Handle); } private Vector3 GetHiddenPosition(Team team) => team == Team.Player1 ? _player1HiddenLocalPosition : _player2HiddenLocalPosition; private Vector3 GetShownPosition(Team team) => team == Team.Player1 ? _player1ShownLocalPosition : _player2ShownLocalPosition; private IEnumerator MoveLocal( PvpCharacter character, Vector3 targetLocalPosition, bool playAppear, Action onComplete) { while (character.Transform.localPosition != targetLocalPosition) { character.Transform.localPosition = Vector3.MoveTowards( character.Transform.localPosition, targetLocalPosition, _characterMovementSpeed * Time.deltaTime); yield return null; } if (playAppear) { character.Animator.ResetTrigger(AppearTrigger); character.Animator.SetTrigger(AppearTrigger); } else { character.Animator.enabled = false; } onComplete?.Invoke(); } private sealed class PvpCharacter { public PvpCharacter( Transform transform, Animator animator, AsyncOperationHandle handle) { Transform = transform; Animator = animator; Handle = handle; } public Transform Transform { get; } public Animator Animator { get; } public AsyncOperationHandle Handle { get; } } }