Создание мультиплеера в unity3d. Информационный портал по безопасности. Сообщение и его тип

Всем привет! Сегодня хотелось бы рассказать про один из способов, как можно создать локальный мультиплеер в Unity. Данное решение подходит для шоукейсов, теста фич или локального мультиплеера. К примеру, если вам хочется видеть, что делает игрок, но не хочется скажем на андроиде тратить лишние ресурсы и забирать скринкаст с помощью ADB, то можно просто поднять сервер на какой-то машинке в виде копии приложения, которое работает на телефоне, и слать туда информацию о действиях игрока.

Я вкратце опишу, как можно это сделать с помощью HLAPI на Unet, но не через NetworkingManager, а чуть более низкоуровнево. По доброй традиции приложу пример своей реализации такого клиент-серверного взаимодействия. Пример прошу не судить строго, так как я прекрасно понимаю, что архитектура данного решения никуда не годится и создаёт кучу проблем в перспективе. Моей целью было максимально быстро (за выходные) написать систему, в которой можно показать принцип работы с сетью. Также скажу с какими проблемами пришлось столкнуться. В данном примере реализации предполагается, что сервер так же является Unity приложением.

В интернете самым частым примером того, как сделать мультиплеер является чат, но я люблю игры, и делать чат мне показалось скучным. Поэтому я решил разобрать то, как можно сделать мультиплеер на примере крестиков-ноликов. Допустим, что вся логика игры, определения победителя, смены ходов и т. п. написаны, и нам осталось прикрутить сервер. В простейшем случае в крестиках-ноликах нам надо обрабатывать 2 сообщения. Определение порядка хода (раздача айдишников) и захват клетки.


В целом игра в данном примере работает очень просто. Есть 2 сцены. Загрузочная и геймплейная. При загрузке геймплейной сцены у нас генерируется поле на котором играют игроки. Там же происходит проверка условия победы, есть классы отвечающие за работу UI, а так же порядок ходов и в целом логику игры, но нам это не особо интересно. Основные классы, которые отвечают за сетку - это Server, Client, NetManager и отдельный файл для сообщений NetMessages и определённый в нём enum MyMsgType. Они представляют из себя обёртку над средствами Unet. С точки зрения Unet основные классы, которые мы будем использовать - это NetworkClient, NetworkServer, MessageBase и MsgType. Что это за классы?

Самые простые классы - это MessageBase и MsgType. Первый - это абстрактный класс, от которого надо наследовать все наши сообщения, чтобы пересылать их между клиентом и сервером. MsgType - это просто класс, который хранит в себе константы, отвечающие за определённый набор вшитых в Unet сообщений.


NetworkServer - это синглтон, который предоставляет возможность обрабатывать общение с удалёнными клиентами. Под капотом он использует экземпляр NetworkServerSimple и по сути представляет из себя удобную обёртку над ним. Для начала нам надо запустить сервер на определённом порте. Для этого необходимо вызвать метод Listen(int serverPort) - этот метод запускает сервер на порте serverPort. (Напомню, что все порты в диапазоне от 0 до 1023 являются системными и их не стоит использовать в качестве параметра данного метода)

Отлично сервер работает и слушает какой-то порт. Теперь нужно, чтобы он реагировал на сообщения. Для этого нужно зарегистрировать хэндлер с помощью метода RegisterHandler(short msgType, Networking.NetworkMessageDelegate handler) . Данный метод принимает параметром тип сообщения и делегат. Делегат в свою очередь должен принимать входным параметром NetworkMessage. Допустим мы хотим, чтобы в тот момент, когда к нему присоединялся игрок на сервере начала загружаться геймплейная сцена, а так же раздавались айдишники игроков. Тогда нужно зарегистрировать хэндлер на соответствующее сообщение, а так же реализовать метод, который мы будем передавать в качестве делегата для регистрации.

Выглядит это примерно так:

Пример регистрации хэндлера и делегата

NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessage msg) { Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) { SendPlayerID(connId, -1); } else { int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS[(index + 1) % Constants.PLAYERS_COUNT]; SceneManager.LoadScene(1); } }

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

Простой сервер есть. Теперь бы клиенты не помешали. Для этого воспользуемся классом NetworkClient. Для того, чтобы присоединиться к серверу надо просто вызвать метод Connect(string serverIp, int serverPort) . В качестве порта мы устанавливаем порт, который слушает наш сервер, в качестве айпи ставим localhost, если мы тестируем наше приложение на одной машине или же айпи компа в локальной сети, который мы используем в качестве сервера (Узнать его можно или в настройках сети, или в консоли с помощью команды ipconfig на том компе, который будет выступать в роли сервера).

Замечательно, мы может подконнектиться. Ранее говорилось, что сервак у нас раздаёт айдишники. Значит нам надо во-первых, послать сообщение о айдишнике, а так же зарегистрировать хэндлер этого сообщения на клиенте. Как упоминалось ранее все сообщения надо наследовать от MessageBase. Для начала определим их (я предпочитаю это делать в отдельном файле):

Сообщение и его тип

Public class PlayerIDMessage: MessageBase { public int PlayerID; } public enum MyMsgType: short { PlayerID = MsgType.Highest + 1, }

Чтобы послать данное сообщение вызываем на клиенте метод Send(short msgType, Networking.MessageBase msg) , который отправит сообщение msg «типа» msgType серверу, или на сервере один из методов в зависимости от цели SendToAll(short msgType, Networking.MessageBase msg) или SendToClient(int connectionId, short msgType, Networking.MessageBase msg) , где connectionId - это id определённого клиента.

Обработка на клиенте пришедшего айдишника

Private void OnPlayerID(NetworkMessage msg) { PlayerIDMessage message = msg.reader.ReadMessage(); _CurrentUser.PlayerID = message.PlayerID; SceneManager.LoadScene(1); }

В принципе всё. Дальнейшее использование зависит от конкретики. Создаём нужные нам сообщения в зависимости от геймплея и отправляем их. В крестиках-ноликах я так же определил ещё одно сообщение, которое отвечает за захват точки. Целиком реализацию проекта можно посмотреть .

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

1. Проверьте, что редактор юнити не заблокирован вашим фаерволом для общения по протоколам TCP и UDP. Однажды я потратил на это некоторое время, при том, что добрался до фаерволла и поставил нужный порт в исключения, но не проверил то, что редактор незаблокирован.

2. Передавайте в сообщениях value-type. Reference-type будут передавать чушь, так как по адресу который вы хотите передать в другую аппу, неизвестно что лежит. (Я думаю, что это и так понятно тем, кто понимает, как работают value и reference type)

В случае, когда мы говорим про локальную сеть и заранее известное железо, нет многих проблем возникающих в случае “настоящего” мультиплеера. Практически не нужно задумываться о задержках, флуктуациях, потерях пакетов и прочем. Поэтому такое решение может быть полезно, хотя и эти проблемы можно решать с таким подходом до некоторого предела. В сравнении с более высоким уровнем абстракции в Unet, через NetworkManager, NetworkBehavior и т.п., оно предоставляет более понятную и очевидную гибкость (на мой взгляд), если на клиентах требуется загружать разные сцены и т. п., а скажем сервер используется, как стриминг, и показывает то, что отображается на девайсе игрока, принимая его позицию и поворот + дублируя происходящее на своей стороне. В остальных случаях, когда необходимо решение быстрее и вы умеете работать с сетью, Unet предоставляет возможность писать на транспортном уровне.

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

Спасибо за внимание!

  • Unity
  • Всем привет! Сегодня хотелось бы рассказать про один из способов, как можно создать локальный мультиплеер в Unity. Данное решение подходит для шоукейсов, теста фич или локального мультиплеера. К примеру, если вам хочется видеть, что делает игрок, но не хочется скажем на андроиде тратить лишние ресурсы и забирать скринкаст с помощью ADB, то можно просто поднять сервер на какой-то машинке в виде копии приложения, которое работает на телефоне, и слать туда информацию о действиях игрока.


    Я вкратце опишу, как можно это сделать с помощью HLAPI на Unet, но не через NetworkingManager, а чуть более низкоуровнево. По доброй традиции приложу пример своей реализации такого клиент-серверного взаимодействия. Пример прошу не судить строго, так как я прекрасно понимаю, что архитектура данного решения никуда не годится и создаёт кучу проблем в перспективе. Моей целью было максимально быстро (за выходные) написать систему, в которой можно показать принцип работы с сетью. Также скажу с какими проблемами пришлось столкнуться. В данном примере реализации предполагается, что сервер так же является Unity приложением.

    В интернете самым частым примером того, как сделать мультиплеер является чат, но я люблю игры, и делать чат мне показалось скучным. Поэтому я решил разобрать то, как можно сделать мультиплеер на примере крестиков-ноликов. Допустим, что вся логика игры, определения победителя, смены ходов и т. п. написаны, и нам осталось прикрутить сервер. В простейшем случае в крестиках-ноликах нам надо обрабатывать 2 сообщения. Определение порядка хода (раздача айдишников) и захват клетки.

    В целом игра в данном примере работает очень просто. Есть 2 сцены. Загрузочная и геймплейная. При загрузке геймплейной сцены у нас генерируется поле на котором играют игроки. Там же происходит проверка условия победы, есть классы отвечающие за работу UI, а так же порядок ходов и в целом логику игры, но нам это не особо интересно. Основные классы, которые отвечают за сетку - это Server, Client, NetManager и отдельный файл для сообщений NetMessages и определённый в нём enum MyMsgType. Они представляют из себя обёртку над средствами Unet. С точки зрения Unet основные классы, которые мы будем использовать - это NetworkClient, NetworkServer, MessageBase и MsgType. Что это за классы?

    Самые простые классы - это MessageBase и MsgType. Первый - это абстрактный класс, от которого надо наследовать все наши сообщения, чтобы пересылать их между клиентом и сервером. MsgType - это просто класс, который хранит в себе константы, отвечающие за определённый набор вшитых в Unet сообщений.

    NetworkServer - это синглтон, который предоставляет возможность обрабатывать общение с удалёнными клиентами. Под капотом он использует экземпляр NetworkServerSimple и по сути представляет из себя удобную обёртку над ним. Для начала нам надо запустить сервер на определённом порте. Для этого необходимо вызвать метод Listen(int serverPort) - этот метод запускает сервер на порте serverPort. (Напомню, что все порты в диапазоне от 0 до 1023 являются системными и их не стоит использовать в качестве параметра данного метода)

    Отлично сервер работает и слушает какой-то порт. Теперь нужно, чтобы он реагировал на сообщения. Для этого нужно зарегистрировать хэндлер с помощью метода RegisterHandler(short msgType, Networking.NetworkMessageDelegate handler) . Данный метод принимает параметром тип сообщения и делегат. Делегат в свою очередь должен принимать входным параметром NetworkMessage. Допустим мы хотим, чтобы в тот момент, когда к нему присоединялся игрок на сервере начала загружаться геймплейная сцена, а так же раздавались айдишники игроков. Тогда нужно зарегистрировать хэндлер на соответствующее сообщение, а так же реализовать метод, который мы будем передавать в качестве делегата для регистрации.

    Выглядит это примерно так:

    Пример регистрации хэндлера и делегата

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessage msg) { Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) { SendPlayerID(connId, -1); } else { int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS[(index + 1) % Constants.PLAYERS_COUNT]; SceneManager.LoadScene(1); } }


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

    Простой сервер есть. Теперь бы клиенты не помешали. Для этого воспользуемся классом NetworkClient. Для того, чтобы присоединиться к серверу надо просто вызвать метод Connect(string serverIp, int serverPort) . В качестве порта мы устанавливаем порт, который слушает наш сервер, в качестве айпи ставим localhost, если мы тестируем наше приложение на одной машине или же айпи компа в локальной сети, который мы используем в качестве сервера (Узнать его можно или в настройках сети, или в консоли с помощью команды ipconfig на том компе, который будет выступать в роли сервера).

    Замечательно, мы может подконнектиться. Ранее говорилось, что сервак у нас раздаёт айдишники. Значит нам надо во-первых, послать сообщение о айдишнике, а так же зарегистрировать хэндлер этого сообщения на клиенте. Как упоминалось ранее все сообщения надо наследовать от MessageBase. Для начала определим их (я предпочитаю это делать в отдельном файле):

    Сообщение и его тип

    public class PlayerIDMessage: MessageBase { public int PlayerID; } public enum MyMsgType: short { PlayerID = MsgType.Highest + 1, }


    Чтобы послать данное сообщение вызываем на клиенте метод Send(short msgType, Networking.MessageBase msg) , который отправит сообщение msg «типа» msgType серверу, или на сервере один из методов в зависимости от цели SendToAll(short msgType, Networking.MessageBase msg) или SendToClient(int connectionId, short msgType, Networking.MessageBase msg) , где connectionId - это id определённого клиента.

    Обработка на клиенте пришедшего айдишника

    private void OnPlayerID(NetworkMessage msg) { PlayerIDMessage message = msg.reader.ReadMessage(); _CurrentUser.PlayerID = message.PlayerID; SceneManager.LoadScene(1); }


    В принципе всё. Дальнейшее использование зависит от конкретики. Создаём нужные нам сообщения в зависимости от геймплея и отправляем их. В крестиках-ноликах я так же определил ещё одно сообщение, которое отвечает за захват точки. Целиком реализацию проекта можно посмотреть .

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

    1. Проверьте, что редактор юнити не заблокирован вашим фаерволом для общения по протоколам TCP и UDP. Однажды я потратил на это некоторое время, при том, что добрался до фаерволла и поставил нужный порт в исключения, но не проверил то, что редактор незаблокирован.

    2. Передавайте в сообщениях value-type или сериализуемые reference-type, а так же не передавайте наследников MonoBehavior. Так же важно понимать что reference-type в данном случае будет передавать копию объекта, а не сам объект, и его надо обрабатывать соответствующим образом.

    В случае, когда мы говорим про локальную сеть и заранее известное железо, нет многих проблем возникающих в случае “настоящего” мультиплеера. Практически не нужно задумываться о задержках, флуктуациях, потерях пакетов и прочем. Поэтому такое решение может быть полезно, хотя и эти проблемы можно решать с таким подходом до некоторого предела. В сравнении с более высоким уровнем абстракции в Unet, через NetworkManager, NetworkBehavior и т.п., оно предоставляет более понятную и очевидную гибкость (на мой взгляд), если на клиентах требуется загружать разные сцены и т. п., а скажем сервер используется, как стриминг, и показывает то, что отображается на девайсе игрока, принимая его позицию и поворот + дублируя происходящее на своей стороне. В остальных случаях, когда необходимо решение быстрее и вы умеете работать с сетью, Unet предоставляет возможность писать на транспортном уровне.

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

    Посмотрело: 1044

    Всем привет! Сегодня хотелось бы рассказать про один из способов, как можно создать локальный мультиплеер в Unity. Данное решение подходит для шоукейсов, теста фич или локального мультиплеера. К примеру, если вам хочется видеть, что делает игрок, но не хочется скажем на андроиде тратить лишние ресурсы и забирать скринкаст с помощью ADB, то можно просто поднять сервер на какой-то машинке в виде копии приложения, которое работает на телефоне, и слать туда информацию о действиях игрока.

    Я вкратце опишу, как можно это сделать с помощью HLAPI на Unet, но не через NetworkingManager, а чуть более низкоуровнево. По доброй традиции приложу пример своей реализации такого клиент-серверного взаимодействия. Пример прошу не судить строго, так как я прекрасно понимаю, что архитектура данного решения никуда не годится и создаёт кучу проблем в перспективе. Моей целью было максимально быстро (за выходные) написать систему, в которой можно показать принцип работы с сетью. Также скажу с какими проблемами пришлось столкнуться. В данном примере реализации предполагается, что сервер так же является Unity приложением.

    В интернете самым частым примером того, как сделать мультиплеер является чат, но я люблю игры, и делать чат мне показалось скучным. Поэтому я решил разобрать то, как можно сделать мультиплеер на примере крестиков-ноликов. Допустим, что вся логика игры, определения победителя, смены ходов и т. п. написаны, и нам осталось прикрутить сервер. В простейшем случае в крестиках-ноликах нам надо обрабатывать 2 сообщения. Определение порядка хода (раздача айдишников) и захват клетки.

    В целом игра в данном примере работает очень просто. Есть 2 сцены. Загрузочная и геймплейная. При загрузке геймплейной сцены у нас генерируется поле на котором играют игроки. Там же происходит проверка условия победы, есть классы отвечающие за работу UI, а так же порядок ходов и в целом логику игры, но нам это не особо интересно. Основные классы, которые отвечают за сетку - это Server, Client, NetManager и отдельный файл для сообщений NetMessages и определённый в нём enum MyMsgType. Они представляют из себя обёртку над средствами Unet. С точки зрения Unet основные классы, которые мы будем использовать - это NetworkClient, NetworkServer, MessageBase и MsgType. Что это за классы?

    Самые простые классы - это MessageBase и MsgType. Первый - это абстрактный класс, от которого надо наследовать все наши сообщения, чтобы пересылать их между клиентом и сервером. MsgType - это просто класс, который хранит в себе константы, отвечающие за определённый набор вшитых в Unet сообщений.

    NetworkServer - это синглтон, который предоставляет возможность обрабатывать общение с удалёнными клиентами. Под капотом он использует экземпляр NetworkServerSimple и по сути представляет из себя удобную обёртку над ним. Для начала нам надо запустить сервер на определённом порте. Для этого необходимо вызвать метод Listen(int serverPort) - этот метод запускает сервер на порте serverPort. (Напомню, что все порты в диапазоне от 0 до 1023 являются системными и их не стоит использовать в качестве параметра данного метода)

    Отлично сервер работает и слушает какой-то порт. Теперь нужно, чтобы он реагировал на сообщения. Для этого нужно зарегистрировать хэндлер с помощью метода RegisterHandler(short msgType, Networking.NetworkMessageDelegate handler) . Данный метод принимает параметром тип сообщения и делегат. Делегат в свою очередь должен принимать входным параметром NetworkMessage. Допустим мы хотим, чтобы в тот момент, когда к нему присоединялся игрок на сервере начала загружаться геймплейная сцена, а так же раздавались айдишники игроков. Тогда нужно зарегистрировать хэндлер на соответствующее сообщение, а так же реализовать метод, который мы будем передавать в качестве делегата для регистрации.

    Выглядит это примерно так:

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessage msg) { Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) { SendPlayerID(connId, -1); } else { int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS[(index + 1) % Constants.PLAYERS_COUNT]; SceneManager.LoadScene(1); } }


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

    Простой сервер есть. Теперь бы клиенты не помешали. Для этого воспользуемся классом NetworkClient. Для того, чтобы присоединиться к серверу надо просто вызвать метод Connect(string serverIp, int serverPort) . В качестве порта мы устанавливаем порт, который слушает наш сервер, в качестве айпи ставим localhost, если мы тестируем наше приложение на одной машине или же айпи компа в локальной сети, который мы используем в качестве сервера (Узнать его можно или в настройках сети, или в консоли с помощью команды ipconfig на том компе, который будет выступать в роли сервера).

    Замечательно, мы может подконнектиться. Ранее говорилось, что сервак у нас раздаёт айдишники. Значит нам надо во-первых, послать сообщение о айдишнике, а так же зарегистрировать хэндлер этого сообщения на клиенте. Как упоминалось ранее все сообщения надо наследовать от MessageBase. Для начала определим их (я предпочитаю это делать в отдельном файле):

    public class PlayerIDMessage: MessageBase { public int PlayerID; } public enum MyMsgType: short { PlayerID = MsgType.Highest + 1, }


    Чтобы послать данное сообщение вызываем на клиенте метод Send(short msgType, Networking.MessageBase msg) , который отправит сообщение msg «типа» msgType серверу, или на сервере один из методов в зависимости от цели SendToAll(short msgType, Networking.MessageBase msg) или SendToClient(int connectionId, short msgType, Networking.MessageBase msg) , где connectionId - это id определённого клиента.

    private void OnPlayerID(NetworkMessage msg) { PlayerIDMessage message = msg.reader.ReadMessage(); _CurrentUser.PlayerID = message.PlayerID; SceneManager.LoadScene(1); }


    В принципе всё. Дальнейшее использование зависит от конкретики. Создаём нужные нам сообщения в зависимости от геймплея и отправляем их. В крестиках-ноликах я так же определил ещё одно сообщение, которое отвечает за захват точки. Целиком реализацию проекта можно посмотреть .

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

    1. Проверьте, что редактор юнити не заблокирован вашим фаерволом для общения по протоколам TCP и UDP. Однажды я потратил на это некоторое время, при том, что добрался до фаерволла и поставил нужный порт в исключения, но не проверил то, что редактор незаблокирован.

    2. Передавайте в сообщениях value-type. Reference-type будут передавать чушь, так как по адресу который вы хотите передать в другую аппу, неизвестно что лежит. (Я думаю, что это и так понятно тем, кто понимает, как работают value и reference type)

    В случае, когда мы говорим про локальную сеть и заранее известное железо нет многих проблем возникающих в случае “настоящего” мультиплеера. Практически не нужно задумываться о задержках, флуктуациях, потерях пакетов и прочем. Поэтому такое решение может быть полезно, хотя и эти проблемы можно решать с таким подходом до некоторого предела. В сравнении с более высоким уровнем абстракции в Unet, через NetworkManager, NetworkBehavior и т.п., оно предоставляет более понятную и очевидную гибкость (на мой взгляд), если на клиентах требуется загружать разные сцены и т. п., а скажем сервер используется, как стриминг, и показывает то, что отображается на девайсе игрока, принимая его позицию и поворот + дублируя происходящее на своей стороне. В остальных случаях, когда необходимо решение быстрее и вы умеете работать с сетью, Unet предоставляет возможность писать на транспортном уровне.

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