Портирование с движка Qlie на RenPy. Часть 2
Часть 1 можно прочитать здесь: https://habr.com/ru/post/426431/ или в нашем блоге: https://kindkanjistudio.wordpress.com/2018....e_part1

В предыдущей части я подробно разобрал особенности движка Qlie на примере игры Bishoujo Mangekyou -Norowareshi Densetsu no Shoujo-(美少女万華鏡 -呪われし伝説の少女-) , которую мы переводили.
Теперь к главному. Почему нам пришлось портировать эту игру на RenPy, чтобы продолжить перевод? Причин было несколько.

Изначально движок категорически отказывался принимать кириллические символы в каком-либо виде. После некоторых манипуляций с exe файлом, удалось заставить его воспринимать файл сценария с кириллическими символами. В этом во многом помог китайский перевод, сделанный командой Mingyue Chinese Localization Group.
Но так и не удалось решить проблему с межбуквенным интервалом. Поскольку изначально мы использовали немоноширинный шрифт, это приводило к тому, что расстояния между буквами в игре становились непропорционально большими, или наоборот, буквы налезали друг на друга.

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

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

Когда нашему программисту(Chtobi) наконец надоело возиться с exe-шником игры, была предложена идея портировать игру целиком на другой движок и не мучиться с Qlie.
Хорошо поразмыслив, мы стали искать возможные варианты.

Порт Haru на OnScripter

Мы решили начать с поиска предыдущих попыток портирования Bishoujo Mangekyou -Norowareshi Densetsu no Shoujo- , потому что мы сами не умеем ничего делать с нуля, только воровать чужое и копировать потому что так проще. К удивлению, оказалось, что существует японский порт этой игры на бесплатный движок OnScripter, сделанный для запуска на PSP еще достаточно давно в 2012 году.

Автор порта Haru подробно расписал весь процесс. Предлагается распаковать все ресурсные файлы игры из .pack архивов, потом запустить несколько .bat скриптов для преобразования графики и звуков.
В процессе, игровые фоны ужимаются до разрешения 480x270, звуки перекодируются, а из отдельных изображений создаются анимации. Затем нужно было запустить несколько скриптов на Perl для преобразования файлов сценария .s в формат OnScripter'а и запаковать все получившееся в архив нового движка.

Первая мысль - последовать примеру и сделать порт на OnScripter - оказалась не самой удачной. Все-таки данный движок предназначался именно для японских новелл.
Существуют также версии ONScripter-ANSI с поддержкой кириллической кодировки cp1251 и PONScripter с поддержкой UTF-8, но они работают с далеко не всеми командами оригинала. Кроме того, портированию подверглось не все и игровые видео работают не совсем правильно, о чем упоминает и сам Haru.
В итоге, наиболее полезными оказались именно скрипты на Перле для портирования .s файлов.

Подготовка файлов к портированию на RenPy

Поскольку предыдущий вариант нам не подходил, пришлось выбрать в качестве конечной цели портирования движок RenPy.

На первый взгляд, большей части ресурсных файлов игры никакая обработка была не нужна. RenPy прекрасно распознает jpg, png, ogg, ogv и wav файлы из архивов оригинальной игры. Однако сложности возникли именно с включением их в игру, поскольку для использования сначала требуется импортировать изображения в скрипт через функцию define.

Несмотря на то, что RenPy умеет автоматически импортировать изображения опцией automatic_images, движок категорически отказывался воспринимать японские имена из-за багов в старой версии (https://github.com/RenPy/RenPy/issues/1477, https://github.com/RenPy/RenPy/issues/369). А большая часть графики в игре - файлы с японскими названиями.
Поэтому пришлось задуматься о создании .rpy файлов с импортом всех ресурсов не пользуясь средствами RenPy, а затем перевести названия файлов, чтобы с ними можно было работать.
В первую очередь был написан скрипт на Питоне для импорта файлов. Суть достаточно проста - скрипт сканирует указанные в параметрах папки с игровыми ресурсами и создает rpy файл. define-имя при необходимости изменяется, чтобы соответствовать требованиям движка и к нему добавляется нужный префикс.
Так как некоторые файлы хранятся в разных папках, но имеют одинаковые имена, требуется, чтобы они также имели разные define-имена.

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

Небольшая справка по транслитерации и слоговым азбукам.
В японском языке для записи слов используются кандзи(https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D0%BD%D0%B4%D0%B7%D0%B8),хирагана(https://ru.wikipedia.org/wiki....D%D0%B0) и катакана(https://ru.wikipedia.org/wiki....D%D0%B0) (вместе катакану и харагану именуются просто кана).
Транслитерация японских символов латиницей называется ромадзи.

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

Преобразователь в ромадзи KAKASI использует собственный довольно ограниченный файл словаря. Также, из-за особенностей алгоритма, KAKASI иногда неверно выполняет токенизацию текста, то есть, неверно разбивает предложение на слова. А поскольку названия некоторых файлов состояли из двух или более слов, было очень желательно романизировать все правильно.

В качестве альтернативы был найден питоновский модуль jProcessing. Модуль требовал наличия японского синтаксического парсера CaboCha, а тот в свою очередь требовал морфологический анализатор MeCab. В добавок, jProcessing оказался без поддержки Python 3.
Пришлось переписать его для работы с третьей веткой Питона. К счастью, это было довольно легко.
Таким образом, готовый скрипт должен был последовательно выполнить переименование японских имен файлов в ресурсных папках, а затем переименование имен файлов в .rpy файлах, с оставлением оригинального имени файла в комментарии.

Чтобы как-то определиться с масштабом задачи и отследить все ошибки работы jProcessing, перед непосредственно переименованием создавалась JSON-база всех файлов попавших в обработку(возиться с созданием настоящей БД не хотелось). Записывался уникальный хэш файла, как ключ словаря, затем оригинальное имя файла, имя файла после замены символов на ромадзи и полный путь к оригинальному файлу.
Код

"ff49d4f3c768d7529e31886fe2523da3": ["キリエ_大_制服01_青目伏し目01.png", "kirie_dai_seihuku01_ao_me_husime01.png", "С:\\RenPy\\game\\images\\charaimage\\kirie\\キリエ_大_制服01_青目伏し目01.png"]


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

Оказалось, в стандартной библиотеке Питона(версии 3.3 на тот момент) просто нет никаких способов быстро определить, принадлежит ли символ к той или иной группе Юникода. Например, в Perl достаточно добавить регулярное выражение вроде /^(?:\p{Han}|\p{Hiragana}|\p{Katakana}|\p{InCJKSymbolsAndPunctuation}) и разом учесть все возможные японские символы, включая пунктуацию.
Для аналогичного приема можно использовать стороннюю питоновскую библиотеку regexp, но ее пришлось бы качать отдельно только ради этой малости.

Проще оказалось взять код каждого символа в имени файла и сравнить его с японскими диапазонами Юникода: '\u3000'-'\u303f', '\u30a0'-'\u30ff', '\uff00'-'\uffef', '\u4e00'-'\u9fcb', '\u3400'-'\u4dbf', '\uf900'-'\ufa6a', '\u31F0'-'\u31FF', '\u3220'-'\u3243', '\u3280'-'\u337F'
Например, иероглиф с символьным кодом 0x5927 попадает в Юникодный диапазон 0x4e00 - 0x9fef (CJK Unified Ideographs).

Далее, японский текст разбивается на слова и переводится в ромадзи, а не японские символы добавляются отдельно после преобразования.

Уже после первого запуска выяснились неприятные особенности парсера CaboCha.
Вот пример преобразования названий файлов(слева оригинальное название, справа после двоеточия - конечный вариант):
"sounds/▼ブチュ!01.ogg": "__01.ogg"
"sounds/▼胸揉み音フニフニ.ogg": "__mune_momi_on.ogg"

А вот как правильно должны записываться файлы в ромадзи:
_butyu!01.ogg
_mune_momi_on_funi_funi.ogg

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

Кроме того, под нож пошли символы вроде японского восклицательного знака!, японские круглые скобки( )и все подобное. Далее, CaboCha по какой-то причине не может распознать строку полностью, если сочетание хираганы/катаканы и кандзей показалось ей неразборчивым.
Так в слове ▼ブチュ!не удалось разобрать вообще ничего и в ответ попал только восклицательный знак(замененный на подчеркивание) и цифры 01.

В имени ▼胸揉み音フニフニ получилось распознать кандзи 胸揉み音(mune momi on), но не удалось распознать フニフニ
После пары исправлений в jProcessing эта неточность была устранена.

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

Структура игрового сценария на Qlie

Пожалуй, самым сложным и времязатратным стал этап переделывания игрового сценария(переведенного текста новеллы со служебными командами) c Qlie на RenPy.
В качестве отправной точки вполне подходил скрипт Haru на Perl. Разумеется, требовалось сделать нечто аналогичное на Питоне для переработки оригинального сценария на RenPy. Более того, скрипт Haru некоторые возможности движка вообще исключает из обработки, а хотелось их сохранить.

Часть команд сценарного языка движка Qlie я уже разобрал в первой части, теперь настало время погрузиться глубже.
Для примера можно взять начальную сцену игры. После каждой команды движка в комментариях дается объяснение ее действия.
Код

@@@Library\Avg\header.s
@@Main  
@Комментарий начинается со знака одиночной собаки.
@Подгрузка определений из файла header.s , проставление метки Main  
^savedate,"現在"
^saveroute,"美少女万華鏡-1-"
^savescene,"呪われし伝説の少女 オープニング"
@Установка параметров для сохраняемой игры(по-видимому, не используется)

^facewindow,1  
@Включение показа маленького портрета в углу текстового окна  

^sload,Env1,◆セミ01アブラゼミ
@Проигрывание звука ◆セミ01アブラゼミ на канале Env1
^eeffect,ex1,WhiteFlash
@Эффект белой вспышки

^ffade,Overlap,,1000
@Эффект растворения одного кадра в другом за 1 секунду
^iload,BG1,1_19木漏れ日.png
@Загрузка картинки 1_19木漏れ日.png и выведение ее на экран с присвоением id BG1
^eeffect,Stop
@Остановка эффекта белой вспышки

^ffade,Overlap,,300
@Затухание в течении 0,3 секунд

^cadd,仲居
@Добавление персонажа 仲居 для последующего вывода изображения
^cbase,,中,旅館制服
@Добавление размера 中 и варианта одежды 旅館制服 для последующего вывода изображения
^cface,,微笑02
@Добавление варианта лица 微笑02 для последующего вывода изображения
^cd,仲居
@Выключение спрайта персонажа 仲居

^cface,,微笑02
@Добавление варианта лица 微笑02 для последующего вывода изображения

^we
@Включение текстового окна

^mload,BGM1,nbgm13
@Проигрывание трека nbgm13 на канале BGM1

^cface,,笑顔02
@Добавление варианта лица 笑顔02 для последующего вывода изображения
【仲居】
%1_naka0006%
「いらっしゃいませ。遠いところをようこそおいでくださいました。お疲れになりましたでしょう?」
@Вывод в текстовое окно фразы персонажа 仲居 с проигрыванием звукового файла 1_naka0006
^cadd,深見
@Добавление персонажа 深見

【深見】
「いや……空気もきれいでリフレッシュできました」

^facewindow,0  
@Выключение показа маленького портрета в углу текстового окна  

^ffade,Overlap,,1000
^iload,BG1,0_03旅館入り口昼.png

^cbase,仲居,中,旅館制服
@Добавление имени персонажа 仲居, размера 中 и варианта одежды 旅館制服 для последующего вывода изображения
^cface,,微笑02
@Добавление варианта лица 笑顔02 для последующего вывода изображения
^ce,仲居
@Включение спрайта персонажа 仲居
^cface,,,頷き
@Добавление эффекта 頷き для последующего вывода изображения
【仲居】
%1_naka0007%
「そうですか~。それは何よりです」

^svol,ENV1,0,500
@Уменьшение громкости до 0 на канале ENV1 в течении 0,5 секунд


Вывод изображений и спрайтов персонажей

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

Из предыдущей части, где мы занимались переименованием файлов, становится понятно, что все спрайтовые изображения героев имеют названия типа 仲居_中_旅館制服_微笑02.png
Соответственно, каждое название состоит из имени героя, значения размера картинки, описания одежды и выражения лица.

Таким образом, если требуется вывести на экран спрайт персонажа или его портрет в текстовом окне, применяются команды ^cadd, ^cbase и ^cface
Перед непосредственно выводом изображения, нужно как бы собрать имя файла из кусочков:
Код

^cadd,仲居
^cbase,,中,旅館制服
^cface,,微笑02

Есть также возможность вывести спрайт с определенной анимацией, добавив команду ^cface с третьим параметром 頷き:
Код

^cface,,,頷き

Если все соединить, получается: персонаж - 仲居(горничная), размер картинки - 中(средний), описание одежды - 旅館制服(традиционная гостиничная одежда), выражение лица - 微笑02(улыбка), анимация - 頷き(кивок).
Казалось бы, почему не сделать вывод каждой картинки простой и понятной ссылкой на конкретный файл? Но нет, это для слабаков.

В RenPy также существует аналогичная система работы с изображениями:
Код

image mary night happy = "mary_night_happy.png"
image mary night sad = "mary_night_sad.png"
show mary night sad
show mary night happy at right

Но нужная картинка все же выводится одной командой show со всеми атрибутами сразу.

На этом сложности не заканчиваются. Вывод спрайта не происходит сразу после выполнения команд ^cadd , ^cbase и ^cface , а только после выведения фразы соответствующего героя на экран, в данном случае 【仲居】

Путаницы добавляют еще несколько нюансов. Спрайты персонажей и их портреты выводятся одними и теми же командами, в зависимости от значения флага ^facewindow, .
Поэтому после команды ^facewindow,1 выводится портрет горничной в текстовом окне, а после ^facewindow,0 обычный спрайт горничной.

При этом еще учитывается состояние флага ^ce, (CharaEnable) для конкретного персонажа. Соответственно, после ^ce,仲居 на экран выводится спрайт горничной, а после ^cd,仲居 соответствующий спрайт убирается и затем, при появлении фразы персонажа с 【仲居】выводится портрет в текстовом окне.

Также во время добавления нового персонажа без каких-либо спрайтов при помощи команды ^cadd,深見, предыдущий спрайт автоматически убирается после начала фразы нового персонажа【深見】.
Стоит также упомянуть о системе имен героев. В файле avgsystem_custom.s описаны все имена, а так же используемый цвет шрифта.
Код

@@!Avg_OnMessageNameDraw_case_end
  \case,ResultStr[1]
  \ans,"蓮華"
  \o,"[c,$FF2d232d]蓮 華"
  \ans,"???"
  \o,"[c,$FF2d232d]"+ResultStr[1]
  \ans,"深見"
  \o,"[c,$FF643638]深 見"
  \ans,"仲居"
  \o,"[c,$FFb14216]仲 居"
  \ans,"キリエ"
  \o,"[c,$FF79153c]"+ResultStr[1]
  \ans,"滋比古"
  \o,"[c,$FF697680]"+ResultStr[1]
  \ans,"芦田"
  \o,"[c,$FF000000]芦 田"
  \ans,"岡崎"
  \o,"[c,$FF000000]岡 崎"
  \else
  \o,"[c,$FF000000]"+ResultStr[1]
  \end

При этом встречающееся в сценарии имя【蓮華@???】 выводиться в игре просто как ???

Проигрывание видео

Движок игры проигрывает файлы ogv при помощи цепочек команд:
Код

\gvar,theora,th
  \scp,th
  \l,"万華鏡.ogv"
  \pp,0,0,10
  \e  
  \p,1  

Как можно убедиться, \scp,th создает внутреннюю переменную движка th и присваивает ей значение "万華鏡.ogv" , \pp видимо устанавливает позицию на экране, а \e выводит видео на экран.

Анимации

Движок Qlie предоставляет несколько способов для создания анимаций, что сильно усложняет портирование сценария, потому что в каждом случае приходиться разбираться отдельно.
Начнем с анимаций, записанных в .b файлах. Работать с ними можно так же, как и с обычными картинками.
^iload,BG1,さまよい.b

Данная команда выведет на экран анимированный фон, хранящийся в さまよい.b
Аналогично работе с видео файлами, можно выводить на экран изображения и анимированные .b файлы в наложение на уже существующие.
Код

\gvar,Img,op_image
  \scp,op_image
  \l,"Image\万華鏡_ロゴ.b"
  \li,0,"Image\nop01.png"
  \m,0
  \p,0,0,100
  \e  

  \scp,snd[12]
  \i
  \l,"Voice\1_renk0052.ogg"
  \v,0
  \v,256,500
  \t,1
  \p,0


На экран будут выведены анимация 万華鏡_ロゴ.b , картинка nop01.png(переменная op_image) и проигран звук 1_renk0052.ogg . Удаление рисунка происходит командой \del,op_image
Более мелкие анимации с движением спрайтов на экране движок делает при помощи задания новой позиции изображения (в данном случае, это картинка キリエ) на экране командой \p2
Код

^cbase,キリエ,大,制服01
^cface,キリエ,赤目通常01
^ce,キリエ
  \sub,@@!Avg_CharaGetNo,self,キリエ
  \scp,Avg_Chara[ResultInt[0]]
  \mm,3,100
  \p2,640,1080,500,200,200,0
  \e,1


Анимированный переход с одного экрана на другой выполняется через команду ^ffade :
^ffade,Overlap,,300
Первый параметр Overlap является своего рода шаблоном. Как именно должен выполняться переход, описано в файле templatefade_overlap.txt Последний параметр - время, за которое должен выполниться переход.

В другом случае, переход может быть дополнен .png маской соответствующего эффекта, во втором параметре. Здесь wp短冊左.png - шаблон для «эффекта жалюзей», движущихся влево.
^ffade,Blind,wp短冊左.png,2000,7
Еще один вариант - переход с использованием анимационного файла .b
^ffade,Ex1,Zoom02,2000
Здесь используется эффект из файла zoom02.b

Другой способ проиграть короткую анимацию - это команда ^eeffect
^eeffect,WhiteFlash
Воспроизведение эффекта белой вспышки по шаблону templateeffect_whiteflash.txt с использованием файла whiteflash.b
^eeffect,Ex1,ぶるっ
Тряска экрана, анимация ぶるっ.b
Интересная особенность в том, что анимации применяются к любому фону, который в данный момент на экране.

Работа с игровыми переменными

Поскольку Bishoujo Mangekyou довольно линейна, там нет частых проверок внутриигровых переменных.
Переменные создаются уже упомянутой выше командой \gvar :
\gvar,int,##route_f
int - соответственно, тип переменной, ##route_f - название. Судя по всему, ## обозначает, что переменная действует не только в том файле сценария, где была объявлена, но и в других.

Присвоение значения переменной:
\cal,##route_f=1
Пример конструкции, для проверки нескольких условий:
Код

  \case,##route_f
  \ans,1 \del,##route_f \jmp,@@Top,"kk_main_13b.s"
  \else
  \del,##route_f \jmp,@@Top,"kk_main_13a.s"
  \end


В данном случае, если значение переменной ##route_f равно 1, выполняется удаление переменной (\del,##route_f) и переход в файл kk_main_13b.s . Во всех остальных случаях - удаление переменной и переход на файл kk_main_13a.s .

Однако в Qlie при этом еще существуют переменные, сохраняющие свое значение даже после перезапуска игры. Что-то вроде persistent variables из RenPy.
\svar,int,#1_route_a_f
\svar создает переменную, которой потом можно также присвоить значение:
\cal,#1_route_a_f=1

Условный оператор \if позволяет выполнить действие, в зависимости от значения переменной:
Код

\if,#1_route_a_f==1
  \if,#1_route_b_f==1
  \clk
  \jmp,@@Top,"kk_omake_01.s"
  \end
  \end

Если переменные 1_route_a_f и 1_route_b_f равны 1, выполняется переход в файл kk_omake_01.s .

Это далеко не все команды, но пока остановимся на этом.

Портирование главного меню

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

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

Открытие CG-артов после их появления в игре RenPy выполняет автоматически через Image Gallery. В оригинале на Qlie еще была возможность увеличить просматриваемый CG арт, но можно было обойтись и без нее.
Переигрывание отдельных сцен из игры выполняется в Qlie с помощью команд @@Replay05_02 \scp,sys \?sr \if,ResultStr[0]!=""\then, в RenPy для этих же целей нужно создавать отдельный именованный label и далее запускать проигрывание через команду Replay.

Прослушивание мелодий в окне Music Box было выполнено при помощи функции Music Room. Сложным оказалось добиться адекватного поведения при переключении мелодий и, что не маловажно, движения полоски прогресса проигрывания.

Портирование звука и музыки

При этом в RenPy не нашлось встроенной возможности повторить звуковую подсистему Qlie. В оригинале окно настроек имеет несколько ползунков для изменения громкости звуковых эффектов, музыки, голоса, видео и общей громкости. Соответственно, при изменении общей громкости(Master volume) менялась громкость всех каналов.

Но в RenPy нет понятия Master volume. Все создаваемые каналы привязаны к микшерам(по умолчанию: music, sfx, voice), с помощью которых можно менять громкость командой _preferences.set_volume(mixer, volume) Пришлось создавать дополнительные микшеры для остальных групп каналов(о них немного позже).

Таким образом, Master volume был сымитирован созданием дополнительной переменной qlie_master_vol на которую домножались все другие переменные громкости миксеров. Например:
Код

_preferences.set_volume("voice", persistent.qlie_voice_vol * qlie_master_vol)

Другой особенностью оказалось большое количество каналов, которые использует игра для вывода звука:
Код

^sload,SE1,▲シーツ捲り01,0
^sload,SE2,◆車急発進,0
^mload,BGM1,nbgm10
^sload,ENV1,■クチュ音05


^mload вполне ожидаемо проигрывает музыку nbgm10 на канале BGM1 и так происходит со всей фоновой музыкой в игре. А вот ^sload проигрывает звуковые эффекты и звуки окружения на разных каналах: SE1, SE2, ENV1...

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

RenPy.music.register_channel("env2", mixer="sfx", loop=True, stop_on_mute=True, tight=False, file_prefix="", file_suffix="", buffer_queue=True, movie=False)

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

Создание механизма портирования сценария на Python

Как я упоминал выше, было решено создать собственную питоновскую программу, которая бы давала на выходе готовый rpy файл, с учетом всех преобразований оригинальных команд. С самого начала мы сделали несколько грубых ошибок. Во-первых, очень захотелось создать программу без посторонних пакетов, чтобы она работала как скрипт от Haru, но на чистом Питоне.

Поэтому вместо того, чтобы взять сторонний лексер вроде PLY или что-то подобное, мы решили сделать все вручную на регулярных выражениях и словарях. Это оказалось не самой лучшей идеей в конечном итоге.
Когда разработка стала затягиваться, пришлось отказаться от «вылизывания до блеска» и разработки программы так, чтобы она выдавала на выходе идеальный, полностью рабочий rpy-файл. На это уже просто не хватало времени и сил. К сожалению, даже сейчас программа работает кое-как и требует капитального допила.

Поскольку каждый файл сценария начинается со строки @@Main , логично было заменить ее на label с названием файла. Например, label kk_op_01 .
Подгрузка фоновых картинок(в Qlie - ^iload) благополучно решалась через scene image()

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

Поскольку в Qlie эффекты и переходы задаются командами ^eeffect или ^ffade , в RenPy приходилось добавлять их к команде вывода изображения на экран после with.
Было:
Код

^ffade,Overlap,,1000
^iload,BG1,0_03旅館入り口昼.png

Стало:
Код

scene image(q_0_03ryokan_irikuchi_hiru) with Dissolve(1.0, alpha=False, time_warp=None)
# Загрузка на экран изображения q_0_03ryokan_irikuchi_hiru с эффектом перехода Dissolve в течении одной секунды


Много проблем возникало из-за того, что не всегда было понятно, к какому переходу какая команда относится.
Почти все эффекты приходилось создавать эмпирически, на глазок, только частично опираясь на информацию их соответствующих файлов типа \library\avg\templatefade_overlap.txt

Реализация большей части переходов(transitions) получилось достаточно простой. Такие эффекты делаются при помощи png шаблонов с различной прозрачностью, которые находятся в папке \fade .
Соответствующий шаблон подставляется в RenPy команду ImageDissolve. Таким образом,
Код

^ffade,Blind,wp十字右.png,2000,3
^iload,BG1,0_02旅館廊下.png

Заменяется на:
Код

scene image(q_0_02ryokan_rouka) with ImageDissolve(wpjyuuji_migi_png,2.0, 50, reverse=False)

Эффект белой вспышки
Код

^eeffect,WhiteFlash  

можно запросто выразить в RenPy вот так:
Код

define white_flash = Fade(0.1, 0.0, 0.5, color="#fff")

Но как понять, что значит подобная строчка?
Код

^eeffect,Ex1,ぶるっ


Суть этого и аналогичных ему эффектов в том, что они используют команды анимации и графику из файлов .b (в данном случае: ぶるっ.b), о которых мы уже упоминали. К сожалению, мы не нашли никакого способа распознать, что именно за команды там записаны.
А поэтому в данном случае приходилось сначала смотреть на эффект в оригинальной игре, а потом воспроизводить его в виде команд transform :
Код

transform shake: #ぶるっ
  ease .06 xoffset 20
  ease .06 xoffset -20
  ease .05 xoffset 20
  ease .05 xoffset -20
  ease .05 xoffset 0

При этом эффект нужно применять с указанием текущего фона:
scene image(q_1_10mori_yoru) at shake
Точно также обрабатываются и эффекты перехода из .b файлов:
Код

^ffade,Ex1,Zoom02,2000

Приблизительно соответствующий эффект:
Код

transform q_zoom_in_02(old_widget=None, new_widget=None, delay_time=1.0):
  delay delay_time
  contains:
  old_widget  
  contains:
  Flatten(new_widget)
  truecenter
  rotate 90.0 zoom 2.5 alpha 0.0 #  
  linear delay_time rotate 0.0 zoom 1.0 alpha 1.0


Аналогично эффект появления можно добавить и к спрайту героини:
Код

show image(img_k_kirie_naka_seihuku02_ao_me_tsuujyou01) as kirie with Dissolve(0.3, alpha=False, time_warp=None)  

Другой вариант - вывод спрайта с дополнительной анимацией 頷き.b , указанной в ^cface :
Код

^cadd,仲居  
^cbase,,中,旅館制服  
^cface,,微笑02  
^cface,,,頷き  

В таком случае, анимация кивка(nod) добавляется c использованием at .
Код

show image(img_h_nakai_naka_ryokan_seihuku_bisyou02) at nod with Dissolve(0.3, alpha=False, time_warp=None)  

Разумеется, перед этим нужно создать отдельный transform для каждой подобной анимации.
Код

transform nod: # 頷き
  ease 0.8 yoffset 10 # Медленное изменение положения спрайта по y  
  ease 0.8 yoffset -0 # …и возвращение в исходное положение.  


Наконец, пожалуй, самый сложный метод вывода анимаций. Это сочетание команд Avg_, scp, iload и некоторых других.
По каким-то причинам разработчики решили наиболее запутанные последовательности выразить именно так. В этом есть определенные плюсы, поскольку все команды доступны в скрипте.
Пример:
Код

^iload,BG1,1_06ヒロイン家リビング夜
  \scp,Avg_Image[0]
  \mm,3,100
  \p2,640,360,10,100,100,0
^cbase,キリエ,大,制服01
^cface,キリエ,赤目通常01
^ce,キリエ
  \sub,@@!Avg_CharaGetNo,self,キリエ
  \scp,Avg_Chara[ResultInt[0]]
  \mm,3,100
  \p2,640,1080,500,200,200,0
  \e,1

^iload загружает нужную картинку на экран, затем через \p2 выполняется ее позиционирование. Поверх этого добавляется спрайт героини ^cbase,キリエ,大,制服01 .
Спрайт в свою очередь позиционируется относительно фона через координаты в \p2 .
Существуют и более сложные цепочки команд для приближения и панорамирования на фоне:
Код

^iload,BG2,ズームイン_エクステ
  \scp,Avg_Image[1]
  \mm,3,100
  \li,0,"Image\n08_1_61.png"
  \exv,0,320
  \exv,1,540
  \exv,2,200
  \exv,3,200
  \exv,4,0
  \exv,5,0

Здесь еще и применяется анимация из ズームイン_エクステ.b
Несмотря на то, что в подобных моментах все команды явно приведены, оказалось проще сделать нечто аналогичное, как в предыдущих случаях. Причина в труднопонимаемой системе координат, которая не совсем совпадает с той, которая используется в RenPy.

Спасало только то, что данные анимации(включая .b файлы) не переусложнены и почти все можно выразить в виде ATL-выражений transform :
Код

transform q_zoom_in_ext(old_widget=None, new_widget=None, delay_time=1.0, zoom_to_x=0.0, zoom_to_y=0.0): #ズームイン_エクステ
  delay delay_time  
  contains:
  old_widget
  contains:
  Flatten(new_widget)
  subpixel True
  size (config.screen_width, config.screen_height)  
  crop_relative True  
  crop (zoom_to_x, zoom_to_y, 0.4, 0.4) alpha 0.0  
  linear delay_time crop (0.0, 0.0, 1.0, 1.0) alpha 1.0


И последними остаются анимации из секций imoavi.
После распаковки, про которую я рассказывал в первой части, imoavi превращается в набор png картинок. RenPy может воспроизводить такие последовательности в виде анимации, но у меня так и не получилось этого сделать без подтормаживаний в игре(возможно, в свежей версии это все-таки сработает).

На помощь пришел ffmpeg, который смог превратить png в webm видеоролик с беспотерьным сжатием. Готовый ролик вставляется вместо анимированного фона.

Заключение

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

Преимущество порта на RenPy только в том, что можно собрать отдельные версии под Linux и Mac, но оригинал и так неплохо работает под Wine.

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

Ссылки:
Наш форк jProcessing:
https://bitbucket.org/Chtobi/jprocessing-py3-master/
Скрипт для переименования файлов и правки скриптов с заменой японских символов на ромадзи:
https://bitbucket.org/Chtobi....n_biman
Скрипт для портирования:
https://bitbucket.org/Chtobi/qlie-tools/src/master/qlie_s_to_renpy/
Статья Haru о портировании игры для PSP:
http://harupspgame.blog64.fc2.com/blog-entry-200.html


Автор материала: Nazon
Материал от пользователя сайта.

Translate 02 Июля 2019 1017 Nazon Qlie, Bishoujo Mangekyou -Norowareshi Den, 美少女万華鏡 呪われし伝説の少女, RenPy 4.5/15

Комментарии (6):
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
2
1 Zerg_4ik   (02 Июля 2019 23:15)
159362
Сколько страданий...

3
2 Хемуль   (03 Июля 2019 00:29)
87473
Вот как обычно. Пишут в блогах какую-то полную чушь - куча народу появляется, а действительно полезную статью - ноль внимания. А, между тем, именно такими по своей информативности, на мой взгляд, должны быть нормальные статьи.

3
3 Lisper   (03 Июля 2019 16:03)
87222
Сколько же труда, изобретательности и времени вложено в такую затею.
Спасибо за годную статью.
P.S. Может, стОит и первую часть разместить на анивизе?

0
5 Nazon   (08 Июля 2019 07:21)
3724
Спасибо.
Не знаю, есть ли смысл, ссылки на первую часть все равно указаны. Ну и скоро обе части можно будет прочитать у нас в блоге.

1
4 Shiigeru   (04 Июля 2019 15:02)
66183
Ничего не понял, но очень интересно. Ставлю 5.

0
6 Wukapmwgzc545   (20 Июля 2019 16:13)
111675
харошая игра