316 lines
9.6 KiB
C#
316 lines
9.6 KiB
C#
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<CharacterDefinition> _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<Team, PvpCharacter> _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<PvpCharacter> player1Task = LoadCharacterAsync(_characterDefinitions[0], Team.Player1);
|
||
Task<PvpCharacter> 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<PvpCharacter> LoadCharacterAsync(CharacterDefinition definition, Team team)
|
||
{
|
||
AsyncOperationHandle<GameObject> 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<Animator>(true);
|
||
|
||
if (animator == null)
|
||
{
|
||
Addressables.ReleaseInstance(handle);
|
||
throw new InvalidOperationException($"У PvP-персонажа {definition.CharacterName} отсутствует Animator.");
|
||
}
|
||
|
||
MessageBox messageBox = characterObject.GetComponentInChildren<MessageBox>(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<PvpCharacter> 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<GameObject> handle)
|
||
{
|
||
Transform = transform;
|
||
Animator = animator;
|
||
Handle = handle;
|
||
}
|
||
|
||
public Transform Transform { get; }
|
||
public Animator Animator { get; }
|
||
public AsyncOperationHandle<GameObject> Handle { get; }
|
||
}
|
||
}
|