18

Unity UDP P2P (напрямую и через собственный релэй)

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


Когда-то я сделал ставку на оклусовскую сетевуху для нашей игры, и на сегодняшний день это вылилось в переписывание половины игры (и ф*йс*ук забанен, и сетевуху свою они сворачивают), но так же наша команда написала свой собственный релэй-сервер на C# (http://gitlab.sa-wd.ru/DarkanSoince/networking/-/tree/v3).


Как писать.


Шишек набито достаточно, так что вот вам описание на пальцах как делать сетевуху:

1) Пакет из байтов для отправки делаем под 500-1200 байт (есть мнение про 500, я использую 1000)

2) В пакет влазит еще? - суем, а не отправляем отдельным пакетом
3) Не влазит? отправляем и забиваем новый пакет вне таймера
4) Отправляем по таймерам, а не по кадрам (хотя тут как хотите)

5) Поскольку все пакеты одинаковые, не пересоздаем пакет, а заменяем в нём данные перед отправкой

6) Reliable пакеты отправляем только когда надо знать что пакет дошел (например, отправить старт игры - релиэйбл, движение - не релиэйбл)

7) Если вам надо менять состояние чего-либо, что меняется часто (например владелец предмета), посылаем это состояние в каждом пакете, а не отдельной командой для смен состояния (например вместе с движением).


Простой путь - смотрите тут примеры - http://gitlab.sa-wd.ru/DarkanSoince/networking/-/tree/v3/Program

Смените IP на 127.0.0.1 тут - http://gitlab.sa-wd.ru/DarkanSoince/networking/-/blob/v3/Networking/ClientProgram.cs

Запускать сначала мастер, потом два клиента.

cd .\Program\Master\
dotnet run --framework net6.0
cd .\Program\Client\
dotnet run --framework net6.0

Сервер запустит и после отключения клиентов убьет мастер.


Сложный путь. Штош, перейдем к коду.


Мы используем гугл-протобаф для сериализации/десериализации и первым делом вам надо будет взять и понаписать файлов в /Proto/Event (смотрим код репы, читаем тут https://developers.google.com/protocol-buffers/docs/proto3#scalar) про каждый тип пакета, который вы вообще умеете отправлять.


Допустим что мы хотим что бы персонаж ходил

ProtoTransform.proto (пример названия файла в /Proto/Event)
syntax = "proto3";
package protos;
message ProtoTransform {
int32 netId = 1;  // что двигаем
float positionX = 2; 
float positionY = 3; 
float positionZ = 4;
float rotationX = 5; 
float rotationY = 6; 
float rotationZ = 7; 
float rotationW = 8; 
int32 sequence = 9; // счетчик, который всегда идет вверх
float velocityX = 10; 
float velocityY = 11; 
float velocityZ = 12; 
int32 playerId = 14; 
float angularVelocityX = 15; 
float angularVelocityY = 16; 
float angularVelocityZ = 17;
}

Зачем sequence - что бы два раза не делать одно и то же если пакет пришел позже следующего (а он может и прийти не по порядку и не прийти вовсе, просто при отправке его плюсуем)


Тыкаем build proto.bat, всё, магия готова и наш протофайл сбилжен :)


Тыкаем unity package.bat и у нас в папке /UnityPackage появится билд сетевухи


Запускаем юнити

Window -> package manager -> + -> add package from disk -> папка /UnityPackage/package.json


// Это будет наш нетворкинг. Да, весь в одном файле :)
using SAWD.Networking;
public class SAWDNet : MonoBehaviour {
public enum MessageType {
Transform = 100, // это что бы не перебивать значения из скомпилленого файла
LuboeDrudoe = 101
}
Protos.ProtoTransform protoSendTransform = new Protos.ProtoTransform();
private void Start() {
clientProgram = new ClientProgram();
clientProgram.Start();
clientProgram.Enqueue_ToMaster("default"); // http://gitlab.sa-wd.ru/DarkanSoince/networking/-/blob/v3/Program/Master/.env (этот дэфаулт - QUEUE_default= отсюда, а не из Name, обязательно законфигить под себя!)
// входящий пакет с трансформом
Net.On((int) MessageType.Transform, (point, data) => { // входящий трансформ
var protoOnReceiveTransform = ProtoTransform.Parser.ParseFrom(data.Data);
// protoOnReceiveTransform вот ваш пакет данных, делайте что хотите с ним
});
// я подключился к серверу
Net.On((int) Net.MessageType.Server_Connected, ((point, data) => {
Debug.Log("-Server_Connected- SAWDNet.localPlayerId:" + SAWDNet.localPlayerId);
var connected = Proto.Event.Connected.Parser.ParseFrom(data.Data);
Debug.Log("-Server_Connected- connected:" + connected);
foreach (var userId in connected.Ids) {
Debug.Log("-Server_Connected- userId:" + userId);
//RemoteUser.CreateUser(userId);
// создайте тут юзера (аватар и тд с ид userId). Тут могут быть дубли, создавайте один раз на юзера!
}
}));
// кто-то подключился к серверу после меня
Net.On((int) Net.MessageType.Server_AnotherUserConnected, ((point, data) => {
var connected = Proto.Event.Connected.Parser.ParseFrom(data.Data);
//RemoteUser.CreateUser(connected.Id);
Debug.Log("-Server_AnotherUserConnected- connected:" + connected);

SendTransform(); // один раз отправим трансформ новому подключившемуся

}));

}


private void Update()

{
Net.Update();
if (!Debug.isDebugBuild) return;
while (Net.Logs.TryDequeue(out var log)) {
Debug.Log(log); // у сетевухи разные треды, читаем лог в главном треде
}
}


private void SendTransform(){
// отправка один раз для примера
protoSendTransform.PositionX = lastLocalPosition.x;
protoSendTransform.PositionY = lastLocalPosition.y;
protoSendTransform.PositionZ = lastLocalPosition.z;
Net.Broadcast(GroupType.Client | GroupType.Server, (int) SAWDNet.MessageType.Transform, protoSendTransform , true); // тру значит релиэйбл, будет долбить пока не передаст, ВАМ тут тру не надо, вам надо фолс :)
}


Запускаем сервер


cd .\Program\Master\
dotnet run --framework net6.0

(проверяем что IP мастер сервера в клиенте указан верно, иначе ничего не подключится! http://gitlab.sa-wd.ru/DarkanSoince/networking/-/blob/v3/Networking/ClientProgram.cs)


Запускаем пару клиентов. После коннекта они отправят и получат protoSendTransform или через сервер или напрямую, если они смогут подключиться.


Может быть когда-нибудь я осилю написать нормальную документацию.

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"