Всем привет
Продолжаем серию разборов чужого кода с этой страницы: Публичный пример Defold.
Сегодня разбираем пример от britzl — Локализация (Localization).
Попробуйте этот проект в действии: запустите пример.
Исходная папка с проектом: ссылка на github.
Исходная папка с публичными примера Defold на github: ссылка на github.
Обращение к новичкам:
Я предполагаю, что у вас уже установлен Defold, если нет, перейдите по этой ссылке: Добро пожаловать в Defold.
Также, будет плюсом, если вы хотя бы поверхностно знакомы со строительными блоками Defold. Если нет, ознакомиться с основными концепциями Defold можно на официальном сайте — перейдите по этой ссылке.12.
Пример того, как скачать и открыть готовый проект:
Скачиваем архив с примерами проектов из github
Распаковываем скачанный ZIP архив примеров в любую папку во вашему усмотрению.
Переходим в папку examples
.
Ищем проект play_animation
.
Открываем game.project
.
Внимание: скриншоты представлены ниже, это пример скачивания и открытия проекта, в этом примере мы рассматриваем проект с названием play_animation, потому название папок проекта будет отличаться.
Этот проект представляет собой пример реализации системы локализации для игрового движка Defold. Его цель — показать, как можно обрабатывать переведённый текст в играх, переключать языки и отображать соответствующие строки в интерфейсе. Проект является минималистичным, но демонстрирует базовые принципы локализации, которые могут быть расширены для более сложных сценариев.
Рассмотрим файловую структуру этого проекта:
- В папке
fonts
размещены файлы, необходимые для отображения пользовательского шрифта в этом проекте. - В папке
images
размещены изображения, которые будут использованы в проекте.
localization.atlas
— это место, куда мы добавим изображения из папкиimages
, которые потом будут использоваться игровыми объектами и их компонентами.
localization.collection
— коллекция, которая является основной и загрузочной коллекцией этого примера.
Содержит два игровых объекта.bg
— содержит компонентsprite
, который берёт изображение из атласаlocalization.atlas
.go
— этот игровой объект содержит в себе компонентыsprite
,label
иscript
.
Как работает код:
- Инициализация:
- Главный скрипт загружает модули
localization.translate
иlocalization.translations
. - Вызывает
translate.add_translations(translations)
, чтобы передать таблицу переводов вM.translations
модуляtranslate
. - Устанавливает начальный текст метки через
translate("GREETING", "Bob"
).
- Главный скрипт загружает модули
- Обновление интерфейса:
- Каждый кадр (update) главный скрипт отправляет сообщение рендерингу для отображения текущего языка (
translate.current_language
) и подсказки. - При переключении языка (
on_input
) вызываетсяtranslate.change_language()
, а затемupdate_translation()
обновляет текст метки.
- Каждый кадр (update) главный скрипт отправляет сообщение рендерингу для отображения текущего языка (
- Логика локализации:
- Модуль
translate
хранит переводы вM.translations
и используетM.current_language
для выбора нужного языка. - Функция
translate
(или вызовM(key, ...)
) возвращает переведённую строку, подставляя аргументы (например, “Bob
”) в%s
. - Таблица переводов из
localization.translations
предоставляет строки дляGREETING
иBYE
на двух языках.
- Модуль
Рассмотрим localization.script:
Он реализует простую систему локализации (перевода текста) с возможностью переключения языков по клику:
local translate = require "localization.translate"
local translations = require "localization.translations"
local LANGUAGES = { "en", "se" }
local function update_translation()
label.set_text("#label", translate("GREETING", "Bob"))
end
function init(self)
msg.post(".", "acquire_input_focus")
self.language_index = 0
translate.add_translations(translations)
update_translation()
end
function final(self)
msg.post(".", "release_input_focus")
end
function update(self, dt)
msg.post("@render:", "draw_text", { text = "Click to change language. Current: " .. translate.current_language, position = vmath.vector3(70, 100, 0) } )
end
function on_input(self, action_id, action)
if action.released then
self.language_index = (self.language_index + 1) % #LANGUAGES
translate.change_language(LANGUAGES[1 + self.language_index])
update_translation()
end
end
Подключение модулей (Что такое модуль, что такое require в Defold)
local translate = require "localization.translate"
local translations = require "localization.translations"
Определение доступных языков:
local LANGUAGES = { "en", "se" }
Создаётся таблица LANGUAGES
, содержащая коды языков: “en
” (английский) и “se
” (шведский). Это список языков, между которыми можно переключать.
Функция update_translation
:
local function update_translation()
label.set_text("#label", translate("GREETING", "Bob"))
end
- Функция обновляет текст компонента с идентификатором
#label
(текстовый компонент в сцене Defold вlocalization.collection
). translate("GREETING", "Bob")
вызывает функцию из модуляtranslate
, которая возвращает переведённую строку для ключа"GREETING"
, подставляя"Bob"
в шаблон перевода.
Функция init
:
function init(self)
msg.post(".", "acquire_input_focus")
self.language_index = 0
translate.add_translations(translations)
update_translation()
end
Это стандартная функция инициализации в Defold, которая вызывается при создании игрового объекта.
msg.post(".", "acquire_input_focus")
:
- Отправляет сообщение текущему объекту (
.
означает текущий объект), чтобы он получил фокус ввода. Это позволяет объекту обрабатывать пользовательский ввод (например, клики мыши или касания).
self.language_index = 0:
- Инициализирует переменную
language_index
в таблицеself
(контекст игрового объекта) значением 0. - language_index отслеживает текущий язык (0 соответствует первому языку в
LANGUAGES
, т.е. “en
”).
translate.add_translations(translations):
- Регистрирует таблицу переводов из модуля
translations
в системе локализации. Это делает переводы доступными для функцииtranslate()
.
update_translation():
- Вызывает функцию для установки начального текста метки (например,
"Hello, Bob!"
для английского языка).
Функция final
:
function final(self)
msg.post(".", "release_input_focus")
end
Это стандартная функция Defold, вызываемая при уничтожении игрового объекта.
msg.post(".", "release_input_focus"):
- Освобождает фокус ввода, чтобы объект больше не обрабатывал пользовательские события. Это хорошая практика для очистки ресурсов.
Функция update
:
function update(self, dt)
msg.post("@render:", "draw_text", { text = "Click to change language. Current: " .. translate.current_language, position = vmath.vector3(70, 100, 0) } )
end
Это стандартная функция Defold, вызываемая каждый кадр.
msg.post("@render:", "draw_text", {...}):
- Отправляет сообщение системе рендеринга (
@render
) для отрисовки текста на экране.
{ text = ..., position = vmath.vector3(70, 100, 0) }
:
text: строка "Click to change language. Current: "
+текущий язык (например, "en" или "se")
, полученный черезtranslate.current_language
.
position
: координаты текста на экране (x=70, y=100, z=0).
translate.current_language
:
- Это свойство или функция модуля
translate
, возвращающая код текущего языка (например, “en”). - Результат: на экране отображается текст, например, “
Click to change language. Current: en
”
Рассмотрим translate.lua:
-- create this by hand or generate it from an export from a localization tool of some kind
-- optionally parse it from json
return {
["en"] = {
GREETING = "Hello, my name is %s",
BYE = "Bye",
},
["se"] = {
GREETING = "Hej, jag heter %s",
BYE = "Hej då",
}
}
Этот код представляет собой Lua-модуль, который возвращает таблицу с переводами для двух языков: английского (“en
”) и шведского (“se
”)
Код возвращает Lua-таблицу, которая используется как модуль в Defold. Таблица организована следующим образом:
- Ключи верхнего уровня — это коды языков: “
en
” (английский) и “se
” (шведский). - Для каждого языка есть подтаблица с ключами (GREETING, BYE) и соответствующими строками перевода.
Ключи и значения:
- “
GREETING
”: строка приветствия, содержащая заполнитель%s
для подстановки имени.- Для “
en
”:"Hello, my name is %s"
(например, подставив “Bob
”, получим “Hello, my name is Bob
”). - Для “
se
”:"Hej, jag heter %s"
(например, “Hej, jag heter Bob
”).
- Для “
- “
BYE
”: строка прощания.- Для “
en
”: “Bye
”. - Для “
se
”: “Hej då
”.
- Для “
Рассмотрим translations.lua:
local M = {
default_language = "en",
current_language = sys.get_sys_info().device_language or sys.get_sys_info().language,
translations = {},
}
--- Add translations
-- Translations should be in the format:
--
-- {
-- [language] = {
-- [key] = string,
-- }
-- }
-- @param translations
function M.add_translations(translations)
for language,translations_for_language in pairs(translations) do
M.translations[language] = M.translations[language] or {}
for key,translation in pairs(translations_for_language) do
M.translations[language][key] = translation
end
end
end
--- Change current language
-- @param language
function M.change_language(language)
M.current_language = language
end
--- Get translation for a specific key, optionally formatting it with
-- additional values
-- @param key The key containing the text to translate
-- @return The translated text
function M.translate(key, ...)
local t = M.translations[M.current_language] or M.translations[M.default_language]
if not t or not t[key] then
return key
else
if select("#", ...) > 0 then
return t[key]:format(...)
else
return t[key]
end
end
end
-- this will make it possible to call the module table as a function and invoke M.translate()
return setmetatable(M, {
__call = function(self, key, ...)
return M.translate(key, ...)
end
})
local M = {
default_language = "en",
current_language = sys.get_sys_info().device_language or sys.get_sys_info().language,
translations = {},
}
Создаётся локальная таблица M
, которая будет возвращена как модуль.
Поля таблицы:
-
default_language = "en"
: Устанавливает английский язык как язык по умолчанию, если текущий язык недоступен. -
current_language = sys.get_sys_info().device_language or sys.get_sys_info().language
:- Устанавливает текущий язык на основе системной информации устройства.
sys.get_sys_info().device_language
возвращает язык устройства (например, “en
”, “se
”), если доступно (в Defold это зависит от платформы).- Если
device_language
недоступно (например, на некоторых платформах), используетсяsys.get_sys_info().language
(устаревшее поле, но для совместимости). - Если оба значения
nil
, тоcurrent_language
останетсяnil
до вызоваchange_language
.
-
translations = {}
: Пустая таблица для хранения переводов, куда будут добавляться данные из add_translations.
--- Add translations
-- Translations should be in the format:
--
-- {
-- [language] = {
-- [key] = string,
-- }
-- }
-- @param translations
function M.add_translations(translations)
for language,translations_for_language in pairs(translations) do
M.translations[language] = M.translations[language] or {}
for key,translation in pairs(translations_for_language) do
M.translations[language][key] = translation
end
end
end
- Функция добавляет переводы в таблицу
M.translations
. - Ожидаемый формат
translations
:
{
["en"] = { GREETING = "Hello, my name is %s", BYE = "Bye" },
["se"] = { GREETING = "Hej, jag heter %s", BYE = "Hej då" }
}
- Внешний цикл
for language,translations_for_language in pairs(translations)
перебирает языки (например, “en
”, “se
”). - Для каждого языка создаётся пустая таблица в M.translations[
language
], если она ещё не существует (M.translations[language] or {}
). - Внутренний цикл
for key,translation in pairs(translations_for_language)
добавляет каждую пару ключ-перевод (например,GREETING = "Hello, my name is %s"
) в таблицуM.translations[language]
.
После вызова add_translations
таблица M.translations
заполняется переводами, например:
M.translations = {
en = { GREETING = "Hello, my name is %s", BYE = "Bye" },
se = { GREETING = "Hej, jag heter %s", BYE = "Hej då" }
}
Функция change_language
--- Change current language
-- @param language
function M.change_language(language)
M.current_language = language
end
Функция устанавливает текущий язык, сохраняя его в M.current_language
.
- Аргумент
language
— это строка, например, “en
” или “se
”. - Это используется для выбора нужной таблицы переводов при вызове
translate
.
Функция translate
:
--- Get translation for a specific key, optionally formatting it with
-- additional values
-- @param key The key containing the text to translate
-- @return The translated text
function M.translate(key, ...)
local t = M.translations[M.current_language] or M.translations[M.default_language]
if not t or not t[key] then
return key
else
if select("#", ...) > 0 then
return t[key]:format(...)
else
return t[key]
end
end
end
- Функция возвращает переведённую строку для указанного ключа
key
на текущем языке, с возможностью форматирования строки с дополнительными аргументами (…). - Шаги выполнения:
- Выбор таблицы переводов:
local t = M.translations[M.current_language] or M.translations[M.default_language]
:- Пытается получить таблицу переводов для текущего языка (M.current_language).
- Если такой таблицы нет (например, язык не поддерживается), используется таблица для языка по умолчанию (
M.default_language
, т.е. “en”).
- Проверка наличия перевода:
if not t or not t[key] then return key
:- Если таблица переводов отсутствует или ключ key не найден, возвращается сам ключ (это защита от ошибок, чтобы вместо ошибки отобразить, например, “GREETING”).
- Форматирование строки:
if select("#", ...) > 0 then
:select("#", ...)
возвращает количество дополнительных аргументов, переданных в функцию.- Если аргументы есть (например, “Bob”), вызывается
t[key]:format(...)
, который форматирует строку с использованиемstring.format
.
Пример: еслиt[key] = "Hello, my name is %s"
и аргумент"Bob"
, то результат —"Hello, my name is Bob"
.
else return t[key]
: - Если аргументов нет, возвращается строка перевода без изменений (например, “Bye”).
M.translations = { en = { GREETING = "Hello, %s" }, se = { GREETING = "Hej, %s" } }
M.current_language = "en"
print(M.translate("GREETING", "Bob")) -- Вывод: "Hello, Bob"
M.current_language = "se"
print(M.translate("GREETING", "Bob")) -- Вывод: "Hej, Bob"
print(M.translate("BYE")) -- Вывод: "Hej då" (без форматирования)
print(M.translate("UNKNOWN")) -- Вывод: "UNKNOWN" (ключ не найден)
Метатаблица для вызова модуля как функции:
return setmetatable(M, {
__call = function(self, key, ...)
return M.translate(key, ...)
end
})
Модуль возвращается с установленной метатаблицей, которая позволяет вызывать таблицу M как функцию.
- Метаметод
__call
перехватывает вызовы видаM(key, ...)
и перенаправляет их вM.translate(key, ...)
.
Это делает синтаксис более удобным. Вместо:
M.translate("GREETING", "Bob")
можно писать:
M("GREETING", "Bob")
Например:
local translate = require "localization.translate"
print(translate("GREETING", "Bob")) -- То же, что translate.translate("GREETING", "Bob")
Исправление бага:
Если во время игры фоновое изображение не отображается, то установите z-позицию
игрового объекта bg
на -0.5
:
Надеюсь, кому-нибудь этот материал будет полезен.
Всем спасибо за внимание
Другие разборы:
- Разбор проектов на Defold 1. Tilemap Collisions [tilemap]
- Разбор проектов на Defold 2. Параллакс [parallax]
- Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
- Разбор проектов на Defold 4. Воспроизвести анимацию [animation, movement, input]
- Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции [proxy-collection, gameloop, collection, gui]
- Разбор проектов на Defold 6. Пауза [пауза]
- Разбор проектов на Defold 7. Простая кнопка [простая кнопка]
- Разбор проектов на Defold 8. Фабрики и свойства [фабрики и свойства]
- Разбор проектов на Defold 9. Селектор уровня [селектор уровня].
- Разбор проектов на Defold 10. Боец комбо [ввод и анимация]
- Разбор проектов на Defold 11. Coin Magnet (Магнит монет) [фабрика]
- Разбор проектов на Defold 12. Simple Ai (простой ИИ) [ИИ][AI]