dagon:network — асинхронный UDP-клиент для игр

В Dagon грядет расширение для создания сетевых игр! Я решил интегрировать библиотеку ENet и написать поверх нее легковесный клиент, при помощи которого игра сможет взаимодействовать с UDP-сервером. Причем это работает через менеджер событий и асинхронную систему обмена сообщениями (dagon.core.messaging), о которой я писал в соответствующем посте. Это дает сразу два преимущества: сетевой клиент не блокирует игровую логику (поскольку работает в собственном потоке), и гарантируется порядок отправки сообщений (сообщения добавляются в SPSC-очередь).

Выглядит это так:

import dagon.ext.network;

class TestScene: Scene
{
    MyGame game;
    NetworkClient netClient;

    this(MyGame game)
    {
        super(game);
        this.game = game;
    }

    override void afterLoad()
    {
        // Инициализация игры...

        netClient = New!NetworkClient("GameClient",
            game.networkManager, "XXX.XXX.XXX.XXX", 1234, this);
        netClient.run();

        eventManager.messageBroker.enabled = true;
    }

    override void onMessage(
        int domain, string sender, string message, void* payload)
    {
        if (sender == "GameClient")
        {
            logInfo("[Server] ", message);
        }
    }

    override void onKeyDown(int key)
    {
        send("GameClient", "Hello, World!");
    }
}

class MyGame: Game
{
    NetworkManager networkManager;

    this(uint w, uint h, bool fullscreen, string title, string[] args)
    {
        super(w, h, fullscreen, title, args);
        networkManager = New!NetworkManager(this);
        currentScene = New!TestScene(this);
    }
}

Реализация полностью потоко-безопасна — хост и пир ENet, ассоциированные с NetworkClient, изолированы в рамках потока, и цикл enet_host_service у него тоже свой собственный.

На данный момент NetworkClient работает только со строками (как, собственно, и сам брокер сообщений). Для поддержки бинарных данных, видимо, придется чуть изменить архитектуру. Кроме того, хочется, чтобы было из коробки шифрование, так что буду копать в сторону AEAD-алгоритмов.

Обновления

Dagon 1.4.0

Добавлена поддержка сжатия текстур в BC4 и BC7, бэкпортированная из Dagon 2. Компрессор BC7 является D-портом библиотеки bc7enc Рича Гелдриха, компрессор BC4 написан с нуля.
Модуль dagon.resource.sdlimage объединен с dagon.resource.texture.
prefilterCubemap теперь принимает опциональный параметр lumaScale, коэффициент яркости для входных значений кубмапа.
Исправлен баг, из-за которого свойство Application.fullscreen не обновлялось по конфигу.

dlib 1.5.1

Небольшой багфикс-релиз dlib 1.5, в котором был исправлен неприятнейший баг декодера JSON — пустые объекты и массивы интерпретировались как узлы JSONType.Null.

Chillwave Drive — новые машины

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

Обновления

Dagon 1.3.0

Выпустил новую версию движка, до этого также вышло несколько багфиков для 1.2. Метод Application.takeScreenshot теперь совершает полную отрисовку кадра, чтобы все изменения в рендере отразились на скриншоте. Добавлены методы Material.copyTo и Material.copyFrom для копирования свойств одного материала в другой. Добавлено свойство Entity.blurOnlyRotation, предназначенное для ограничения motion-векторов угловой скоростью, игнорируя изменение позиции — это важно для объектов, которые вращаются, но не меняют своего положения в экранном пространстве, таких, как колеса машины.

Улучшен префильтр яркости для эффекта glow: добавлен верхний порог яркости, а нижний теперь не срезает пиксели жестко, используя технику soft-knee для плавной градации. Разница заметна, например, при свечении неба, которое имеет неравномерную яркость. Соответственно, добавлены новые опции render.conf: glow.softKnee, glow.maxEnergy.

Исправлены баги рендеринга декалей. Я использую диффузные декали для размытых теней под днищем машин, но это приводило к тому, что тень некрасиво наползала на вертикальную стенку, когда машина приближалась к ней вплотную. Это вызвано вырожденным случаем при расчете текстурных координат проекции. Проблема исправлена тем, что прозрачность декали зависит от ее копланарности поверхности, на которую она проецируется. Если поверхность перпендикулярна, то прозрачность равна нулю.

dlib 1.5.0

Вышло небольшое обновление dlib. Добавлена функция trsMatrix для создания матрицы трансформации из смещения/поворота/масштаба за один шаг, без перемножений. Появилась функция lerpAngle для линейной интерполяции углов по кратчайшей дуге, а также shortestAngleDelta для вычисления угловой дельты по кратчайшей дуге. Добавлена новая изинг-функция easeOutElastic. Функция wrapAngle теперь работает с любыми угловыми единицами при помощи опционального параметра периода (по умолчанию период равен 360, что означает градусы).

Chillwave Drive

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

Как сделать лидерборд в гонках

Лидерборд — это таблица, ранжирующая участников заезда по какому-либо критерию, например, по позиции на маршруте. Фактически, это логика гонок, которая определяет, кто побеждает, а кто проигрывает. Правильная реализация лидерборда — не такая уж простая задача, как может показаться, потому что она зависит от того, как игра хранит трассу, и во многом пересекается с кодом ИИ-оппонентов.

Если отталкиваться от методологии, которую я описал в статье Как сделать ИИ для гонок, то задача упрощается – можно использовать следующий подход, который показался мне весьма надежным и лишенным логических изъянов.

(далее…)

Dagon 1.2.0

Выпустил новую версию Dagon. Основным поводом к этому релизу стало исправление серьезного бага, связанного с горячим подключением геймпадов (в качестве индексов для GameInputDevice использовались неправильные ID). Кроме того, игра заметно подтормаживала при вызове InputManager.getAxis из-за использования SDL_GameControllerGetAxis — эта проблема была решена использованием значений осей из событий SDL_CONTROLLERAXISMOTION. Соответственно, GameInputDevice теперь имеет поле axisValues.

Исправлена утечка памяти в методе Application.takeScreenshot.

Добавлена поддержка суперсэмплинга. Он управляется при помощи опции ss.sampleRatio в render.conf. Если выставить в нее 2, то игра будет рендериться в двухкратном разрешении.

Шейдер FXAA заменен на более качественную версию 3.11. Добавлена поддержка независимого от разрешения радиуса размытия для фильтра свечения (glow). Если в render.conf присутствует опция glow.normalizedRadius, то обычный glow.radius не используется — радиус вычисляется динамически как доля высоты экрана. Например, если выставить значение 0.005, то при 1080p радиус свечения будет 6 пикселей.