UPD. Актуализировал на момент 26.11.2022. Хочу поделиться опытом с теми, кто хочет попробовать себя в написании сетевой игры, но не знает с чего начать. Так как информации по этой теме в интернете много, но полезную и актуальную было найти тяжело (а в русскоязычном сегменте и подавно), я решил собрать и структурировать то, что удалось найти. Итак, для написания сетевой игры на Unity сейчас есть несколько вариантов: UNet. Устаревшая сетевая технология. На данный момент deprecated и поддержка закончится в ближайшие пару лет. Но что же Unity предлагает взамен? NetCode. Потенциально крутая технология, которая будет работать в связке с Entity Component System. Но очень уж медленно она развивается, за пару лет существования вышло 6 версий разной степени багованности, api постоянно меняется и делать что-то серьезное на нем пока рановато. Когда ее доделают – неизвестно. Я слежу за ней уже около года и особого прогресса не заметил. Что тогда остается? Из бесплатных решений это: MLAPI. Альтернатива UNet с широким спектром возможностей. Достойное решение, стоит к нему присмотреться. Mirror. Доведенный до ума UNet, который потенциально может использоваться даже в MMO. Может работать как Клиент+Сервер, так и NoGUI-Сервер. И платные решения (ознакомится с ними не удалось, напишите у кого был опыт как они): Photon. DarkRift2. Таблица преимуществ этих решений от Unity: Мой выбор пал на Mirror, как на ближайший потомок UNet, использующий большинство принципов уже знакомого UNet. На примере простого проекта мы посмотрим основы Mirror, а именно: Настройка окружения NetworkMessage и spawn игрока в выбранной точке Синхронизация переменных посредством SyncVar Синхронизация переменных посредством SyncList Spawn предмета и взаимодействие с предметом Для статьи будем использовать Unity 2021.3.14f1 и Mirror 2022.10.0. Добавляем Mirror себе через Asset Store, создаем проект, импортируем Mirror (Window -> Package Manager -> Packages -> My Assets -> Mirror -> Import). Для начала нам нужно создать префаб игрока. Создаем пустой GameObject (назовем его Player), вешаем на него SpriteRenderer, задаем sprite Knob и масштабируем чтобы лучше его рассмотреть. Далее создаем скрипт Player.cs и вешаем его на тот же GameObject. Редактируем скрипт следующим образом: Компонент NetworkIdentity добавится автоматически при добавлении скрипта, наследуемого от NetworkBehaviour. В одном GameObject (и всех его потомках) может быть только один NetworkIdentity. NetworkIdentity позволяет отличить один сетевой объект от другого (для этого используем netId - его значение всегда будет уникальным).  Добавляем компонент NetworkTransform, чтобы положение нашего игрока синхронизировалось между всеми игроками. Ставим галочку ClientAuthority, чтобы изменения произведенные клиентом, считались валидными. UPD. С версии 2022.10.0 это галочка считается устаревшей, вместо нее выбираем способ синхронизации: SyncSettings -> Sync Direction -> ClientToServer Компонент NetworkIdentity также добавится автоматически при добавлении NetworkTransform (если его еще не было). Если вам нужно синхронизировать потомков, добавляйте NetworkTransformChild на тот же объект, где уже есть NetworkIdentity, и указывайте в Target тот transform, который нужно синхронизировать.  Делаем из нашего GameObject префаб. Получилось что-то такое: Далее создаем скрипт NetMan.cs, создаем пустой GameObject (назовем его NetMan) и вешаем на него скрипт. Это будет наш скрипт, который отвечает за старт сервера и подключение игроков. Пока просто наследуем класс от NetworkManager, на этом этапе этого будет достаточно. У нас в инспекторе появятся настройки сервера и добавится компонент KcpTransport. Докидываем на тот же GameObject компонент NetworkManagerHUD (он создает необходимое для подключения GUI). Don’t Destroy On Load. Будет ли объект существовать между сценами? Run In Background. Будет ли компонент продолжать работать когда окно программы неактивно? Auto Start Server Build. Будет ли сервер стартовать автоматически, если была выбрана опция билда «Server Build»? Show Debug Messages. По этой опции не удалось разобраться или найти какую-то информацию. Server Tick Rate. Количество обновлений сервера в секунду. Server Batching. Должен ли сервер сначала собрать текущую сетевую информацию и отправить ее в LateUpdate разом? Полезно для уменьшения нагрузки на CPU и сеть, но увеличивает задержку. Server Batch Interval. Чем выше это значение, тем реже будет отправляться сетевая информация.  Теперь нам нужно указать префаб, который будет спавниться в качестве игрока. Перетаскиваем префаб Player в поле Player Prefab и после этого убираем его со сцены (оставляем только камеру и NetMan). Первый этап готов. Выставляем выполнение в неполном экране (чтобы несколько экземпляров помещалось), делаем сборку, запускаем 2 экземпляра и проверяем. Один экземпляр стартуем как сервер, второй как клиент. На wasd двигаем своего персонажа, он успешно синхронизируется с другим экземпляром. На примере спавна в выбранной точке мы научимся отправлять сообщения на сервер. В настройках NetMan убираем галочку AutoCreatePlayer, дальше мы будем контролировать спавн игрока сами. Для этого мы изменим скрипт NetMan.cs. Начнем с создания struct с данными о позиции: Далее создадим метод непосредственно спавна, который будет выполняется только на сервере: Теперь перегрузим мтод OnStartServer (выполняется только на сервере) и добавим в него обработчик сетевого сообщения: Создадим метод, который будет активировать спавн (и выполняться локально на клиенте): И напоследок зададим условия для активации спавна:  Второй этап готов. Делаем сборку, проверяем. После подключения нужно кликнуть левой кнопкой мыши в точку, где игрок хочет засвапниться. Переходим к очень интересной фиче – SyncVar. Она позволяет избежать ручной синхронизации данных. Главное правило – меняем переменную только на сервере и не используем ее как данные (только как временное хранилище для данных, которые нам нужно обработать). Для начала подготовим объекты, которые мы будем использовать для наглядной синхронизации. Например, здоровье в виде красных кружков. Открываем редактирование префаба Player и добавляем ему несколько объектов, представляющих собой жизнь (Knob + красный цвет). Располагаем их так, чтобы было хорошо видно. Редактируем скрипт Player.cs, добавляем переменные: Сохраняем, закидываем объекты-жизни в переменную HealthGos и выставляем такое же количество в переменной Health. Добавляем в Update обновление объектов-жизней в соответствии с количеством жизней: И переходим к методу на клиенте, который будет выставлять Health в соответствии с синхронизированным значением: Теперь нам нужно сделать метод, который будет менять переменную _SyncHealth. Этот метод будет выполняться только на сервере. Далее переходим к методу, который также будет выполняться на сервере, но клиент сможет запросить его выполнение: Command используется для того, чтобы клиенты могли попросить сервер выполнить заданную команду. Rpc используется для того, чтобы сервер мог попросить клиентов выполнить заданную команду. Command можно вызывать на сервере+клиенте, но Rpc нельзя вызывать на клиенте. Передавать в Rpc и Command можно только ограниченный набор типов. Вызов Rpc в режиме сервер+клиент также выполнится на нем самом. Пример Rpc:  Все готово для синхронизации, зададим условия изменения жизней. На этом этапе сделаем простую схему – каждый игрок может только уменьшить свои жизни. Для этого дополним Update: Этап завершен. Теперь у игроков всегда будет актуальное количество жизней, даже у тех, кто присоединяется позднее (после изменения количества жизней у других игроков). Синхронизировать одну переменную это конечно хорошо, но для серьезных проектов нам понадобится инструмент посерьезнее. SyncList позволяет синхронизировать массивы данных. Разберемся с ним на примере сохранения пройденного пути по нажатию кнопки (просто для наглядности). Редактируем скрипт Player.cs по аналогии с SyncVar. Изменение массива на сервере: Команда для запроса с клиента на сервер: И обработчик события изменения массива на клиенте: Теперь перегрузим метод старта клиента: Синхронизация готова, но нам нужно задать условия изменения массива и визуализировать данные. Создадим пустой GameObject + SpriteRenderer + Knob + меняем цвет. Сохраняем как префаб Point. Добавим компонент LineRenderer на префаб Player, выставим ему ноль позиций и немного уменьшим ширину. Отредактируем скрипт Player.cs:   Этап завершен, посмотрим на результат. Во время выполнения игрок может нажать клавишу P и его позиция отправится в массив для синхронизации всем игрокам. Также точки соединяться линией, чтобы маршрут был виден наглядно. На последнем этапе мы посмотрим как спавнить предметы и взаимодействовать с ними. Добавим нашему игроку возможность стрелять пулями. Создадим новый скрипт Bullet.cs: Также создадим пустой GameObject + SpriteRenderer + Knob + меняем цвет. Вешаем на него скрипт Bullet.cs. Добавляем компонент NetworkTransform. Сохраняем как префаб Bullet. В скрипт Player.cs добавляем спавн пули на сервере: И запрос на свапн со стороны клиента: Выставляем условие появления пули: Добавим еще уничтожение игрока, если жизни закончились: В настройках NetMan выставляем префаб Bullet как доступный для спавна: Не забываем выставить префаб Bullet в переменную BulletPrefab префаба Player. Напоследок добавляем на префаб Player компонент CircleCollider2D и ставим галочку IsTrigger, чтобы пуля могла отловить попадание. Последний этап завершен. Проверяем. По нажатию правой кнопки мыши из игрока вылетает пуля и летит туда, где стоял курсор. Если по пути пуля встречает другого игрока – он теряет одну жизнь. Все пули синхронизированы, даже если игрок подключился после их спавна.  Надеюсь эти примеры помогут разобраться с азами работы с сетью в Unity. Знатоков этой темы призываю к обсуждению недочетов (про производительность и GC сейчас речь не идет). Полный проект можно скачать на гитхабе по этой ссылке.  Unity developer