1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/leng_yue-GameDesigner

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

GDNet

Это сеть, разработанная специально для игр. Она представляет собой фреймворк и систему состояний на основе действий. GDNet разработана с использованием C# и поддерживает .NET Framework и Core версии. В настоящее время она в основном используется в Unity3D, а также в проектах с формами и консольных приложениях.

Система обладает высокой расширяемостью и позволяет быстро расширять новые протоколы. На данный момент поддерживаются tcp, gcp, udx, kcp и веб-протоколы. GDNet проста в освоении, имеет подробные комментарии к API.

Версия GDNet 6.0: изменения

  1. Методы Send и SendRT в версии 5.0 были заменены на Call. Это изменение касается как сервера, так и клиента.
  2. Поля protocol и func объединены в одно поле protocol. Поля methodHash и func удалены.
  3. Метод Call заменён на Request. Клиент может использовать await для ожидания результата. Сервер должен записывать player.Token после получения запроса. При отправке ответа клиенту необходимо использовать параметр Token.
  4. Добавлены примеры распределённых систем и несколько основных классов для работы с ними:
    • ConsistentHashing — класс для создания виртуальных узлов и обработки хеш-цикла;
    • LoadBalance — класс балансировки нагрузки, который включает в себя согласованное хеширование, пул клиентов и маршрутизацию запросов к физическим узлам с помощью согласованного хеширования при обращении к базе данных и циклический перебор при обращении к другим серверным узлам;
    • UniqueIdGenerator — генератор распределённых уникальных идентификаторов, аналогичный снежинкам. Вместо использования времени для вычисления идентификатора используется настраиваемый счётчик. Если идентификатор машины уникален, то распределённые узлы не будут генерировать одинаковые идентификаторы. Количество бит, используемых для идентификатора машины и счётчика, можно настроить по своему усмотрению. Например, 16 бит для идентификатора машины и 48 бит для счётчика.
  5. Фреймворк Client Framework переименован в GameCore. Оптимизирован процесс установки.
  6. В системе состояний добавлены примеры использования кода для создания состояний, добавления действий и поведения. Можно использовать Excel для настройки навыков и динамической конфигурации состояний навыков.

Схема модулей

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

Использование

  1. Скачайте GameDesigner и распакуйте его. Откройте Unity и перейдите в Window/PackageManager. Нажмите «+», чтобы добавить пакет из папки на диске.

  2. Выберите путь к папке GameDesigner/GameDesigner/package.json для импорта пакета GDNet.

  3. Если всё сделано правильно, вы увидите пакет в списке.

  4. Установите ApiCompatibilityLevel* = .NET 4.x и AllowUnsafeCode в настройках проекта. Для версий после 2021 года установите ApiCompatibilityLevel* = .NET Framework.

  5. Создайте серверный проект. Вы можете использовать консоль или приложение с формой, либо добавить новый серверный проект в Assembly-CSharp проект Unity.

  6. Добавьте серверный проект, выбрав консольный проект.

  7. Определите имя сервера и выберите путь к корневому каталогу вашего проекта Unity, на уровне с папкой Assets.

  8. Добавьте существующий проект, выбрав файл GameDesigner.csproj из каталога GameDesigner.

  9. Выберите каталог GameDesigner, где находится файл GameDesigner.csproj. 10. Щёлкните правой кнопкой мыши на ссылку «Server», появится меню, выберите в нём пункт «Добавить ссылку».

  10. Выберите пункт «GameDesigner» в меню «Проекты», подтвердите свой выбор.

  11. Создайте новый файл скрипта службы (Service), этот файл будет содержать ваш класс сервера.

internal class Client : NetPlayer // Ваш класс клиента
{
}

internal class Scene : NetScene<Client> // Ваш игровой класс сцены
{
}
class Service : TcpServer<Client, Scene> // Ваш класс сервера
{
    protected override bool OnUnClientRequest(Client unClient, RPCModel model)
    {
        Console.WriteLine(model.pars[0]);
        // Вы можете понимать это как возврат true, если введённые имя пользователя и пароль верны, и false, если имя пользователя или пароль неверны.
        return true; // 100% необходимо понять это. Если вы вернёте false, то будете постоянно вызываться здесь и не сможете вызвать методы с атрибутом [Rpc]. Если вы возвращаете true, клиент может вызывать методы с [Rpc] после отправки команды SendRT("метод", "параметр").
    }
    [Rpc(cmd = NetCmd.SafeCall)] // После использования SafeCall первый параметр будет содержать объект клиента, который является клиентом, отправившим запрос, а этот параметр — соответствующий объект этого клиента.
    void test(Client client, string str)
    {
        Console.WriteLine(str);
        SendRT(client, "test", "Ответ сервера rpc"); // Ответ сервера
    }
}
  1. В методе main напишите следующий код:
var server = new Service(); // Создаём объект сервера
server.Log += Console.WriteLine; // Регистрируем метод вывода сообщений сервера в консоль
server.Run(9543); // Запускаем сервер на порту 9543
while (true)
{
    Console.ReadLine();
}
  1. Создайте проект клиента для консоли, следуя тем же шагам, что и при создании проекта сервера.

  2. Определите класс Test для тестирования процесса rpc:

class Test
{
    [Rpc]
    void test(string str)
    {
        Console.WriteLine(str);
    }
}
  1. В методе main добавьте следующий код. Этот код предназначен для проекта клиента и не должен использоваться в Unity, так как это приведёт к зависанию программы:
TcpClient client = new TcpClient();
client.Log += Console.WriteLine;
Test test = new Test();
client.AddRpcHandle(test);
client.Connect("127.0.0.1", 9543).Wait();
client.SendRT("test", "Первый вызов метода OnUnClientRequest на сервере.");
client.SendRT("test", "Запрос клиента rpc.");
while (true)
{
    Console.ReadLine();
}

На этом основное использование завершено.

Использование Nginx в качестве шлюза

Если вам нужен серверный шлюз, вы можете использовать Nginx для пересылки запросов на реальный сервер. После добавления Nginx запросы будут проходить следующим образом: клиент → Nginx → игровой сервер, сервер → Nginx → клиент.

worker_processes  1;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
events {
    # Количество подключений клиентов
    worker_connections  10240;
}
stream{
    upstream gameServer{
        # Здесь находится реальный игровой сервер. Если он находится в облаке Alibaba Cloud, Tencent Cloud, укажите соответствующий IP-адрес.
        server 127.0.0.1:6667;
    }
    server{
        # Порт, на котором Nginx слушает запросы от клиентов.
        listen 9543;
        proxy_pass gameServer;
    }
}

Подробности см. по ссылке: https://www.cnblogs.com/knowledgesea/p/6497783.html.

Использование MySQL

  1. Скачайте MySQL с официального сайта или из группы GDNet.
  2. Используйте Navicat для создания базы данных MySQL и таблиц.
  3. После установки MySQL и Navicat создайте базу данных и таблицы. Затем используйте инструмент MySQL ORM для генерации файлов классов *.*cs.
  4. Перетащите сгенерированные файлы в проект Unity. Добавьте ссылки на эти файлы с помощью инструмента GameDesigner/Network/ExternalReference.

Объектный пул

GDNet предоставляет два типа пулов объектов: BufferPool для двоичных данных и ObjectPool для классов. В сетевом коде используется BufferPool, который позволяет быстро читать и записывать данные, вместо того чтобы каждый раз создавать новые массивы byte[].

Пример использования BufferPool:

var seg = BufferPool.Take(65535); // Выделяем 65535 байт памяти
seg.WriteValue(123); // Пишем значение 123
BufferPool.Push(seg); // Возвращаем память в пул для повторного использования
var seg1 = BufferPool.Take(65535); // На этот раз берём из пула уже существующий объект seg
seg1.WriteValue(456);
BufferPool.Push(seg1); // Снова возвращаем память в пул

Быстрая сериализация

В GDNet реализована быстрая сериализация, которая работает в 5–10 раз быстрее, чем protobuff. В примере 1 используется адаптер быстрой сериализации, который позволяет синхронизировать до 10 000 кубов. При использовании protobuff можно синхронизировать только 2500 кубов.

Существует три версии сериализатора:

  • NetConvertOld — строковая сериализация с низкой производительностью.
  • NetConvertBinary — двоичная сериализация, более эффективная, чем protobuff, но с таким же размером данных.
  • NetConvertFast2 — быстрая сериализация для высокопроизводительных систем.

Для использования NetConvertFast2 необходимо создать привязки типов. Для этого можно использовать инструмент генерации привязок, доступный по ссылке: https://gitee.com/leng_yue/fast2-build-tool. ``` NetConvertFast2.SerializeObject(new Test()); // Сериализация класса Test var obj = NetConvertFast2.DeserializeObject(seg); // Десериализация класса Test

Fast2BuildToolMethod.Build(typeof(Test), AppDomain.CurrentDomain.BaseDirectory); // Генерация типа привязки Test Fast2BuildToolMethod.BuildArray(typeof(Test), AppDomain.CurrentDomain.BaseDomain); // Генерация массива типов привязки Test Fast2BuildToolMethod.BuildGeneric(typeof(Test), AppDomain.CurrentDomain.BaseDirectory); // Генерация универсального типа привязки Test


В новой версии появилась возможность динамической компиляции типов привязки во время выполнения.

Fast2BuildMethod.DynamicBuild(BindingEntry.GetBindTypes()); // Динамическая компиляция указанного списка типов


Подробная информация доступна в примере GameDesigner\Example\SerializeTest\Scenes\example3.unity.

## Самая быстрая сериализация в сети

> Версия 3 «Экстремально быстрой сериализации» — самая быстрая в сети, никто не возражает?

**Вопрос**: почему так быстро?

**Ответ**: версия 3 «Экстремально быстрой сериализации» использует метод копирования памяти по адресам, напрямую копируя весь блок памяти объекта без необходимости чтения и записи каждого поля по отдельности.

**Вопрос**: что делать, если класс или структура содержит строку или пользовательский класс?

**Ответ**: если в классе нет полей базовых классов (byte, int, long...), то для хранения данных будет использоваться 8 дополнительных байт памяти для указателя на данные. Затем записываются данные строки или другого пользовательского класса.

**Вопрос**: если класс содержит только поля базовых классов, то это очень быстро. А если класс имеет поля пользовательских классов и базовых классов, занимает ли он больше места, чем другие методы сериализации, такие как fastbuff или MemoryPack?

**Ответ**: да, если класс содержит поля пользовательских типов, то потребуется дополнительно 8 байт данных. Можно сжать данные перед выполнением других операций.

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

Для получения адреса экземпляра объекта можно использовать код:

```var address = Unsafe.AsPointer(ref value); // Для структуры нужен только один указатель
address = (void*)(Unsafe.Read<long>(address) + 8); // Для класса нужны два указателя, чтобы получить адрес данных

Все, кроме базовых типов (byte, int, long...) требуют двухуровневых указателей, таких как string, DateTime, decimal... и массивы. Длина массива записывается как значение типа long после него.

Используя полученный адрес объекта, можно выполнить копирование памяти с помощью метода Unsafe.CopyBlock.

{
    int offset = 154; // Здесь вычисляется размер всех полей класса
    Unsafe.CopyBlock(ptr, address, (uint)offset); // Полное копирование
}

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

ECS модуль похож на GameObject->component в Unity. В ECS, GameObject = entity, component = component, выполняются системы. Основной процесс ECS и GameObject аналогичен, но компоненты в ECS можно повторно использовать, а компоненты GameObject — нет. При создании более 10 000 объектов GameObject нужно заново создавать объекты и компоненты, в то время как ECS при вызове Destroy помещает entity или компонент в пул объектов, ожидая следующего повторного использования. На самом деле объект не освобождается, поэтому производительность выше, чем у GameObject.

//ecs время компонента
public class TimerComponent : Component, IUpdate //наследование интерфейса IUpdate приводит к тому, что метод Update вызывается каждый кадр
{
    private DateTime dateTime;
    public override void Awake()
    {
        dateTime = DateTime.Now.AddSeconds(5);//при инициализации текущее время переносится на 5 секунд вперёд
    }
    public void Update()
    {
        if (DateTime.Now >= dateTime)//когда 5 секунд прошло, удалить этот временной компонент, фактически поместить его в пул объектов
        {
            Destroy(this);
        }
    }
    public override void OnDestroy()//вызывается перед помещением в пул объектов
    {
    }
}

static void Main(string[] args)
{
    var entity = GSystem.Instance.Create<Entity>();//создать объект сущности, который будет искать в пуле объектов, если в пуле нет объекта, он будет создан, иначе будет извлечён из сущности
    entity.AddComponent<TimerComponent>();//добавить компонент времени, также искать из пула объектов, если нет, создать, иначе извлечь TimerComponent
    while (true)
    {
        Thread.Sleep(30);
        GSystem.Instance.Run();//выполнять систему ECS каждый кадр
    }
}

MVC модуль

MVC модуль: модель, контроль, вид разделены. MVC модуль подходит для синхронизированных игр. Модель определяет поля, атрибуты и события объекта. Контроллер выполняет бизнес-логику, а представление отображает результат. В синхронизированной среде MVC разделено, каждый обрабатывает своё, достигая независимости.

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

HybridCRL горячее обновление

В клиентском фреймворке поддерживается полное горячее обновление, которое очень просто использовать.

[SyncVar] поле или атрибут синхронизации

Переменные синхронизируются между клиентом и сервером. Переменные могут быть синхронизированы между игроками. Если используется [SyncVar], необходимо включить OnReloadInvoke в меню Unity GameDesigner/Network/InvokeHepler.

Пример кода для [SyncVar]:

using UnityEngine;

internal partial class SyncVarHandlerGenerate
{
    internal virtual bool Equals(Rect a, Rect b) //[syncvar] Rect тип не имеет сравнения, поэтому необходимо написать его самостоятельно
    {
        if (a.x != b.x) return false;
        if (a.y != b.y) return false;
        if (a.width != b.width) return false;
        if (a.height != b.height) return false;
        return true;
    }
}

internal class SyncVarHandler : SyncVarHandlerGenerate
{
    //выполняется только максимальный объект, то есть класс SyncVarHandler выполняется, класс SyncVarHandlerGenerate не будет выполняться
    public override int SortingOrder => 100;

    //если сгенерированный метод Equals неверен, вы можете переписать уже сгенерированный метод Equals ниже, чтобы выполнить собственное суждение
}

Тестирование RPC малых данных на уровне 100 000

Здесь мы протестировали 1 000 000 запросов и ответов от клиента к серверу, и потребовалось 4,67 секунды.

Необходимо импортировать следующие пространства имён:

using Net.Client;
using Net.Config;
using Net.Event;
using Net.Server;
using Net.Share;
using Net.System;
using System;
using System.Diagnostics;
using System.Threading;

Код теста:

internal class Program
{
    static Stopwatch stopwatch;

    static void Main(string[] args)
    {
        NDebug.BindLogAll(Console.WriteLine);

        BufferStreamShare.Size = 1024 * 1024 * 100;//сервер может кэшировать данные размером до 10 МБ для каждого клиента

        //это серверная часть, которую можно скопировать в другой проект консоли
        var server = new TcpServer();
        server.LimitQueueCount = 10000000;//тестирование производительности небольших данных, можно установить здесь, по умолчанию ограничено 65536
        server.PackageLength = 10000000;//размер пакета данных для быстрой передачи, один раз передаётся количество пакетов небольших данных
        server.PackageSize = 1024 * 1024 * 50;//максимальный размер принимаемых данных пакета, если превышен, они будут отброшены
        server.AddAdapter(new Net.Adapter.SerializeAdapter3());//используйте высокоскоростную сериализацию для сериализации модели данных rpc
        server.AddAdapter(new Net.Adapter.CallSiteRpcAdapter<NetPlayer>(server));//используйте высокоскоростной вызов rpc метода адаптера
        server.Run();

        //Это клиентская часть, которая может быть скопирована в другой консольный проект
        var client = new TcpClient();
        client.LimitQueueCount = 10000000;
        client.PackageLength = 10000000;
        client.PackageSize = 1024 * 1024 * 50;
``` **Текст запроса:**

client.AddAdapter(new Net.Adapter.SerializeAdapter3());
    client.AddAdapter(new Net.Adapter.CallSiteRpcAdapter(client));
    client.AddRpcHandle(new Program());
    client.Connect().Wait();

    client.SendRT(new byte[1]);//先进入服务器
    Thread.Sleep(500);

    stopwatch = Stopwatch.StartNew();

    for (int i = 0; i < 1000000; i++)
    {
        client.SendRT(NetCmd.LocalRT, 1, i);
        if (i % 10000 == 0)
            Thread.Sleep(50);
    }

    Console.ReadLine();
}

[Rpc(hash = 1)]
void test(int i)
{
    if (i % 10000 == 0)
        Console.WriteLine(i);
    if (i >= 999999)
    {
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);
    }
}
**Перевод текста на русский язык:**

Client.AddAdapter (новый Net.Adapter.SerializeAdapter3 ());
    Client.AddAdapter (новый Net.Adapter.CallSiteRpcAdapter (клиент));
    Клиент.AddRpcHandle (новая программа ());
    клиент.Connect (). Подождите ();

    клиент.SendRT (новый байт [1]); // сначала войти на сервер
    Поток.Sleep (500);

    секундомер = секундомер.StartNew ();

    для (int я = 0; я <1000000; я ++)
    {
        клиент.SendRT (NetCmd.LocalRT, 1, я);
        если (я% 10000 = 0)
            Поток.Sleep (50);
    }

    Консоль.ReadLine ();
}

[Rpc (хэш = 1)]
пустота тест (int я)
{
    если (я% 10000 = 0)
        Консоль.WriteLine (я);
    если (я> = 999999)
    {
        секундомер.Стоп ();
        Консоль.WriteLine (секундомер.Прошедшее);
    }
}

*Примечание: в запросе присутствуют фрагменты кода, которые не были переведены.* 13 Крис — 200 + 1000 + 200.

14 Мэйпл — 200 + 200. 

15 Синие точки льда — 10.  

Безымянные эксперты, общее пожертвование через WeChat — 653.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

GDNet — это двусторонний RPC-фреймворк для разработки сетевых приложений в Unity3D (клиент, сервер), оконных программ (клиент и сервер) и консольных проектов (сервис). Он обеспечивает высокую эффективность, стабильность, производительность и параллелизм. Поддерживает P2P, пробивание NAT, а также позволяет легко переключаться между различными пр... Развернуть Свернуть
MIT
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/leng_yue-GameDesigner.git
git@api.gitlife.ru:oschina-mirror/leng_yue-GameDesigner.git
oschina-mirror
leng_yue-GameDesigner
leng_yue-GameDesigner
master