Dance Hero: Swipe to Dance

Dance Hero: Swipe to Dance

Гиперказуальная игра от Diesel Puppet для iOS и Android. Задача игрока рисовать гестуры которые присутствуют в левой колонке каждой волны. Делать это нужно быстро, т.к. список с пиктограммами гестур движется вниз. В игре есть магазин, в котором игрок может разблокировать различные танцы. На данный момент есть 10 вариантов танца, и все они чудесны по-своему. Мне очень нравятся анимации персонажа.

Я участвовал в разработке игры в качестве программиста. Идея игры, графика, Spine-анимации и баланс – все делалось командой Diesel Puppet. Игра делалась на моем кроссплатформенном движке AGE.

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

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

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

Версия для iOS

Версия для Android

Android NDK native APIs

Постоянно забываю какой API level какому Android относится.
Сводня табличка по стабильным версиям API NDK.
И что бы два раза не вставать – заметки по SDK platform, а заодно и ассеты Android.
Долгое время я использовал API 16, что соответствует Android 4.1 и Android 4.1.1. Сегодня решил в одной игре перейти на API 21. Перед публикацией посмотрю статистику количества отвалившихся пользователей этой игры из-за переходя на новую версию API.

Книга «Шаблоны игрового программирования»

Шаблоны игрового программирования Перевод книги Game Programming Patterns by Robert Nystrom. Книга была создана для удобного чтения на русском языке в формате электронной книги.

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

Краткий список шаблонов, которые рассмотрены в книге: Command, Flyweight, Observer, Prototype, Singleton, State, Component, Event Queue и прочее.

На сайте автора http://gameprogrammingpatterns.com/ можно заказать печатную или электронную версию книги на английском языке.

Ultimate Question 42

Вчера закончился прием заявок на международный конкурс Ludum Dare. Разработчикам нужно за 72 часа (jam) или за 48 часов (compo) создать игру с нуля на заданную тему.

Наша команда из 4 человек создала игру Ultimate Question 42 за 72 часа.
Я был в роли программиста и технического специалиста (ну а как иначе :)), Шевадзуцкий Александр за геймдизайнера, а Ходас Артем и Доджо у нас художники.

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

Игру решили писать на моем движке сразу для Web, т.к. позволяет оценить игру сразу, без установки на десктоп или мобильное устройство. На сайте проекта доступна нативная версия для Linux, у меня на смартфоне работает версия для iOS. В будущем планируется выпуск публичных версих для iOS, Android, Apple TV (tvOS), Android TV, а так же для платформы Facebook Instant Games.

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

Ссылка на официальную страницу игры на Ludum Dare – Ultimate Question 42

Загрузка и кодирование в base64 на JavaScript

Для шаринга и инвайта в Facebook / Instant Games нужно в качестве параметра image передать картинку, закодированную в base64. Можно сделать это в offline, но это увеличит размер дистрибутива и увеличит время загрузки игры. Значит это не наш метод.

Я делаю это в рантайме с помощью XMLHttpRequest и FileReader:

function toDataURL(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        var reader = new FileReader();
        reader.onloadend = function() {
            callback(reader.result);
        }
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', url);
    xhr.responseType = 'blob';
    xhr.send();
}

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

Emscripten initialization

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

По-умолчанию этот параметр установлен в TRUE. И сегодня я получил слайд-шоу, установив его в значение по-умолчанию.

EmscriptenWebGLContextAttributes attr;
emscripten_webgl_init_context_attributes(&attr);

attr.alpha = EM_FALSE;
attr.depth = EM_FALSE;
attr.stencil = EM_FALSE;
attr.antialias = EM_FALSE; // <-- this should be set to FALSE!
attr.preserveDrawingBuffer = EM_FALSE;
attr.preferLowPowerToHighPerformance = EM_FALSE;
attr.failIfMajorPerformanceCaveat = EM_FALSE;
attr.enableExtensionsByDefault = EM_TRUE;
attr.premultipliedAlpha = EM_TRUE;
attr.majorVersion = 1;
attr.minorVersion = 0;

EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(nullptr, &attr);
emscripten_webgl_make_context_current(ctx);

Emscripten HiDPI

На HiDPI мониторах downscaled текстуры выглядят мягко говоря не очень красиво. Искал способ решить эту проблему. В итоге мои исследования привели к такому не самому легковесному решению.

  1. Получить device pixel ratio для настройки размров canvas.
  2. Получить размер canvas и разрешение css.
  3. На их основе вычислить отношение.
  4. Использовать это отношение для расчета координат мыши и тача.

После этого все будет выглядеть красиво на HiDPI мониторах. Но есть одна “особенность” – увеличенный (в зависимости от отношения размеров css и canvas) в несколько раз фреймбуфер. При отношении равном 2, получаем размер фреймбуфера в четыре раза больший, со всеми вытекающими.

Немного кода

CSS

canvas {
    width: 100vw;
    height: 100vh;
    display: block;
}

JavaScript

window.addEventListener('resize', resizeCanvas, false);

function resizeCanvas() {
    var realToCSSPixels = window.devicePixelRatio;

    var displayWidth  = Math.floor(canvas.clientWidth  * realToCSSPixels);
    var displayHeight = Math.floor(canvas.clientHeight * realToCSSPixels);

    if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
        canvas.width  = displayWidth;
        canvas.height = displayHeight;
    }
}

resizeCanvas();

C++

int width, height;
emscripten_get_canvas_element_size(nullptr, &width, &height);

double cssWidth, cssHeight;
emscripten_get_element_css_size(nullptr, &cssWidth, &cssHeight);

const auto ratiox = (float)(width / cssWidth);
const auto ration = (float)(height / cssHeight);

Можно сделать настройку, доступную пользователю – пусть он сам решает, что ему лучше – производительность или качество рендеринга.

История поиска одного бага

Debugging Довольно много времени потратил на поиск “плавающего” бага. За это время успел отрефакторить кучу кода, до которого руки не доходили ранее. Но баг не ловился. И в его поимке не смогли помочь ни Xcode Instruments, ни Xcode Analyzer (фронтэнд к статическому анализатору llvm).

Баг проявлялся редко и далеко не на всех девайсах. Я никак не мог понять условия его проявления. Пробовал записывать направление и температуру ветра, положение звезд на небе и длительность соседского ора. Но это никак не помогало в поиске проблемы.

Падения игры под отладчиком и backtrace давали BAD EXC, что как бы намекало на испорченную где-то ранее память. Но где именно? Статический анализатор, по идее, должен на раз находить access out of bounds. Но анализатор молчал, типа вообще все пучком.

От безысходности и обиды на самого себя, решил синхронизировать линуксовую версию с main branch, т.к. в процессе поиска бага был сделан серьезный рефакторинг кода. Ну и очевидно, что погонять движок под valgrind никогда лишним не будет.
И valgrind сразу же нашел memory corruption (тот самый “плавающий” баг, на поиски которого я убил много времени. И мелкий ничего не значащий memory leak.

Linux и valgrind рулят!

Emscripten: события клавиатуры в iframe

Столкнулся с проблемой на itch.io, когда приложение запущенной внутри iframe не получает события клавиатуры. Придумал вот такой workaround:

postRun: (function() {
                window.addEventListener('mousedown', function(evt) {
                    window.focus();
                    // evt.preventDefault();
                    evt.stopPropagation();
                    evt.target.style.cursor = 'default';
                }, false);
            })(),

К событию postRun добавляем установку листенера на событие mousedown. Теперь при клике мышью на канавасе с игрой события от клавиатуры будут передаваться в наше приложение.

1 2