Идея следующей статьи появилась после очередной темы с вопросом про то, как сделать локализацию. И, хоть это было известно ранее, выяснилось, что локализация может быть сделана различными способами, каждый из которых имеет свои плюсы и минусы. Для облегчения выбора наиболее подходящего решил составить статью.
Возникает вопрос: а не проще ли максимально подробно рассказать о наиболее удобном способе локализации вместо того, чтобы занимать место ненужными вариантами? А нет, лучше предоставлять выбор среди исследованных альтернатив, взвешивая плюсы и минусы, чем навязывать свою точку зрения, считая её единственно правильной. В итоге получилось, что половина изученных вариантов легче понимаются новичками, в то время как другая половина сложнее, но намного удобнее в сопровождении.
Под
локализацией подразумевают наличие нескольких языков в игре, возможность переключения между ними и перспективу добавления новых языков в будущем.
Некоторые указанные коды, обозначенные словом "Инициализация", и содержащие большое количество текста на одном языке, то есть весь текст без какой-либо обработки, можно размещать в
пользовательских событиях (Добавить событие - Другие - Пользовательское событие; User defined) или в скрипте. Вместо того, чтобы объявлять несколько одинаковых переменных для разных языков (text1_rus, text1_eng, text1_deu), можно хранить в одном наборе переменных (text1, text2, text3) любой язык, выполняя соответствующий языку
event_user при старте игры и в событии переключения языка. Так, в text1 и другие переменные запишется нужный текст из выбранного языка.
Вариант 1: Переменные, глобальные или локальные.
Пример:// - - инициализация, отдельный event_user для каждого языка - -
global.text_start_game='Начать игру';
global.text_end_game='Выход';
. . .
. . .
// - - рисование - -
draw_text(100,100,global.text_start_game);
// или в случае с локальными переменными
draw_text(100,100,Controller.text_start_game);
Плюсы:- есть возможность называть переменную таким образом, чтобы видеть соответствие конкретной переменной определённому тексту.
Минусы:- ровная противоположность плюсу - широкое многообразие имён переменных, из-за которого рано или поздно возникнут различные неудобства в поддержке кода. Можно забыть название переменных или долго придумывать название для новой, в обоих случаях приходится смотреть на список переменных.
- не совсем удобно передавать тексты переводчикам для перевода на другие языки. Нужно копировать тексты из кода в текстовый файл, а затем переведённый копируется обратно.
Удобство: - среднее, сокращается при увеличении объёма текста.
Вариант 2: Массив.
Пример:// - - инициализация, отдельный event_user для каждого языка - -
text_array[1]='Game start'; // старт игры
text_array[2]='...'; // другой текст
. . . // ...
. . .
// - - рисование - -
draw_text(100,100,text_array[1]); // это "старт игры", чтобы не забыть
Плюсы:- все тексты хранятся под одним именем, нет такого многообразия имён, как в варианте с переменными.
Минусы:-
необходимость запоминать соответствие номеров элементов конкретным текстам или постоянно переключаться с кода рисования текста на код объявления массива, чтобы находить это соответствие. Из этого следует сложность в поиске нужного draw_text, где рисуется текст, координаты которого, например, требуют исправления.
- неудобство в передаче текста переводчикам для перевода. Так же необходимо копировать тексты из кода в файл, а при потере правой части выражения (после знака равно) этот текст вовсе восстановлению не подлежит без повторного запроса исходного текста.
Удобство: - ниже среднего.
Вариант 3: ini-файлы.
Пример:// - - инициализация - -
ini_open(global.lang_file); // где global.lang_file содержит имя ini-файла
text_new_game=ini_read_string(global.lang,'Game_start','***error***'); // где global.lang - выбранный язык в виде строки
ini_close();
// - - рисование - -
draw_text(100,100,text_new_game);
Плюсы:- удобное обозначение текстов в файле, выглядит в виде пар "Game_start=Новая игра".
- удобно передавать файлы с текстом переводчикам.
- удобное обращение к текстам в игре, видно по ключу.
- все языки находятся в одном файле. Каждый язык будет расположен под соответствующей секцией [rus], [eng] и так далее, нужно иметь в виду эту структуру при самостоятельном формировании ini-файла. Хотя при желании можно разделить языки по разным файлам, тогда названия секций будут объединять тексты в логические группы.
Минусы:- частое открывание и закрывание файла при сложной архитектуре кода, например, если невозможно переместить считывание текста из события рисования в создание объекта.
Удобство: - высокое. Файлы уже готовы к передаче переводчикам, а в коде видно по ключу, какой именно текст рисуется в определённом месте.
Примечания:- файл с текстами подключается к проекту с помощью Included files, global.lang_file содержит только имя файла.
- как правило, совмещается с одним из двух предыдущих вариантов - тексты считываются либо в переменные, либо в массив.
Вариант 4: ds_map.
Пример:// - - инициализация без парсера - -
global.text_map=ds_map_create();
// отдельный event_user для каждого языка
ds_map_replace(global.text_map,'Game_start','New game');
ds_map_replace(global.text_map,'Game_end','Exit');
. . .
. . .
// - - инициализация с парсером, допускается размещение в скрипте с аргументом lang - -
global.text_map=ds_map_create();
. . .
switch (lang) // argument0
{case 'eng': ds_map_read(global.text_map,'*******строка*из*парсера******'); break;
case 'rus': ds_map_read(global.text_map,'******************строка*из*парсера*********************'); break;
}
// - - рисование - -
draw_text(100,100,ds_map_find_value(global.text_map,'Game_start'));
// или с использованием аксессоров
draw_text(100,100,global.text_map[? 'Game_start']);
Плюсы:- удобное обозначение текстов в файле, структура как в ini-файле.
- удобно передавать файлы с текстом переводчикам.
- удобное обращение к текстам в игре, видно по названию ключа, какой выводится текст.
-
инициализация одного языка занимает всего одну строку кода в случае с парсером.
Минусы:- для упрощения переработки текстовых файлов в ds_map и сокращения кода в игре требуется парсер, перерабатывающий текст в ds_map_write, который потом копируется в игру одной строкой. В принципе, минусом это является до тех пор, пока кто-нибудь не поделится уже готовым кодом парсера.
- кого-то может смущать длина строки ds_map_read, растущая приблизительно на 100 символов при добавлении одной новой переменной текста.
Удобство: - высокое, и отличное после создания парсера. Единственное, что нужно делать после изменений в файлах с текстами, это прогнать их через парсер и вставить строку в скрипт.
Примечания:- структура ds_map_write различается между GM8.1- и GMS, поэтому парсер должен быть создан на той же версии Game Maker, на которой пишется основной проект.
Вариант 5: Макросы (только для GMS).
Пример:// инициализация происходит в диалоговом окне Resources - Define macros...
// - - образец экспортированного файла с макросами, перед импортом необходимо очистить окно макросов - -
// имена макросов с текстами умышленно не начинаются с TEXT, чтобы не мешали во время выбора из автозаполнения
TEXT_GAME_START=scr_text_selector('TEXT_GAME_START')
TEXT_GAME_END=scr_text_selector('TEXT_GAME_END')
T_GAME_START_ENG='New game'
T_GAME_END_ENG='Exit'
T_GAME_START_RUS='Новая игра'
T_GAME_END_RUS='Выход'
=
// - - скрипт scr_text_selector('TEXT_*') - -
switch (global.lang)
{
case 'rus':
switch (argument0)
{
case 'TEXT_GAME_START': return T_GAME_START_RUS; break;
case 'TEXT_GAME_END': return T_GAME_END_RUS; break;
. . .
}
break;
case 'eng':
switch (argument0)
{
case 'TEXT_GAME_START': return T_GAME_START_ENG; break;
case 'TEXT_GAME_END': return T_GAME_END_ENG; break;
. . .
}
break;
}
// - - рисование - -
draw_text(100,100,TEXT_GAME_START);
Плюсы:-
всплывающая подсказка автозаполнения при наборе первых букв макроса не требует запоминания её названия и помогает избежать ошибок в написании.
- в связи со сходством данного варианта с вариантом, использующим переменные, плюс в возможности давать осмысленные названия макросам.
- макросы добавляются в специальном диалоговом окне, имеющем кнопки сохранения и загрузки из текстового файла, что означает удобные выгрузку из проекта, передачу файла переводчикам, а также добавление текста обратно в проект нажатием одной кнопки.
- при добавлении макросов с одинаковыми именами, о конфликте будет сказано во время компиляции в окне ошибок, "одинаковые значения внутри switch".
Минусы:- широкое многообразие имён, как с вариантом, использующим переменные. В связи с наличием всплывающей подсказки автозаполнения, не такой уж и большой минус.
- возможность допустить ошибку при добавлении нового текста или копировании и исправлении старого с целью добавления нового. Это добавление макроса, вызывающего скрипт, добавление макросов с текстами на всех языках, и добавление в скрипт новой позиции case в каждый язык.
- относительно сложная структура экспортированного файла с макросами требует заблаговременного продумывания имён макросов, чтобы после их сортировки (кнопка в окне Define macros) другие макросы, не относящиеся к тексту, не мешали переводчикам.
Удобство: - высокое. Против всплывающего автозаполнения возникает не вполне удобная процедура добавления нового текста в проект и сложная структура файла.
Примечания:- кто-то может назвать этот вариант замаскированным использованием скриптов, мол, можно было бы draw_text(100,100,scr_text_selector('TEXT_GAME_START')), однако, в таком случае нет помощи автозаполнения и, следовательно, нужно снова запоминать имена переменных.
Вариант 6: switch на месте.
Пример:// - - рисование - -
switch (global.lang)
{
case 'rus': draw_text(100,100,'Новая игра'); break;
case 'eng': draw_text(100,100,'New game'); break;
}
Плюсы:- видно сразу, какой текст отображается на данном участке кода.
- быстро внедряется в проекты с малым количеством текста.
Минусы:- очень неудобное редактирование текстов. Если вдруг находится ошибка в тексте или нужно слегка его изменить, необходимо искать текст по всему проекту. Другие варианты локализаций инициализируют все тексты в одном-двух событиях - в начале игры и в коде переключения языка, или вовсе во внешнем файле.
-
невозможная передача текста переводчикам для перевода. Даже если тексты хранятся отдельно в файле, добавление их в код займёт очень много времени, с вероятностью допущения ошибок.
- ужасное добавление нового языка в проект с большим количеством текста. Чтобы не пропустить какой-либо текст, нужно просматривать все объекты, или использовать поиск по скриптам - draw_text.
Удобство: - очень низкое, обратно пропорциональное объёму текста. Самый быстрый и понятный вариант, самый неудобный для исправлений и перевода.
Вариант 7: Копирование игры.
Плюсы:- отсутствуют?
- разве что оправдано при наличии объёмных файлов озвучки текста на разных языках, занимающие весомую часть от общего размера игры. Сомнительный фактор, который к тому же можно решить загрузкой файлов выбранной озвучки перед началом игры. Результатом этого решения станет одна копия игры, только с разными внешними файлами.
Минусы:- уйма. Следующие минусы раздавят все остальные преимущества, которые возникают при использовании любого варианта хранения текстов в каждой копии игры (переменные, массивы, константы).
- отсутствует возможность переключения языка в игре. Чтобы был другой язык, игроку необходимо скачать эту же игру, но с другим языком.
- при нахождении ошибок в проекте, не только в тексте, необходимо исправлять ошибку во всех копиях игры, собирать и загружать их заново на форумы или серверы (Google Play, AppStore, подобные).
- в конце концов, параллельная разработка одного и того же проекта, делать одно и то же несколько раз по количеству языков.
Удобство: -
- стремится к нулю. Интересно, кому вообще может быть удобна параллельная разработка одного и того же проекта, но с разными языками.
Примечания:- такой вариант тоже может возникнуть у кого-либо, поэтому и его нужно обсудить, однако, скорее всего, о таком может подумать только теоретик, не понимающий всех последующих за таким решением неудобств в сопровождении проекта.
Вывод: наиболее удобными, популярными и рекомендуемыми оказались варианты локализаций, использующие
ini-файлы, ds_map и макросы.
Оставшиеся варианты -
с переменными, массивом и switch на месте - приемлемы в небольших проектах с малым объёмом текста, а также легче понимаются новичками.
Вариант
копирования игры не рекомендуется.
Прилагаю код парсера текстового файла в ds_map_write - вариант локализации "
ds_map".
Структура файла должна быть следующей:
//список текстов в игре
Game start=Новая#игра
Game end=Выход
//бонусы
Bonus speed=Ускорение
Очень похоже на ini-файл, но отсутствуют секции в привычном виде. Допускаются пропуски строк и вообще любые строки, не содержащие знак равно, такие считываться не будут. Решётка является принудительным переносом строки.
Для кириллицы убедиться, что файл сохранён в кодировке UTF-8.Код открывает файл с текстами через диалог, переводит текст в ds_map_write и записывает результат в буфер обмена.
var name,f,map,pos,key;
name=get_open_filename('Text|*.txt',''); // you can select file from any location if on Windows
if name='' {exit}
map=ds_map_create();
f=file_text_open_read(name);
while (!file_text_eof(f)) // read full file
{
str1=file_text_read_string(f); // read full line
file_text_readln(f);
pos=string_pos('=',str1); // find equal sign
if pos=0 {continue} // empty string here, look next line
key=string_copy(str1,1,pos-1); // copy text before "="
if string(ds_map_find_value(map,key))!='0' {show_message('Double key! '+key); continue}
// found available key again, say to user and don't add the same key again
ds_map_add(map,key,string_copy(str1,pos+1,string_length(str1)-pos)); // add new key to ds_map
}
file_text_close(f);
clipboard_set_text(ds_map_write(map)); // copy ds_map to clipboard, you can paste it into ds_map_read with Control+V
ds_map_destroy(map);
Автор: Fantom.
Запрещается копирование данной статьи без ведома и согласия автора.