Улучшить PvP-аватары, цвета поля и настройку таймеров

This commit is contained in:
Сергей Бондарь 2026-08-27 12:56:31 +03:00
parent 16ab9e47f9
commit f86eed2d70
7 changed files with 373 additions and 524 deletions

View File

@ -2174,6 +2174,7 @@ MonoBehaviour:
difficultyPreset: {fileID: 11400000, guid: 1c381d73d66d6254d8e6326516732d27, type: 2}
useDummyBots: 0
minThinkingTime: 2
_candidatePauseDuration: 0.35
dummyPreset: {fileID: 0}
--- !u!4 &167531122
Transform:
@ -2811,10 +2812,10 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 858a4cf6151305b4482c687bed474ee2, type: 3}
m_Name:
m_EditorClassIdentifier:
player1Timer: {fileID: 0}
player2Timer: {fileID: 0}
vsBotTime: 180
pvpTime: 120
_player1Timer: {fileID: 0}
_player2Timer: {fileID: 0}
_vsBotTime: 180
_pvpTime: 120
--- !u!114 &259182289
MonoBehaviour:
m_ObjectHideFlags: 0
@ -16538,6 +16539,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Board
pointer: {fileID: 1615046714}
_pvpInnerColor: {r: 0.6313726, g: 0.9960785, b: 0.15294118, a: 1}
_pvpOutlineColor: {r: 1, g: 0.92549026, b: 0.003921569, a: 1}
_pvpCellsColor: {r: 1, g: 0.92549026, b: 0.003921569, a: 1}
--- !u!114 &1768372456
MonoBehaviour:
m_ObjectHideFlags: 0
@ -17249,13 +17253,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 2aaf52d670e0dc845807a4d8ba998208, type: 3}
m_Name:
m_EditorClassIdentifier:
characterPrefabs:
- {fileID: 7423209641351575543, guid: 8e12c1fb0b1bab349b318c743c3c43f3, type: 3}
- {fileID: 4076845309818962033, guid: ce14ac5889418f54cb543bcbdc0cb6ac, type: 3}
AvatarsParent: {fileID: 0}
baseHiddenLocalPosition: {x: -2000, y: -200}
baseShownLocalPosition: {x: -600, y: -200}
CharacterMovementSpeed: 5000
_characterPrefabs:
- {fileID: 6069554613919803763, guid: a6618e61e181953458cf6769884c4403, type: 3}
- {fileID: 6156076713058116187, guid: b268c439fa90d6542ab0c876cd677661, type: 3}
_avatarsParent: {fileID: 0}
_player1HiddenLocalPosition: {x: -20, y: -5.86}
_player1ShownLocalPosition: {x: -5, y: -5.86}
_player2HiddenLocalPosition: {x: 27, y: -5.86}
_player2ShownLocalPosition: {x: 11.4, y: -5.86}
_characterScale: 10
_characterMovementSpeed: 15
--- !u!4 &1851050357
Transform:
m_ObjectHideFlags: 0

View File

@ -107,6 +107,8 @@ public class GameManager : MonoBehaviour
if (mode == GameMode.pve)
CharactersController.I.ApplyFirstCharacterColorsToBoard();
else
Board.Instance.ApplyPvpColors();
menuGameplayTransit.GameplayOpened += OnGameplayOpened;
menuGameplayTransit.GotoGameplay();
@ -126,7 +128,10 @@ public class GameManager : MonoBehaviour
CharactersController.I.StartSession(StartRound);
}
else
{
DualAvatarController.Instance?.ShowCharacters();
StartRound();
}
}
private void OnApplicationQuit() => ComNativeWrapper.Serial_CloseAll();
@ -154,6 +159,10 @@ public class GameManager : MonoBehaviour
CharactersController.I.HideCharacter();
CharactersController.ResetSystem();
}
else
{
DualAvatarController.Instance?.HideCharacters();
}
OnGameFinished?.Invoke(gameMode);
menuGameplayTransit.GotoMenu();
@ -280,8 +289,6 @@ public class GameManager : MonoBehaviour
{
timerManager.SetActiveTimer(Team.Player1);
}
else if (gameMode == GameMode.pvp)
DualAvatarController.Instance?.ShowCharacters();
VitrinaView.Instance.ActivateIfNeed();
DetermineNextTurn(switchPlayer: false);

View File

@ -1,177 +1,196 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using CharacterSystem;
using UnityEngine;
using UnityEngine.Serialization;
public class DualAvatarController : MonoBehaviour
{
const string APPEAR_TRIGGER = "Appear"; //не используется пока в анимацияхъ
const string IDLE_TRIGGER = "Idle";
const string CHIPSPAWN_TRIGGER = "ChipSpawn";
const string WIN_TRIGGER = "Win";
const string LOSE_TRIGGER = "Lose";
[SerializeField, FormerlySerializedAs("characterPrefabs")] private List<GameObject> _characterPrefabs;
[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;
const Team PLAYER1 = Team.Player1;
const Team PLAYER2 = Team.Player2;
[SerializeField] private List<GameObject> characterPrefabs;
Dictionary<Team, Avatar> avatars = new();
[SerializeField] Transform AvatarsParent;
private AvatarEventBus bus;
//Для левого аватара
[SerializeField] private Vector2 baseHiddenLocalPosition;
[SerializeField] private Vector2 baseShownLocalPosition;
[SerializeField] private float CharacterMovementSpeed = 5000;
public Avatar CurrentAvatar => avatars[GameManager.Instance.currentPlayer];
public static DualAvatarController Instance;
private bool spawned = false;
public static DualAvatarController Instance { get; private set; }
public void Initialize()
{
return;
Instance = this;
if (!spawned)
if (_characters.Count > 0)
return;
if (_characterPrefabs == null || _characterPrefabs.Count < 2 ||
_characterPrefabs[0] == null || _characterPrefabs[1] == null)
{
avatars[PLAYER1] = CreateAvatar(characterPrefabs[0], AvatarsParent, PLAYER1);
avatars[PLAYER2] = CreateAvatar(characterPrefabs[1], AvatarsParent, PLAYER2);
spawned = true;
}
bus = AvatarEventBus.Instance;
BindEvents();
}
public void ShowCharacters() //заспвавнить и показать
{
if (characterPrefabs != null && characterPrefabs.Count < 2)
Debug.LogError("Меньше 2 персонажей в списке аватаров.");
//Тут возможно будет рандомизация персонажей
StartCoroutine(MoveLocal(avatars[PLAYER1], GetShownPosition(PLAYER1), CharacterMovementSpeed, OnComplete: AvatarAppeared));
StartCoroutine(MoveLocal(avatars[PLAYER2], GetShownPosition(PLAYER2), CharacterMovementSpeed, OnComplete: AvatarAppeared));
}
public void HideCharacters()
{
StopAllCoroutines();
StartCoroutine(MoveLocal(avatars[PLAYER1], GetHiddenPosition(PLAYER1), CharacterMovementSpeed, OnComplete: () => { }));
StartCoroutine(MoveLocal(avatars[PLAYER2], GetHiddenPosition(PLAYER2), CharacterMovementSpeed, OnComplete: () => { }));
}
Avatar CreateAvatar(GameObject prefab, Transform parent, Team team)
{
GameObject go = Instantiate(prefab, parent);
Avatar avatar = go.GetComponent<Avatar>();
avatar.Initialize(team);
Transform transform = go.transform;
transform.localPosition = GetHiddenPosition(team);
return avatar;
}
Vector2 GetHiddenPosition(Team team)
=> team == PLAYER1 ? baseHiddenLocalPosition : new Vector2(-baseHiddenLocalPosition.x, baseHiddenLocalPosition.y);
Vector2 GetShownPosition(Team team)
=> team == PLAYER1 ? baseShownLocalPosition : new Vector2(-baseShownLocalPosition.x, baseShownLocalPosition.y);
void AvatarAppeared()//тут будем проверять когда оба прибыли.
{
}
//вынести события сюда из аватаров и прописать обработчики.
#region ПРИВЯЗКИ К ШИНЕ
public void BindEvents()
{
bus.OnAvatarsAppeared.AddListener(Appear);
bus.OnTurnStart.AddListener(TurnStart);
bus.OnTurnEnd.AddListener(TurnEnd);
bus.OnEndRound.AddListener(EndRound);
bus.OnDraw.AddListener(Draw);
bus.OnNewRoundStarted.AddListener(NewRound);
bus.OnEndGame.AddListener(EndGame);
}
public void UnbindEvents()
{
bus.OnAvatarsAppeared.RemoveListener(Appear);
bus.OnTurnStart.RemoveListener(TurnStart);
bus.OnTurnEnd.RemoveListener(TurnEnd);
bus.OnEndRound.RemoveListener(EndRound);
bus.OnDraw.RemoveListener(Draw);
bus.OnNewRoundStarted.RemoveListener(NewRound);
bus.OnEndGame.RemoveListener(EndGame);
}
#endregion
#region ОБРАБОТЧИКИ
private void Appear()
{
avatars[PLAYER1].Animator.SetTrigger(APPEAR_TRIGGER);
}
private void TurnStart(Team player) { }
private void TurnEnd(Team player)
{
avatars[player].Animator.SetTrigger(CHIPSPAWN_TRIGGER);
}
private void EndRound(Team winner)
{
//Обозначние для ничьей
if (winner == Team.None)
{
Draw();
Debug.LogError("Для PvP не назначены персонажи 6 и 7.", this);
return;
}
Team loser = winner == Team.Player1 ? Team.Player2 : Team.Player1;
avatars[winner].Animator.SetTrigger(WIN_TRIGGER);
avatars[loser].Animator.SetTrigger(LOSE_TRIGGER);
_characters[Team.Player1] = CreateCharacter(_characterPrefabs[0], Team.Player1);
_characters[Team.Player2] = CreateCharacter(_characterPrefabs[1], Team.Player2);
}
private void Draw()
{
Debug.Log("DRAW");
}
private void NewRound(Team lastRoundWinner)
{
//Debug.Log("NEW ROUND LAST WINNER " + lastRoundWinner);
}
private void EndGame(Team gameWinner)
{
//Debug.Log("ENDGAME");
}
#endregion
#region ХЕЛПЕРЫ
private IEnumerator MoveLocal(Avatar avatar, Vector3 targetLocalPos, float speed, Action OnComplete)
public void ShowCharacters()
{
Transform t = avatar.transform;
while (t.localPosition != targetLocalPos)
if (_characters.Count < 2 || _isVisible || _isShowing)
return;
StopMovement();
_isShowing = true;
PrepareForAppearance(_characters[Team.Player1]);
PrepareForAppearance(_characters[Team.Player2]);
int appearedCharacters = 0;
Action onAppeared = () =>
{
t.localPosition = Vector3.MoveTowards(t.localPosition, targetLocalPos, speed * Time.deltaTime);
appearedCharacters++;
if (appearedCharacters < 2)
return;
_isShowing = false;
_isVisible = true;
};
_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()
{
if (_characters.Count < 2)
return;
StopMovement();
_isShowing = false;
_isVisible = false;
_player1Movement = StartCoroutine(MoveLocal(
_characters[Team.Player1],
GetHiddenPosition(Team.Player1),
false,
null));
_player2Movement = StartCoroutine(MoveLocal(
_characters[Team.Player2],
GetHiddenPosition(Team.Player2),
false,
null));
}
private PvpCharacter CreateCharacter(GameObject prefab, Team team)
{
GameObject characterObject = Instantiate(prefab, _avatarsParent);
Animator animator = characterObject.GetComponentInChildren<Animator>(true);
if (animator == null)
throw new InvalidOperationException($"У PvP-персонажа {prefab.name} отсутствует 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);
}
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 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;
}
OnComplete?.Invoke();
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)
{
Transform = transform;
Animator = animator;
}
public Transform Transform { get; }
public Animator Animator { get; }
}
#endregion
}

View File

@ -7,6 +7,11 @@ using UnityEngine;
public class Board : MonoBehaviour
{
[SerializeField] private ChipPointer pointer;
[Header("PvP colors")]
[SerializeField] private Color _pvpInnerColor = new Color(0.2f, 0.65f, 1f, 1f);
[SerializeField] private Color _pvpOutlineColor = new Color(1f, 0.5f, 0.1f, 1f);
[SerializeField] private Color _pvpCellsColor = new Color(0.65f, 0.2f, 1f, 1f);
private BoardFitter fitter;
private BoardConfiguration config;
@ -35,6 +40,9 @@ public class Board : MonoBehaviour
public void SetColorsImmediately(Color innerColor, Color outlineColor, Color cellsColor)
=> fitter.SetColorsImmediately(innerColor, outlineColor, cellsColor);
public void ApplyPvpColors()
=> SetColorsImmediately(_pvpInnerColor, _pvpOutlineColor, _pvpCellsColor);
public Vector2 GetMiddleStickedPosition() => new Vector2(GetXPosition(Columns/2), config.stickedYOffset);
public Vector2 GetStickedPosition(int column) => new Vector2(GetXPosition(column), config.topLeftCell.position.y + config.stickedYOffset);

View File

@ -61,6 +61,7 @@ public class EndGameHandler : MonoBehaviour
else
{
GameManager gameManager = GameManager.Instance;
DualAvatarController.Instance?.PlayWin(winner);
gameManager.IncreaseScore(winner);
bool isMatchWin = gameManager.HasPvpMatchWinner(winner);
_pvpWinPanel.Open(

View File

@ -1,16 +1,20 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.Serialization;
public class TimerManager : MonoBehaviour
{
[SerializeField] Timer player1Timer, player2Timer;
[SerializeField] float vsBotTime = 180;
[SerializeField] float pvpTime = 90;
[SerializeField, FormerlySerializedAs("player1Timer")] private Timer _player1Timer;
[SerializeField, FormerlySerializedAs("player2Timer")] private Timer _player2Timer;
[SerializeField, FormerlySerializedAs("vsBotTime")] private float _vsBotTime = 180f;
[SerializeField, FormerlySerializedAs("pvpTime")] private float _pvpTime = 90f;
public void SetupTimers(GameMode gameMode)
{
player1Timer.SetupTimer(gameMode == GameMode.pve ? vsBotTime : pvpTime);
if (gameMode == GameMode.pvp)
player2Timer.SetupTimer(pvpTime);
if (_player1Timer != null)
_player1Timer.SetupTimer(gameMode == GameMode.pve ? _vsBotTime : _pvpTime);
if (gameMode == GameMode.pvp && _player2Timer != null)
_player2Timer.SetupTimer(_pvpTime);
SetTimersActivity(gameMode);
}
@ -18,19 +22,26 @@ public class TimerManager : MonoBehaviour
public void SetTimersActivity(GameMode gameMode)
{
bool activateSecondTimer = gameMode == GameMode.pvp;
player1Timer.gameObject.SetActive(true);
player2Timer.gameObject.SetActive(activateSecondTimer);
if (_player1Timer != null)
_player1Timer.gameObject.SetActive(true);
if (_player2Timer != null)
_player2Timer.gameObject.SetActive(activateSecondTimer);
}
public void StopAllTimers()
{
player1Timer.StopTimer();
player2Timer.StopTimer();
_player1Timer?.StopTimer();
_player2Timer?.StopTimer();
}
public void SetActiveTimer(Team currentPlayer)
{
(currentPlayer == Team.Player1 ? player1Timer : player2Timer).StartTimer();
(currentPlayer == Team.Player1 ? player2Timer : player1Timer).StopTimer();
Timer activeTimer = currentPlayer == Team.Player1 ? _player1Timer : _player2Timer;
Timer inactiveTimer = currentPlayer == Team.Player1 ? _player2Timer : _player1Timer;
activeTimer?.StartTimer();
inactiveTimer?.StopTimer();
}
}
}

File diff suppressed because one or more lines are too long