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 или через сервер или напрямую, если они смогут подключиться.
Может быть когда-нибудь я осилю написать нормальную документацию.