Moduły Lua pozwalają na strukturyzację projektu i tworzenie wielokrotnie używanego kodu bibliotecznego. Ogólnie warto unikać duplikacji w projektach. Defold pozwala korzystać z funkcjonalności modułu Lua, aby dołączać pliki skryptowe do innych plików skryptowych. Dzięki temu można zawierać funkcje (i dane) w zewnętrznym pliku skryptowym, aby ponownie używać ich w plikach skryptów obiektów gry i plikach skryptów GUI.
Kod Lua przechowywany w plikach z rozszerzeniem “.lua” w dowolnym miejscu w strukturze projektu gry może być wymagany (dołączony) w plikach skryptowych i plikach skryptów GUI. Aby utworzyć nowy plik modułu Lua, kliknij prawym przyciskiem myszy folder, w którym chcesz go utworzyć w widoku Assets, a następnie wybierz New... ▸ Lua Module. Nadaj plikowi unikalną nazwę i naciśnij Ok:
Załóżmy, że poniższy kod jest dodany do pliku “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
Następnie dowolny skrypt może zażądać (ang. require) tego pliku i używać funkcji:
require "main.anim"
function update(self, dt)
-- zaktualizuj pozycję, ustaw kierunek itp.
...
-- ustaw animację
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
Funkcja require
wczytuje podany moduł. Na początek przegląda tabelę package.loaded
, aby sprawdzić, czy moduł jest już załadowany. Jeśli tak, funkcja require
zwraca wartość przechowywaną w package.loaded[module_name]
. W przeciwnym przypadku wczytuje i ewaluuje plik za pomocą ładowacza (ang. loader).
Składnia łańcucha nazwy pliku (filename string) przekazywanej do require jest nieco specjalna. Lua zamienia znaki "."
w łańcuchu nazwy pliku na znaki separatora ścieżki: '/'
na macOS i Linux oraz '\'
na systemie Windows.
Należy zauważyć, że zazwyczaj nie jest dobrym pomysłem używanie zakresu globalnego do przechowywania stanu i definiowania funkcji, tak jak w przykładzie powyżej. Istnieje ryzyko kolizji nazw, ujawnienia stanu modułu lub wprowadzenia zależności między użytkownikami modułu.
Aby inkapsulować dane i funkcje, Lua używa modułów. Moduł Lua to zwykła tabela Lua służąca do zawierania funkcji i danych. Tabela jest deklarowana jako lokalna, aby nie zanieczyszczać zakresu globalnego:
local M = {}
-- private
local message = "Hello world!"
function M.hello()
print(message)
end
return M
Moduł można następnie używać. Ponownie, preferuje się przypisanie go do zmiennej lokalnej:
local m = require "mymodule"
m.hello() --> "Hello world!"
Rozważmy prosty moduł:
-- module.lua
local M = {} -- tworzy nową tabelę w zakresie lokalnym
M.value = 4711
return M
I użytkownika modułu:
local m = require "module"
print(m.value) --> "4711" (nawet jeśli plik "module.lua" zostanie zmieniony i na nowo załadowany)
Jeśli ponownie załadujesz plik modułu, kod zostanie uruchomiony ponownie, ale nic się nie dzieje z m.value
. Dlaczego?
Po pierwsze, tabela utworzona w pliku “module.lua” jest tworzona w zakresie lokalnym, a odniesienie do tej tabeli jest zwracane użytkownikowi. Ponowne wczytanie pliku “module.lua” ocenia kod modułu ponownie, ale tworzy nową tabelę w zakresie lokalnym zamiast aktualizować tabelę m
.
Po drugie, Lua przechowuje w pamięci podręcznej załadowane pliki. Pierwszym razem, gdy plik jest wymagany, jest on dodawany do tabeli package.loaded
, aby można go było szybciej odczytywać podczas kolejnych wymagań. Aby wymusić ponowne odczytanie pliku z dysku, można ustawić wpis pliku na nil
: package.loaded["my_module"] = nil
.
Aby prawidłowo ponownie załadować moduł, musisz ponownie załadować moduł, zresetować pamięć podręczną i następnie ponownie załadować wszystkie pliki, które używają modułu. Jest to jednak dalekie od optymalnego.
Zamiast tego możesz rozważyć obejście do użycia w trakcie rozwoju programu: umieścić tabelę modułu w zakresie globalnym i spowodować, aby M
odnosiło się do globalnej tabeli, zamiast tworzyć nową tabelę za każdym razem, gdy plik jest używany ponownie. Ponowne wczytanie modułu zmienia zawartość globalnej tabeli:
--- module.lua
-- Zamiast tego użyj "local M = {}" w ostatecznej wersji
uniquevariable12345 = uniquevariable12345 or {}
local M = uniquevariable12345
M.value = 4711
return M
Moduły z zachowaniem stanu przechowują stan wewnętrzny, który jest współdzielony między wszystkimi użytkownikami modułu i można go porównać do singletonów:
local M = {}
-- wszyscy użytkownicy modułu będą współdzielić tę tabelę
local state = {}
function M.do_something(foobar)
table.insert(state, foobar)
end
return M
Z kolei moduł bezstanowy nie przechowuje wewnętrznego stanu. Zamiast tego dostarcza mechanizm do wyeksternalizowania stanu do osobnej tabeli lokalnej dla użytkownika modułu. Oto kilka różnych sposobów, aby to zaimplementować:
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
Użyj modułu w ten sposób:
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 jest dodawane jako pierwszy argument, gdy używa się notacji ":"
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" jest dodawane jako pierwszy argument przy użyciu notacji ":"
print(my_state:get_state()) --> 43
__index
, ale każde domknięcie zawiera własną kopię funkcji, co zwiększa zużycie pamięci.
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
Użyj modułu w ten sposób:
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