Lua-модули позволяют структурировать проект и создавать многократно используемый библиотечный код. Избежание дублирования в проектах является хорошей идеей. Defold позволяет использовать функциональность Lua-модулей, предоставляя возможность включать файлы скриптов в другие файлы скриптов. Это позволяет инкапсулировать функциональность (и данные) во внешний файл скрипта для повторного использования в скриптах игровых объектов и GUI-скриптах.
Lua-код, хранящийся в файлах с окончанием “.lua” где-либо в структуре игрового проекта, может быть затребован в файлах скриптов и GUI-скриптов. Чтобы создать новый файл Lua-модуля, кликните ПКМ по папке, в которой хотите его создать, в представлении Assets, затем выберите New... ▸ Lua Module. Дайте файлу уникальное имя и кликните Ok:
Предположим, в файл “main/anim.lua” добавлен следующий код:
function direction_animation(direction, char)
local d = ""
if direction.x > 0 then
d = "right"
elseif direction.x < 0 then
d = "left"
elseif direction.y > 0 then
d = "up"
elseif direction.y < 0 then
d = "down"
end
return hash(char .. "-" .. d)
end
В таком случае любой скрипт может потребовать этот файл и использовать функцию:
require "main.anim"
function update(self, dt)
-- обновить позицию, задать направление и т.д.
...
-- установить анимацию
local anim = direction_animation(self.dir, "player")
if anim ~= self.current_anim then
msg.post("#sprite", "play_animation", { id = anim })
self.current_anim = anim
end
end
Функция require
загружает конкретный модуль. Она начинает с просмотра таблицы package.loaded
, чтобы определить, загружен ли уже модуль. Если да, то require
возвращает значение, хранящееся в package.loaded[module_name]
. В противном случае он загружает и оценивает файл с помощью загрузчика.
Синтаксис строки имени файла, передаваемой в require
, немного своеобразен. Lua заменяет символы ‘.’ в строке имени файла на разделители путей: ‘/’ в macOS и Linux и ‘\’ в Windows.
Стоит отметить, что использование глобальной области видимости для хранения состояния и определения функций, как мы делали выше, обычно является плохой идеей. В этом случае существует риск конфликта имен, раскрытия состояния модуля или введения связи между пользователями модуля.
Для инкапсуляции данных и функций Lua использует модули. Lua-модуль — это обычная Lua-таблица, используемая для хранения функций и данных. Таблица объявляется локальной, чтобы не засорять глобальную область видимости:
local M = {}
-- private
local message = "Hello world!"
function M.hello()
print(message)
end
return M
Теперь модуль можно использовать. Опять же, предпочтительнее присвоить его локальной переменной:
local m = require "mymodule"
m.hello() --> "Hello world!"
Рассмотрим простой модуль:
-- module.lua
local M = {} -- создает новую таблицу в локальной области видимости
M.value = 4711
return M
И пользователь модуля:
local m = require "module"
print(m.value) --> "4711" (даже если "module.lua" изменен и перезагружен на горячую)
При горячей перезагрузке файла модуля код снова выполняется, но с m.value
ничего не происходит. Почему?
Во-первых, таблица, созданная в “module.lua”, создается в локальной области видимости, и пользователю возвращается ссылка на эту таблицу. Перезагрузка “module.lua” снова оценивает код модуля, но при этом создается новая таблица в локальной области видимости вместо обновления таблицы, на которую ссылается m
.
Во-вторых, Lua кэширует требуемые файлы. Когда файл требуется в первый раз, он помещается в таблицу package.loaded
, чтобы его можно было быстрее прочитать при последующих запросах. Можно заставить файл быть повторно считанным с диска, установив запись файла в значение nil: package.loaded["my_module"] = nil
.
Чтобы правильно выполнить горячую перезагрузку модуля, необходимо перезагрузить модуль, сбросить кэш, а затем перезагрузить все файлы, которые использует модуль. Это далеко не оптимальный вариант.
Вместо этого можно рассмотреть обходной путь, который можно использовать при разработке: поместить таблицу модуля в глобальную область видимости и заставить M
ссылаться на глобальную таблицу вместо создания новой таблицы при каждой оценке файла. Перезагрузка модуля изменит содержимое глобальной таблицы:
--- module.lua
-- Заменить на локальное M = {} после завершения
uniquevariable12345 = uniquevariable12345 or {}
local M = uniquevariable12345
M.value = 4711
return M
Модули с состоянием хранят внутреннее состояние, которое является общим для всех пользователей модуля, и их можно сравнить с синглтонами:
local M = {}
-- Все пользователи модуля будут совместно использовать эту таблицу
local state = {}
function M.do_something(foobar)
table.insert(state, foobar)
end
return M
С другой стороны, модуль без состояния не хранит никакого внутреннего состояния. Вместо этого он предоставляет механизм для экстернализации состояния в отдельную таблицу, локальную для пользователя модуля. Вот несколько различных способов реализовать это:
local M = {}
function M.alter_state(the_state, v)
the_state.value = the_state.value + v
end
function M.get_state(the_state)
return the_state.value
end
function M.new(v)
local state = {
value = v
}
return state
end
return M
Use the module like this:
local m = require "main.mymodule"
local my_state = m.new(42)
m.alter_state(my_state, 1)
print(m.get_state(my_state)) --> 43
local M = {}
function M:alter_state(v)
-- self is added as first argument when using : notation
self.value = self.value + v
end
function M:get_state()
return self.value
end
function M.new(v)
local state = {
value = v
}
return setmetatable(state, { __index = M })
end
return M
Use the module like this:
local m = require "main.mymodule"
local my_state = m.new(42)
my_state:alter_state(1) -- "my_state" is added as first argument when using : notation
print(my_state:get_state()) --> 43
__index
, но при этом каждое замыкание содержит свою собственную копию методов, вследствие чего потребление памяти выше. local M = {}
function M.new(v)
local state = {
value = v
}
state.alter_state = function(v)
state.value = state.value + v
end
state.get_state = function()
return state.value
end
return state
end
return M
Используйте модуль следующим образом:
local m = require "main.mymodule"
local my_state = m.new(42)
my_state.alter_state(1)
print(my_state.get_state())
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB