chip-gameV3/Assets/Scripts/Gameplay/Avatars/DualAvatarController.cs

316 lines
9.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
}
}