Komponent typu skrypt (ang. script) pozwala na tworzenie logiki gry przy użyciu języka programowania Lua. Skrypty dodawane są do obiektów gry dokładnie tak samo jak każdy inny komponent, a Defold wykona kod Lua w ramach funkcji cyklu życia silnika.
W Defoldzie występują trzy rodzaje skryptów Lua, z dostępem do różnych bibliotek Defolda.
Defold wykonuje skrypty Lua jako część cyklu życia silnika i ujawnia cykl życia przez zestaw predefiniowanych funkcji wywołania zwrotnego. Gdy dodasz komponent skryptu do obiektu gry, skrypt staje się częścią cyklu życia obiektu gry i jego komponentu (lub komponentów). Skrypt jest oceniany w kontekście Lua, gdy jest wczytywany, a następnie silnik wykonuje następujące funkcje i przekazuje odniesienie do bieżącej instancji komponentu skryptu. Możesz użyć tego odniesienia “self” do przechowywania stanu w instancji komponentu.
::: ważne “Self” to obiekt typu userdata, który działa jak tabela Lua, ale nie można go przeglądać za pomocą pairs() ani ipairs(), ani drukować za pomocą pprint(). :::
function init(self)
-- These variables are available through the lifetime of the component instance
self.my_var = "something"
self.age = 0
end
```lua function final(self) if self.my_var == “something” then – wykonaj jakiś kod end end
function update(self, dt)
self.age = self.age + dt -- increase age with the timestep
end
function fixed_update(self, dt)
msg.post("#co", "apply_force", {force = vmath.vector3(1, 0, 0), position = go.get_world_position()})
end
function on_message(self, message_id, message, sender)
if message_id == hash("increase_score") then
self.total_score = self.total_score + message.score
end
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
print("Touch", action.x, action.y)
end
end
function on_reload(self)
print(self.age) -- wyświetl wartość zmiennej self.age
end
Obiekt gry z komponentem skryptu implementuje pewną logikę. Często zależy ona od pewnego czynnika zewnętrznego. Przykładowo sztuczna inteligencja przeciwnika może reagować na gracza znajdującego się w określonym promieniu od przeciwnika, drzwi mogą odblokować się i otworzyć w wyniku interakcji gracza itp.
Funkcja update()
pozwala na implementację złożonych zachowań zdefiniowanych jako automat stanów, który działa w każdej klatce - czasami jest to odpowiednie podejście. Jednak z każdym wywołaniem update()
wiąże się pewien koszt. Jeśli naprawdę nie potrzebujesz tej funkcji, powinieneś ją usunąć i spróbować budować swoją logikę w sposób reaktywny. Oczekiwanie biernie na jakąś wiadomość, która wyzwoli reakcję, jest “tańsze” niż aktywne przeszukiwanie świata gry w poszukiwaniu danych do analizy. Ponadto rozwiązanie danego problemu w sposób reaktywny zazwyczaj prowadzi do czystszego i bardziej stabilnego projektu i jego implementacji.
Przyjrzyjmy się konkretnemu przykładowi. Załóżmy, że chcesz, aby komponent skryptu wysłał wiadomość 2 sekundy po inicjalizacji. Następnie powinien oczekiwać na określoną wiadomość odpowiedzi i po jej otrzymaniu, wysłać inną wiadomość 5 sekund później. Niereaktywny kod dla tego wyglądałby mniej więcej tak:
function init(self)
-- Licznik czasu.
self.counter = 0
-- Potrzebujemy tego do śledzenia naszego stanu.
self.state = "first"
end
function update(self, dt)
self.counter = self.counter + dt
if self.counter >= 2.0 and self.state == "first" then
-- wysłanie wiadomości po 2 sekundach
msg.post("some_object", "some_message")
self.state = "waiting"
end
if self.counter >= 5.0 and self.state == "second" then
-- wysłanie wiadomości 5 sekund po otrzymaniu "response"
msg.post("another_object", "another_message")
-- Wyczyszczenie stanu, aby nie wykonać tego bloku stanu ponownie.
self.state = nil
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- Stan "pierwszy" zakończony, wchodzi kolejny
self.state = "second"
-- Wyzerowanie licznika
self.counter = 0
end
end
Even in this quite simple case we get fairly tangled up logic. It’s possible to make this look better with the help of coroutines in a module (see below), but let’s instead try to make this reactive and use a built-in timing mechanism.
local function send_first()
msg.post("some_object", "some_message")
end
function init(self)
-- Poczekaj 2 s, a następnie wywołaj send_first()
timer.delay(2, false, send_first)
end
local function send_second()
msg.post("another_object", "another_message")
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- Poczekaj 5 s, a następnie wywołaj send_second()
timer.delay(5, false, send_second)
end
end
To jest bardziej przejrzyste i łatwiejsze do śledzenia. Pozbywamy się wewnętrznych zmiennych stanu, które często są trudne do śledzenia przez logikę - i które mogą prowadzić do subtelnych błędów. Dodatkowo całkowicie rezygnujemy z funkcji update()
. Zwalnia to silnik z wywoływania naszego skryptu 60 razy na sekundę.
Przetwarzanie wstępne, czyli preprocessing wykrozystuje preprocesor Lua i specjalne znaczniki, aby warunkowo dołączać kod w zależności od wariantu budowy. Przykład:
-- Użyj jednego z tych słów kluczowych: RELEASE, DEBUG lub HEADLESS
--#IF DEBUG
local lives_num = 999
--#ELSE
local lives_num = 3
--#ENDIF
Preprocesor jest dostępny jako rozszerzenie budowania. Dowiedz się więcej na temat sposobu instalacji i użycia na stronie rozszerzenia na GitHubie.
Edytor Defold obsługuje edycję skryptów Lua z kolorowaniem składni i autouzupełnianiem. Aby wyświetlić nazwy funkcji Defold, naciśnij Ctrl+Spacja, aby wyświetlić listę funkcji pasujących do tego, co wpisujesz.