Как стать автором
Поиск
Написать публикацию
Обновить

Turn-based по‑простому: пишем крестики-нолики на Unity + Mirror

Уровень сложностиСредний

Создание мультиплеерных проектов - непростое занятие. Для облегчения понимания этого процесса можно начать с разработки пошаговых игр. В них не требуется постоянная синхронизация данных, поэтому они - отличная точка входа. В статье мы разберём реализацию «пошаговости» на примере крестиков-ноликов, используя Unity + Mirror.

Ключевой принцип мультиплеерных проектов

Основная идея заключается в разделении ответственности между сервером и клиентом. Сервер хранит состояния и содержит набор команд для работы с ними, клиент же - лишь отправляет запрос на исполнение этих команд.

Архитектура «пошаговости»

Мы намеренно оставляем за рамками подключение игроков, запуск сервера и прочие инфраструктурные детали. Наша цель - разобрать принцип.

В отрыве от этих деталей наш проект состоит из следующих сущностей:

  • TurnManager — основной модуль. Он принимает и валидирует ходы, обновляет состояние поля, проверяет выигрыш/ничью, передаёт ход и заканчивает игру.

  • Field — хранит состояние клеток, отрисовывает поле и по запросу TurnManager определяет, остались ли свободные ячейки и есть ли выигрышная комбинация.

  • PlayerManager даёт данные об игроках — их число, имя и символ (X/O) текущего игрока.

Управление ходами через Command

Command в Mirror — это метод с атрибутом [Command] в NetworkBehaviour, который вызывается на клиенте, а выполняется на сервере.

[Command(requiresAuthority = false)]
void CmdPassTurn(int cellId, NetworkConnectionToClient sender = null)
{
    Player requestingPlayer = sender.identity.GetComponent<Player>();
    if (requestingPlayer.playerId == currentPlayerId && field.CanPlaceFigureHere(cellId))
    {
        MakeMove(cellId);
    }
}

[Server]
public void MakeMove(int fieldCellId)
{
    field.PlaceFigureToCell(fieldCellId, playerManager.GetPlayerFigure(currentPlayerId));
    if (field.CheckWinner() != 0)
    {
        RpcEndGame(true);
    }
    else if (field.MovesOver())
    {
        RpcEndGame(false);
    }
    else
    {
        SwitchNextPlayer();
    }
}

Клиент кликает по ячейке, запрос, благодаря [Command], отправляется на сервер, на котором TurnManager валидирует ход, обновляет поле и расчитывает результат насчёт победы/ничьи. Если ход валиден, и его результат не привёл к победе/ничье, ход передаётся следующему игроку.

Переключение игрока на сервере

[SyncVar(hook = nameof(OnPlayerChanged))]
public int currentPlayerId = 0;

public void SwitchNextPlayer()
{
    currentPlayerId = (currentPlayerId + 1) % playerManager.GetPlayerCount();
}

void OnPlayerChanged(int oldValue, int newValue)
{
    playerManager.UpdateCurrentPlayer(newValue);
}

Для определения текущего игрока используется переменную currentPlayerId, она помечена как SyncVar, что означает, что она синхронизируется на всех клиентах при изменении на сервере, а хук будет вызывать метод OnPlayerChanged при его изменении на клиентах, что может быть полезно для обновления текущего игрока на интерфейсе игры.

Немного про [ClientRpc]

[ClientRpc]
public void RpcEndGame(bool hasWinner)
{
    if (hasWinner)
    {
        OnGameEndedEvent?.Invoke(playerManager.GetPlayerName(currentPlayerId));
    }
    else
    {
        OnGameEndedEvent?.Invoke("Никто");
    }
}

Метод, помеченый [ClientRpc], вызывается на сервере, а выполняется на всех клиентах‑наблюдателях объекта. В данном случае при окончании игры он выводит сообщение на экране у всех игроков.

Что такое SyncList?

public readonly SyncList<int> fieldList = new SyncList<int>();

В классе Field у нас есть синхронизируемый список fieldList. Работает также, как и переменная, помеченная [SyncVar]. Это позволяет всем клиентам своевременно обновлять у себя визуал игрового поля без каких-либо задержек.

Заключение

Мы реализовали базовый каркас пошаговой игры на Unity + Mirror: сервер принимает и валидирует ходы, обновляет состояние поля и рассылает результаты клиентам через SyncVar/SyncList и RPC. Этого шаблона достаточно, чтобы строить более сложные пошаговые игры. Если есть вопросы или конструктивная критика — рад выслушать. Спасибо за прочтение.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.