Defold. Некоторые сложности и их решения.

2016-06-27_23-19-45Читая предыдущий пост о Defold может сложится впечатление, что движок идеален и просто «серебряная пуля». Да, так и есть ^___^.  В этот раз я расскажу о сложностях, с которыми  столкнулся и как они решаются.

render_script

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

Game Scale

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

-- при старте уровня отправляю сообщение в render_script
msg.post("@render:", "level_start", {level_width = lvl_width, level_height = lvl_height})
-- получаю сообщение и задаю размеры области для отрисовки
function on_message(self, message_id, message)
    ...
    elseif message_id == hash("level_start") then
       	original_width = message.level_width
       	original_height = message.level_height
    end
end

Render predicates

— определяет порядок отрисовки по тэгу в материале. Подробности со всеми премудростями в документации, а вот где это пригодилось  в проекте.

GUI в движке Defold независимая сущность со своими типами объектов, в которую нельзя поместить все, что вздумается. Мне же вздумалось использовать партиклы в GUI. Вроде, все просто. Вешаем компонент фабрики на тот же объект, что и компонент GUI и при создании нового партикла задаем нужные координаты. У меня это место появления звезды. Но, вот беда, наши частицы отрисовываются под звездой и z на это никак не влияет. Можно, конечно, начать убиваться и материться мол «Как же так, партиклы обычная вещь для ГУИ, что за фигня … «, но вместо этого идем на форум и в документацию и находим решение.

2016-06-27_21-21-58Для начала создадим копию материала, что используется для партиклов. По умолчанию он находится в builtins/materials/ particlefx.material . Зададим ему tag, у меня это gui_particle. Далее идем в наш render_script и создадим там новый предикат. А после отрисуем его там, где положено. Выглядит это примерно вот так:

--в вашем render_script
function init(self)
  ...
  --создаем наш предикат
  self.gui_particle_pred = render.predicate({"gui_particle"})
  ...
end
function update(self)
   ...
    render.draw(self.gui_pred)
    render.draw(self.text_pred)
    --отрисовываем наш предикат там, где нужно
    render.draw(self.gui_particle_pred)
   ...
end

Теперь у нас есть материал, который мы можем задавать для всех партиклов, которые нужно отрисовать над GUI.

Materials 

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

Над текущим проектом, мне повезло работать с замечательным человеком, который очень внимательно относится к деталям. Именно он и оторвал меня от «угара» написания игровой логики и, уже на ранних прототипах, предложил решить следующую проблему:
ful
Как видно, левая картинка намного четче, чем правая (несмотря на то, что изображение уже пережато несколько раз). Так вот, левый скриншот сделан при скейле 1, а правый при скейле 0.5. В принципе, при любом скейле отличным от 1 картинка заметно «плывет». Из опыта работы на флэш родилась мысль об использовании нескольких наборов текстур, и все знакомые flash разработчики пропагандировали именно этот подход. Но опыт работы с unity подсказывал, что копать нужно в сторону сжатия текстур. После тщательного изучения форума и документации  таки решил спросить сообщество как с этим быть, ответили мне моменталтно и решение оказалось крайне простым.

2016-06-27_22-12-12В настройках материала мы можем создать sampler и задать его тип фильтрации. В моем случае, необходимо было задать FILTER_MODE_LINEAR. Но здесь есть один нюанс, который я упустил. Сэмплер — это тип переменной шейдера. Создавая его в материале, мы просто добавляем параметры для переменной, которую укажем в поле name. А переменная эта задается в шейдере. Например, этот скриншот для материала sprite использует шейдер sprite.fp :

varying mediump vec4 position;
varying mediump vec2 var_texcoord0;

uniform lowp sampler2D DIFFUSE_TEXTURE;
uniform lowp vec4 tint;

void main()
{
    // Pre-multiply alpha since all runtime textures already are
    lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
    gl_FragColor = texture2D(DIFFUSE_TEXTURE, var_texcoord0.xy) * tint_pm;
}

Именно DIFFUSE_TEXTURE мы и должны указать в качестве значения поля name в samplers. А вот шейдер gui.fp используемый в материале gui:

varying mediump vec4 position;
varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;

uniform lowp sampler2D texture;

void main()
{
    lowp vec4 tex = texture2D(texture, var_texcoord0.xy);
    gl_FragColor = tex * var_color;
}

Здесь переменная сэмплер имеет имя texture, поэтому, не забываем смотреть на код шейдера и в параметрах материала писать правильное имя переменной сэмплера.

Теперь у нас четкая картинка с любым скейлом 🙂

result

Другое.

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

Конечно, есть еще всякие мелочи:

  • встроенный твинер не умеет работать с пользовательскими векторами (но умеет с некоторыми свойствами объектов, что являются векторами) — уже создана таска. Штука совершенно не критичная и легко обходится.
  • нет события на окончание партикла. Тоже очень минорная задача, которая обходится маленьким скриптом. Но и этот вопрос поднимался на форуме и таск уже создан.
  • невозможно просто взять и ускорить спрайтовую анимацию, об этом уже писал у себя в блоге Андрей Лапшин. Таска тоже создана и на форуме есть код, как это обойти. Да, велосипед, но не смертельно.

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

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