Статьи

Тестирование HTML5-игры

HTML5 Логотип картофеляНесколько недель назад мой друг Дэвид Руссет написал отличную статью о «тестировании анимации ваших спрайтов для всех устройств и браузеров» . В этой статье использовалась эталонная среда под названием «HTML5 Potatoes Gaming Bench» для получения согласованной оценки для различных тестов спрайтов.

Я постараюсь объяснить вам, как мы создали эту платформу и какое решение мы приняли, чтобы она отражала эффективную производительность протестированного оборудования и браузера.

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

образ

Дельта время

Прежде всего, мы должны поговорить о дельте времени. Действительно, когда вы пишете игры или какие-либо ресурсоемкие приложения, принцип всегда один и тот же: у вас должен быть цикл рендеринга, где выполняется отображение (спрайты, эффекты и т. Д.).

Пользователь будет чувствовать отличную производительность, когда время между двумя визуализированными кадрами будет близко к известной цели (1000/60) мс (около 16 мс).

Поэтому нацеливание на 60 кадров в секунду будет обязанностью любого разработчика игр: никто не склонен испытывать задержки или медлительность в отзывчивости своей игры.

Фактически, дельта-время можно определить как истекшее время между двумя проходами в цикле рендеринга .

Чтобы вычислить это значение, вам просто нужно сохранить текущую дату () при запуске нового кадра и вычесть предыдущую сохраненную дату (). Результатом является количество миллисекунд между двумя кадрами:

var previousDate;

var computeDeltaTime = function() {
    if (!previousDate) {
        previousDate = Date.now();
        return 0;
    }

    var currentDate = Date.now();
    var delta = currentDate - previousDate;
    previousDate = currentDate;

    return delta;
};

Вызов этой функции непосредственно перед рендерингом нового кадра позволит нам определить текущее время дельты:

var renderLoop = function() {
    while (renderInProgress) {
        var deltaTime = computeDeltaTime();
        
        // render your frame here
    }
};

Визуализация петли

На самом деле, цикл рендеринга не может быть функцией while (true) {} . Действительно, JavaScript является однопоточным, поэтому зацикливание функции просто полностью блокирует ваш браузер.

Поэтому вы должны вызывать браузер каждый раз, когда запрашивается кадр. Для этого W3C представил новый API под названием requestAnimationFrame .

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

requestAnimationFrame намного лучше, чем простой API setTimeout, потому что API setTimeout может обеспечивать прерывистую анимацию и увеличивать потребление ресурсов путем рендеринга ненужных кадров.

Это идеальное место для рендеринга вашего нового кадра. И, очевидно, это идеальное место для оценки вашего времени дельты. (Стоит отметить, что requestAnimationFrame предоставляет вам параметр, указывающий время, на которое запланирована функция для вызова).

Итак, новый цикл рендеринга выглядит так:

var renderLoop = function () {
    var deltaTime = computeDeltaTime();

    // render your frame here
    
    // Schedule new frame
    requestAnimationFrame(renderLoop);
};

// First call
requestAnimationFrame(renderLoop);

Увы, API requestAnimationFrame поддерживается не каждым браузером в соответствии с www.caniuse.com :

образ

Чтобы не использовать эталонный тест для работы не в каждом браузере, мы можем использовать этот небольшой polyfill:

POTATOES.Tools.queueNewFrame = function (func) {
    if (window.requestAnimationFrame)
        window.requestAnimationFrame(func);
    else if (window.msRequestAnimationFrame)
        window.msRequestAnimationFrame(func);
    else if (window.webkitRequestAnimationFrame)
        window.webkitRequestAnimationFrame(func);
    else if (window.mozRequestAnimationFrame)
        window.mozRequestAnimationFrame(func);
    else if (window.oRequestAnimationFrame)
        window.oRequestAnimationFrame(func);
    else {
        window.setTimeout(func, 16);
    }
};

Вы можете заметить, что API setTimeout используется в качестве запасного варианта для API requestAnimationFrame .

Измерение FPS

Для эталонного теста требуется значение, называемое FPS (обозначающее количество кадров в секунду). Вычисление FPS является легкой задачей, когда у вас есть requestAnimationFrame и время дельты.

Для этого в HTML5 Potatoes Gaming Bench используется функция handleMetrics . Эта функция отвечает за вычисление непосредственного FPS и среднего FPS. Первый — это просто FPS, основанный на времени разницы между текущим кадром и предыдущим. Второй — это среднее значение FPS для заданного количества предыдущих кадров:

var fpsFrame = 20; // fps window frame
var fpsCap = 60;
var previousFramesDuration = [];

var handleMetrics = function () {
    previousFramesDuration.push(Date.now());

    if (previousFramesDuration.length >= fpsFrame) {

        if (previousFramesDuration.length > fpsFrame) {
            previousFramesDuration.splice(0, 1);
        }

        var avg = 0;
        for (var id = 0; id < fpsFrame - 1; id++) {
            avg += previousFramesDuration[id + 1] - previousFramesDuration[id];
        }
        avg /= fpsFrame - 1;

        POTATOES.GamingBench.currentFPS = Math.min(fpsCap, 1000.0 / (
                      previousFramesDuration[fpsFrame - 1] - previousFramesDuration[fpsFrame - 2]));
        POTATOES.GamingBench.averageFPS = Math.min(fpsCap, 1000.0 / avg);
    }
    POTATOES.Tools.queueNewFrame(handleMetrics);
};

Чтобы результаты были правильными, я установил Windows Performance Toolkit , чтобы сравнить показатели.

Для своих тестов я использовал эти стенды: http://www.html5potatoes.com/gamingbench/index.htm

образ

Используются два теста:

  • Холст пикселей манипуляции
  • Обработка изображений с веб-работниками

Анализатор производительности Windows дал следующие результаты:

образ

Мы видим, что первый тест выполняется между 25 и 30 кадрами в секунду. Второй работает почти на полной скорости.

Хорошей новостью является то, что результаты, рассчитанные на игровом стенде, похожи:

образ

образ

Второй тест не более 60 кадров в секунду, потому что мы используем requestAnimationFrame, который предотвращает ненужные рендеры.

Таким образом, мы можем считать, что измеренные значения согласуются.

Использование HTML5 Potatoes Gaming Bench

HTML5 Potatoes Gaming Bench — это открытая и бесплатная платформа, которую вы можете свободно использоватьSourire

Вы можете скачать весь код, использованный ранее здесь: http://www.html5potatoes.com/gamingbench/gamingbench.zip

Gaming Bench был разработан, чтобы предоставить инфраструктуру для тестирования того, что вы хотите в браузере. Итак, чтобы добавить свой собственный тест на игровой стенд, вам просто нужно создать объект Bench с этим кодом:

var tunnelBench = new POTATOES.GamingBench.Bench("Canvas pixels manipulations", "http://aka.ms/potatoes_tunnel",
    function (workbench) { // Init
        init(workbench, this.onInitCompleted);
    }, function () { // Run
        render = true;
        POTATOES.Tools.queueNewFrame(renderingLoop);
    }, function (workbench) { // End
        render = false;            
    });

POTATOES.GamingBench.registerBench(tunnelBench);

Вы должны предоставить 3 функции:

  • Init : эта функция вызывается один раз для создания DOM, требуемого вашим тестом. Он дает вам параметр workbench, который является DOM-контейнером, куда вы можете добавлять свои объекты.
  • Выполнить : эта функция вызывается для запуска вашего теста (здесь вы можете вызвать функцию queueNewFrame )
  • End : эта функция вызывается, чтобы остановить рендер и в конечном итоге очистить связанные ресурсы

Функция POTATOES.GamingBench.registerBench используется для добавления вашей скамьи в список активных скамей.

Чтобы запустить полную игровую скамью, вы должны использовать этот код:

// Starting benchmark
POTATOES.GamingBench.start();

Если вам нужно отменить его, просто используйте этот код:

POTATOES.GamingBench.cancel();

Вы также можете пропустить текущий тест и перейти к следующему:

POTATOES.GamingBench.skipCurrent();

Наконец, если вы хотите собрать результаты, вы должны использовать такой код:

POTATOES.GamingBench.onprocessended = function () {
    // Generating the result list
    var score = 0;
    for (var index = 0; index < POTATOES.GamingBench.benches.length; index++) {
        var bench = POTATOES.GamingBench.benches[index];
        score += bench.score;

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

Понимание оценки

После запуска полной игровой скамьи, вы можете анализировать счет , используя график , полученный с d3.js . Оценка рассчитывается путем добавления одного очка к каждому визуализированному кадру. Очевидно, что вы можете изменить это поведение для собственной скамьи, чтобы учесть другую метрику. Это то, что Дэвид Руссет сделал для своих скамей .

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

var avgFps = 0;
for (var i = 0; i < bench.stats.length; i++) {
    avgFps += bench.stats[i].y;
}

avgFps /= bench.stats.length;

Я очень надеюсь, что вы найдете эту платформу полезной, чтобы помочь вам создавать замечательные и эффективные игры!

Идти дальше