Game Maker - создание игр | HellRoom Games
Январь 13, 2025, 17:37:43 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.

Войти
Новости:
 
   Начало   Game Maker Помощь Правила форума Поиск Календарь Войти Регистрация  
Страниц: [1]   Вниз
  Печать  
Автор Тема: Сравнение вариантов локализаций, мультиязычность, выбор языка в игре  (Прочитано 17477 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Fantom
I am... All of me
Гл. Администратор
*

Репутация: 1325
Offline Offline

Пол: Мужской
Награды:
5000 сообщений!За постоянность! [500 дней на форуме]За лояльность! [+1000 репутации]За отличные статьи по Game Maker!Тру Админ :DЗнаток Game Maker...
API: GameMaker Studio Master
Сообщений: 5026



« : Апрель 14, 2016, 11:55:21 »

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

   Под локализацией подразумевают наличие нескольких языков в игре, возможность переключения между ними и перспективу добавления новых языков в будущем.
Некоторые указанные коды, обозначенные словом "Инициализация", и содержащие большое количество текста на одном языке, то есть весь текст без какой-либо обработки, можно размещать в пользовательских событиях (Добавить событие - Другие - Пользовательское событие; 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 и записывает результат в буфер обмена.
Код: (ds_map_write_parser)
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.
Запрещается копирование данной статьи без ведома и согласия автора.
« Последнее редактирование: Апрель 27, 2016, 13:20:13 от Fantom » Записан

Christopher
mgflernGD
GM Pro user
*

Репутация: 99
Offline Offline

Пол: Мужской
Награды:
3 место в HellRoom Jam XВторое место на HellRoom Jam #9 [Антибиотик]500 сообщений!За постоянность! [10 дней на форуме]
API: Game Maker 7.0 Pro
Деятельность: Инди-разработчик
Сообщений: 713


DragonGameStudios


WWW
« Ответ #1 : Апрель 14, 2016, 12:02:40 »

Ого, это определенно пойдет в копилку  Уважуха
Кстати, я использовал переменные, сохраняя текст в скрипте, вызывая его при надобности, менял локализацию через глобальную переменную(подводный камень таков, что каждый обьект должен был вызывать скрипт\или же обьект контроллер, что вызывало проблемы, где хранить строки)
Записан
life Jumb
GM Pro user
*

Репутация: 251
Offline Offline

Пол: Мужской
Награды:
1000 сообщений!За постоянность! [100 дней на форуме]За лояльность! [+150 репутации]За добавление полезных программ!Третье место на HellRoom Jam #6 [Игра на конкурс]2 место в конкурсе Адекватные игры #3 [Антиутопия]
API: GameMaker Studio Pro
Деятельность: Программист
Сообщений: 1512



WWW
« Ответ #2 : Апрель 14, 2016, 15:48:29 »

Статья хорошая, самый нормальный вариант, это ds_map

дс_мапов столько же, сколько языков будет в игре (скорее всего 2)
С дублированным содержанием
global.txt_ru=ds_map_create();
global.txt_en=ds_map_create();

Далее либо руками прямо на месте, либо сканируем файл(ы) и заполняем содержимое обоих дс_мапов

Для упрощения поиска значения скрипт t_g("Game start")

Код: (draw_gui)
draw_text(x,y, t_g("Game end"))
В принципе, я бы сделал так.  
Записан

 
Fantom
I am... All of me
Гл. Администратор
*

Репутация: 1325
Offline Offline

Пол: Мужской
Награды:
5000 сообщений!За постоянность! [500 дней на форуме]За лояльность! [+1000 репутации]За отличные статьи по Game Maker!Тру Админ :DЗнаток Game Maker...
API: GameMaker Studio Master
Сообщений: 5026



« Ответ #3 : Апрель 14, 2016, 16:02:14 »

В моём варианте достаточно одного ds_map, который перезаписывается в начале игры и в момент переключения языка. Перезапись происходит одной командой ds_map_read, если есть парсер, или вызовом event_user/скрипта, который содержит ряд текстов на отдельном языке.
Для меня, такой скрипт в рисовании, и постоянное хранение нескольких ds_map в памяти, немного излишни.
Сам использую ds_map, даже в относительно небольших проектах, привык уже
Записан

life Jumb
GM Pro user
*

Репутация: 251
Offline Offline

Пол: Мужской
Награды:
1000 сообщений!За постоянность! [100 дней на форуме]За лояльность! [+150 репутации]За добавление полезных программ!Третье место на HellRoom Jam #6 [Игра на конкурс]2 место в конкурсе Адекватные игры #3 [Антиутопия]
API: GameMaker Studio Pro
Деятельность: Программист
Сообщений: 1512



WWW
« Ответ #4 : Апрель 14, 2016, 16:10:27 »

Просто я предложил вариант, он не претендует быть удобным для всех.
А вот скриптами упрощающими работу пренебрегать не стоит во всех вариантах.
Записан

 
igorm13
Новичок
*

Репутация: -2
Offline Offline

API: GameMaker 8.1 Standard
Деятельность: шутерами
Сообщений: 5



« Ответ #5 : Май 23, 2016, 20:46:57 »

хорошо,буду ипользовать!!!!! 
Записан
MusNik
KeeVee Games
GM Pro user
*

Репутация: 733
Offline Offline

Пол: Мужской
Награды:
3000 сообщений!За постоянность! [200 дней на форуме]За лояльность! [+500 репутации]За отличную игру Rock painting story!За помощь в развитии форума!За отличные статьи по Game Maker!...
API: GameMaker Studio 2
Сообщений: 3241



WWW
« Ответ #6 : Март 15, 2017, 18:38:52 »

ds_map, кстати из json'а загрузить можно, и парсер не нужен.
Записан

Черный Думер
Пользователь
***

Репутация: 11
Offline Offline

Пол: Мужской
API: GameMaker 8.1 Standard
Деятельность: Организатор проектов, тестер, разработчик вспомогательного кода
Сообщений: 53


Треугольник будет выпит!


WWW
« Ответ #7 : Май 23, 2019, 12:34:08 »

Есть ещё такой вариант, очень аккуратный и удобный, на мой взгляд. Нигде не видел, придумал сам.

Создаём объект, назовём его i18n (можно ещё lang или str - главное, чтобы покороче).
В нём определяем User Event 0, в котором прописываем локальные переменные с текстом на стандартном языке (например английском).
Также делаем Create Event, в котором вызываем User Event 0.

Создаём экземпляр этого объекта, он будет у нас единственным на всём времени работы программы (этот приём, он же шаблон проектирования, называется "синглтон").
А поскольку экземпляр уникален, то обращаться к строкам можно так: i18n.string_name.
Локализацию же загружаем, присваивая считанные строки через variable_local_set() (variable_instance_set() в GMS и GMS2).
Таким образом, файл локализации должен содержать как минимум пары "имя переменной - локализованный текст".
Сброс локализации на стандартную осуществляется также просто - выполнением User Event 0 у нашего объекта.

Заметим, что у объекта пространство имён собственное и изолированное, благодаря чему строки не замусоривают общее (глобальное) пространство имён.
Вдобавок, из-за этого variable_local_set() не сможет изменить какую-нибудь важную переменную - вот вам и безопасность на сдачу.
А ещё такой подход позволяет создавать частичные локализации - строки, которых нет в файле, просто останутся с изначально прописанным текстом на стандартном языке.

Попробуйте, не пожалеете. Надеюсь, кому-нибудь да окажется полезно.
« Последнее редактирование: Май 23, 2019, 12:42:37 от Черный Думер » Записан

Codepage Converter - Обеспечение совместимости старых расширений с GM 8.1
Bit Wizard - Функции для работы с битами
XP Messages - Стандартные сообщения GM в WinXP-стиле

Чёрный Думер, Черный Думер
С монстрами сражается.
Черный Думер, Черный Думер
Рокетланчер плавится.
SilentPhil
Norland
GM Pro user
*

Репутация: 479
Offline Offline

Пол: Мужской
Награды:
Первое место на HellRoom Jam #7 [Hell in Your Fridge]500 сообщений!За постоянность! [50 дней на форуме]За лояльность! [+150 репутации]Настоящий игродел!Второе место на HellRoom Jam #6 [По следам Артакса]...
API: GameMaker Studio 2
Деятельность: GML, Pixel Art
Сообщений: 1363



WWW
« Ответ #8 : Май 23, 2019, 13:10:09 »

Еще есть такая штука как JSON, которая в GMS 2 загружается в ds_map одной командой. Посему очень удобно хранить файлы локализации, настройки и другие штуки (даже сейвы при желании) в JSON.
Записан

         
Да, на них можно кликать.
Aristokrat952
Твоя мама - лама
GM Pro user
*

Репутация: 385
Offline Offline

Пол: Женский
Награды:
1 место в Дичайшем джеме #8500 сообщений!За постоянность! [500 дней на форуме]За лояльность! [+150 репутации]1 место в конкурсе Адекватные игры #3 [Антиутопия]3 место в конкурсе ...
API: Game Maker 8.0 Pro
Деятельность: Чет там делает
Сообщений: 1311


Дмитрий - зануда.


« Ответ #9 : Май 23, 2019, 17:45:05 »

Еще есть такая штука как JSON, которая в GMS 2 загружается в ds_map одной командой. Посему очень удобно хранить файлы локализации, настройки и другие штуки (даже сейвы при желании) в JSON.

Спасибо, Musnik. В смысле Фил. Запутался..
Записан

Статус: сплю и анимешусь с Яшей
SilentPhil
Norland
GM Pro user
*

Репутация: 479
Offline Offline

Пол: Мужской
Награды:
Первое место на HellRoom Jam #7 [Hell in Your Fridge]500 сообщений!За постоянность! [50 дней на форуме]За лояльность! [+150 репутации]Настоящий игродел!Второе место на HellRoom Jam #6 [По следам Артакса]...
API: GameMaker Studio 2
Деятельность: GML, Pixel Art
Сообщений: 1363



WWW
« Ответ #10 : Май 23, 2019, 19:27:23 »

Ага, неловко вышло.
Записан

         
Да, на них можно кликать.
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  

HellRoom Games © 2006-2012 All Rights Reserved
Powered by SMF 1.1.21 | SMF © 2013, Simple Machines
Страница сгенерирована за 1.734 секунд. Запросов: 34.