Простой (очень) мультиплеер Playmaker + Photon

Туториал написан на основе собственного опыта.

Версия Unity: 2021.3.3f1

Версия Playmaker: 1.9.5 (приобретенная в Asset Store)

Сложность: чуть выше новичка, но базовые принципы конечно знать стоит.

По времени займет минут 30-40.

Что будем делать

Создадим простейшую синхронизацию двух экземпляров билда между собой — у нас будет одна комната в которой будет 2 игрока, которые будут управлять одной переменной в игровом объекте на сцене.

Такое может быть полезно для простых пошаговых коллективных игр типа крестики-нолики, когда оба клиента знают правила, и отражают их в зависимости от общего состояния игрового поля.

Подготовка

Ставим-импортируем через Package Manager (меню Windows) ассет PUN2 — FREE.

После импорт откроется окошко PUN Wizard — для удобства назовем его визард настроек Фотона.

Видим строчку для ввода — вводим туда свой рабочий емейл и жмем Setup Project. После чего происходит магия и конкретно этот ваш проект юнити становится привязан к серверам Фотона через полученный ID. По сути происходит магия настройки соединения.

Визард можно закрыть.

Теперь шуруем в Экосистему Плеймейкера(Alt+E) и скачиваем там ассет Playmaker PUN2. Устанавливаем.

Всё. Подготовительные работы завершены — тепреь в проекте есть две важные составляющие — фотон для мультиплеера и непосредственно визуальный скриптинг плеймейкера под этот самый фотон. Теперь создайте две сцены — одну назовите Menu, а вторую Game. И добавьте обе в настройках билда. Теперь можно приступать к творчеству.

Сцена «MENU» — меню с подключением

Открываем сцену с меню. Как уже можно было понять — тут мы сделаем меню с подключением к комнате. Никаких фокусов с дизайном и стилем, только хардкор, только чистый принцип работы.

Закидываем на сцену канвас и ставим в него 3 кнопки и один инпут.

У меня они названы для удобства Button A/B/C, но это влияет только на вашу организацию процесса, называйте так, чтобы не запутаться. на выходе на сцене нужны три кнопки — подключиться к мастеру(Button A), присоединиться(Button B) и хостануть(Button C).

Теперь идем в папке проекта по адресу Assets/PlaymakerPUN2/Resources и находим там префаб PlayMaker Photon Proxy. Тянем его на сцену.

Там уже будут компоненты, но нас пока они не интересуют. Этот префаб используется непосредственно для проксирования сети. Если на сцене нет этого префаба, значит сцена не в сети и совершать действия с сетью на этой сцене не получится — юнити выдаст ошибку.

Добавим пустой игровой объект на сцену и назовем его MENU MANAGER — или как хотите, главное идентификация — и добавим в него новый FSM Плеймекера. Это будет наш объект управления сетью и подключениями на сцене. Можно FSM добавить хоть в объект проксирования который перед этим затянули на сцену, но лучше все делать раздельно.

Идем в этот новый FSM в менеджере меню и начинаем творить. Строим конструкцию как на скрине.

Что здесь происходит: первый стейт — ждем нажатия кнопки CONNECT TO MASTER(в моем случае она называется как Button A). Когда кнопка нажата — во втором стейте подключаемся к мастерсерверу.

Мастерсервер — это выделенный сервер фотона под ваш проект, который в бесплатной версии поддерживает до 20 одновременных подключений для вашего ID. Это 10 одновременных игр в ваши крестики-нолики. Подключение к мастер-серверу необходимо, так как фотон имеет клиент-серверную архитектуру, и сделать на нем p2p подключение через PUN нельзя. Увы и ах, но у фотона не самые кусачие цены на тарифы для интересных игр.

Едем дальше.

Мы подключились к мастерсерверу. Теперь у нас может быть два пути (на самом деле больше, но суть не сильно отличается) — мы можем создать комнату или же присоединиться к ней. В нашем примере нам нужно и то и другое, причем подключаться мы будем «адресно» — то есть хост должен будет сообщить игроку название комнаты.

Когда игрок подключается к мастерсерверу, то сервер возвращает глобальный эвент — и мы его можем поймать. Мы его должны ловить. Для этого жмем правой кнопкой мыши в FSM и добавляем глобальный переход через меню Add Global transition/Custom Events/PHOTON/ON CONNECTED TO MASTER.

У нас создается стейт, который активируется если мы удачно вошли на мастерсервер. В этом состоянии мы теперь можем ждать новых действий от игрока — или он захочет присоединиться к созданной комнате, или же захочет хостануть новую.

Я построил вот такую конструкцию, в которой два важных эвента — это ожидание нажатия какой либо из двух кнопок.

Что здесь происходит: Если мы удачно подключились к серверу, то ждем нажатия одной из двух кнопок — JOIN или HOST (у меня это кнопки Button B и Button C). Все остальные эвенты я напичкал просто для красоты и визаульных рюшечек.

Теперь присядем по очереди на разные стулья.

ИГРОК ХОСТУЕТ КОМНАТУ

Допустим игрок жмет кнопку HOST. Cлучается событие CREATE ROOM, наш стейт меняется и в новом стейте мы создаем игровую комнату.

Что здесь происходит: в новом стейте (который у меня называется Create new Room) мы добавляем два действия.

  1. Get Textmesh Pro Input Text — нам нужно взять строку из поля инпута и сохранить в переменную. Я юзаю Textmesh Pro, для него нужно скачать свой пак экшенов плеймекера в экосистеме, но можно юзать любой инпут и любым способом получить строчку из этого инпута (даже старый UI инпут будет ок). Главное здесь — получить строчку из инпута. Но даже это дейсвтие можно опустить и задать строчку насильно, без всяких инпутов. Потому что самое важное действие следующее.
  2. PUN Create Room Advanced — этим дейсвтием мы создаем непосредственно игровую комнату на мастерсервере, куда смогут заходить игроки. Room Name может быть произвольным, может определяться инпутом — на ваше усмотрение. Если определять название комнаты через инпут — то подключение можно делать «адресным», что я и сделал в своем примере. Остальные настройки как на скрине, но поиграть с ними никто не запрещает, ничего не сломается, если вы укажете количество игроков не 2 а 5.

Всё. Комната создана. Теперь мастерсервер знает, что вы собираетесь поиграть. Но нужно донастроить комнату, и закинуть хоста в неё уже.

Завершаем этот стейт событием FINISHED и создаем следующий стейт:

Что здесь происходит: это напрочь важный стейт, в котором мы делаем две важные вещи. Просим мастерсервер автматически синхронизировать сцены в нашей комнате (играть двумя разными билдами не получится и так, но при этом сцены будут между собой обмениваться информацией полноценно), и второе — задаемся важным вопросом, который в сетевых играх достаточно распространен: мы как бы отвечаем серверу на вопрос «а являемся ли мы мастер-клиентом». Проще — а являемся ли мы активным игроком в комнате.

И если наш клиент является таковым — то происходит событие isMaster и нас переносит в новый стейт.

Что здесь происходит: когда мы ответили серверу, что действительно являемся живым игроком и сервер это принял, мы просим Фотон загрузить нас в матрицу — и загружаем нашу вторую сцену, которую мы создали предусмотрительно вначале (сцена «Game»).

Всё. Хост загрузился в игру и уже ждет подключения новых игроков.

ИГРОК ПОДКЛЮЧАЕТСЯ В КОМНАТУ

Вернемся на 4 шага назад, где мы только что подключились к мастер серверу и ждем нажатия одной из двух кнопок — JOIN или CREATE. В прошлый раз мы выбрали синюю таблетку и нажали на CREATE. В этот раз мы нажмем на JOIN и попробуем адресно подключиться к хосту.

Для этого у нас есть событие JOIN TARGET ROOM, которое случается если мы нажмем на кнопку JOIN. И оно ведет нас на новый стейт.

Что здесь происходит: сразу скажу — этот стейт не критичный в разрезе все туториала, но так как мы делаем адресное подключение к хосту — то нам нужно проверить поле инпута на предмет введенного названия комнаты, и если поле инпута содержит хотя бы три символа — попробовать подключиться к комнате. Собственно тут я это и сделал — взял из инпута строку, получил её длину и сравнил. Если длина меньше трех символов то происходит событие Short name, игроку показывается уведомление о том что название комнаты к которой хотим подключиться короткое, и возвращаемся в стейт к ожиданию нажатия JOIN или HOST.

Интереснее когда, длина строки нормальная и мы получаем событие Try to connect.

Что здесь происходит: здесь всего в один экшен мы просим фотон в плеймекере подключиться к комнате с названием, которое взяли из инпута (у меня это переменная RoomNameString).

Обратите внимание — галочка Create if not Exist не стоит. Это значит, что если комната с таким именем не будет найдена, то и новая комната не будет создана.

Дальше важный момент. У меня в примере в этом экшене стоит событие Join Failed — если подключение не удалось, то мы можем перейти в новый стейт через это событие. НО. У Фотона в плеймейкере есть еще один способ получить событие неудачного подключение. Через глобальные события.

Поправьте мою ошибку и стейте Room doesnt Exist сделайте событие FINISHED и верните его к стейту где ждем нажатия двух кнопок. Чтобы можно было несколько раз пробовать подключение если ошибетесь в названии комнаты созданной хостом.

Магия почти завершена.

Теперь игрок может не только хостануть комнату, но и обратиться к серверу чтобы подключиться к ней.

Но что если на сервере всё же найдется комната с нужным названием и сервер скажет что к ней можно подключиться?

Нужно добавить кое-что очень важное.

Сделаем глобальное событие ON JOINED ROOM.

Потому что если сервер найдет комнату с нужным именем, он вернет нам это событие и нам нужно игрока, который пытается подключиться к хосту, закинуть в комнату к этому хосту. А чтобы это сделать, нам нужно будет это событие связать со стейтом, где мы отвечаем серверу на вопрос — являемся ли мы «мастер-клиентом», то есть являемся ли мы игроком.

ВСЁ. Мы создали базовое соединение между двумя игроками с помощью плеймейкера и фотона в юнити — можно хостануть комнату и подключиться к ней, и оба игрока будут находиться «на одной» сцене.

С первой сценой «Menu» мы закончили. Перетекаем теперь на сцену с игрой.

Сцена «GAME» — игровая сцена

Здесь будет всё чуть сложнее чем в прошлый раз, но постараюсь не кошмарить. Главное не запутаться. Не боги горшки обжигают всё же.

Открываем вторую сцену которую мы создали с названием GAME.

Напомню, что мы хотим сделать так, чтобы два игрока могли контролировать переменную одного и того же игрового объекта на сцене. То есть если Вася изменит значение переменной в игровом объекте, Петя должен это увидеть со своей стороны.

Настройте сцену как вам удобно, и как вы хотите. У меня в примере она выглядит вот так.

У меня на сцене есть несколько ключевых объектов:

  1. Цилиндр с канвасом, в котором стоит Ноль (через textMesh).
  2. Кнопка More.
  3. Кнопка Less.

Сначала проведем базовую подготовку игровых объектов — сделаем так, чтобы значение переменной у объекта вообще менялось. Сделаем это максимально кустарно — нам нужно при нажатии на кнопку More прибавить единицу к переменной в игровом объекте, и при нажатии на Less — отнять единицу из переменной, а саму переменную вывести разумеется над объектом вместо нуля. Это рядовая задача для тех кто хотя бы второй день пользуется плеймейкером.

Цилиндру добавляем FSM, я его назвал INTEGER, и сразу же идем в переменные и добавляем там переменную INT как целочисленную (тип integer):

Профит. Теперь выбираю канвас с двумя кнопками которые сделал, и вешаю на него FSM, в котором делаю обработку нажатия этих двух кнопок.

Я очень надеюсь, что читающий это уже знаком с базисом плеймейкера и способен сделать так, чтобы переменная в игровом объекте уменьшалась или увеличивалась при нажатии на одну из кнопок, но на всякий прикрепляю скрин. На нем стейт с действиями, которые происходят после нажатия на кнопку MORE:

  1. Получаем целочисленную переменную(INT) из FSM(INTEGER) нашего игрового объекта и сохраняем её(PlayerInt).
  2. Прибавляем к этой сохраненной единицу.
  3. Возвращаем значение этой переменной обратно в тот FSM (который в цилинде, и который называется INTEGER).

Всё. Остановились. проверьте сцену — у вас должно меняться значение переменной в FSM который называется INTEGER и который находится в объекте цилиндре. Если не сложилось — пишите в чат телеграма по плеймейкеру и вам помогут.

Если всё получилось, то переходим к магии мультиплеера.

НАСТРОЙКА ИГРОКОВ И СЦЕНЫ ПОД МУЛЬТИПЛЕЕР

Как мы помним, у нас и хост, и игрок уже вошли в созданную комнату — им загрузилась игровая сцена. Теперь нужно синхронизироваться и объяснить комнате, кто есть кто и что мы тут делаем.

Для этого нам нужно создать особый игровой объект — префаб игрока. ЭТО ВАЖНО, потому как если на сцене нет сетевого игрока — то и игры по сети нет.

Делаем следующее.

  1. Создаем на сцене пустой игровой объект.
  2. Называем его как удобно, я назвал PLAYER INSTANCE.
  3. В компонентах этому объекту добавляем Playmaker Game Object Photon Proxy.
  4. В компонентах этому объекту добавляем Photon View.
  5. В компонентах этому объекту добавляем Playmaker Photon View (Script).
  6. Переносим этот объект как префаб в папку Assets/Resources. Если вы этого не сделаете, то весь прочитанный туториал смысла не имеет. Сетевые префабы фотона должны лежать всегда именно в этой папке.
  7. Удаляем префаб со сцены.

Этими шагами мы создали только что объект «игрока» — проще говоря — наши глаза в сетевой игре, наш сетевой аватар, с помощью которого мы сможем взаимодействовать по сети.

Теперь давайте его иницализируем, чтобы игрок точно оказался в комнате. Для этого создаем пустой игровой объект, называем его к примеру GAME MANAGER и вешаем на него скрипт FSM.

Внутри этого FSM делаем сначала вот так:

А потом вот так:

На старте мы иницируем сетевое событие, как бы говоря комнате публично (чтобы все слышали) что есть активное подключение, и отправляем событие Create в сеть комнаты.

А второй шаг — это непосредственно реакция на это событие Create — как только мы получаем это событие, мы создаем инстанс нашего игрока — того самого префаба, что мы создали в папке Resources.

Простым языком — когда игрок (например хост) входит в комнату и оказывается на этой сцене, мы создаем сетевой экземпляр игрока. Если мы просто создадим объект как обычно — он не инициируется в сети Фотона и никакой синхронизации не будет.

Всё. Осталось немного. Игроки инициализированы и уже на этом моменте соединение работает. Но надо увидеть это вживую же.

Для этого мы и синхронизируем управление переменной в объекте.

Идем в наш цилиндр (у которого есть FSM с переменной внутри), и входим его единственный FSM, который мы назвали INTEGER.

Вешаем действие — если переменная целочисленная меняется (а она у нас меняется как раз таки кнопками на экране), то запускаем событие FINISHED и переходим в следующее состояние(на скрине это State 2):

Тут делаем две вещи. Важные. Сначала конвертим число в строку. А потом совершаем синхронизацию, за счет отправки глобального сетевого события CCHANGE, к которому прикладываем значение нашей только что сконвертированной строки. И возвращемся через FINISHED к стартовому состоянию в котором ждем изменения целочисленной.

Простыми словами: как только целочисленная меняется, мы шлем всем в сети глобальное событие, и в этом событии прикладываем значение целочисленной переменной в виде строчки. Представьте комнату, в которой курица сносит яйцо и каждый раз со снесенным яйцом громко кричит «CCHANGE!» чтобы все петухи это услышали. Вот мы сделали тоже самое.

Последний штрих.

Создаем реакцию на глобальное событие.

Что здесь происходит: как только наш цилиндр, находящийся на сетевой сцене (если помните, мы загрузили сцену не как обычно, а именно как сетевую), слышит событие CCHANGE, он должен отреагировать. И реагирует он следующим образом:

  1. Get Event String Data — важно, получаем строчное значение (и сохраняем в строчную переменную) из последнего полученного события, которым у нас является глобальное событие CCHANGE.
  2. Конвертируем полученную строчку в число (переменная PlayerInt)
  3. Устанавливаем значение переменной INT (которая у нас уже есть в этом FSM) равное полученному значению PlayerInt.
  4. Отражаем это визуально через textMeshPro.

ВСЁ.

Как проверить что всё ок?

Сделайте билд. Первой сценой должна быть сцена с меню

Запустите билд на одном экране. На втором мониторе запустите проверку сцены в юнити.

Подключитесь в обоих билдах к мастер серверу.

В одном билде введите название комнаты — к примеру 1234 и нажмите HOST. Вас должно перенести на новую (вторую сцену).

Во втором билде — к примеру который запущен из юнити — введите название комнаты которую только что создали — 1234 и нажмите JOIN. Вас должно перенести на новую (вторую сцену).

Если вы нажмете на кнопку MORE или LESS в одном билде, то во втором запущенном билде должна будет измениться цифра на экране — и наоборот. Если это происходит — то поздравляю, вы сделали первый шаг в мультиплеер на фотоне с помощью виуального скриптинга Плеймейкер.

Можно делать свою ММО на визуальных скриптах.

Ниче не работает

Полный бардак. Пишите с вопросами в чат телеграма по плеймейкеру, будем разбираться — https://t.me/unityplaymakers

Пример того, на основе чего был написан этот тутор можно скачать здесь.