This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
Przekazywanie wiadomości to mechanizm, dzięki któremu obiekty gry w Defold mogą komunikować się ze sobą. Ten podręcznik zakłada, że znasz podstawy mechanizmu adresowania i podstawowych elementów budujących grę.
Defold nie realizuje programowania obiektowego w tym sensie, że definiujesz aplikację przez budowanie hierarchii klas z dziedziczeniem i metodami składowymi w obiektach, jak w Javie, C++ czy C#. Zamiast tego Defold rozszerza Luę o prosty i skuteczny model zorientowany obiektowo, w którym stan obiektu jest przechowywany wewnętrznie w komponentach skryptowych i dostępny przez referencję self. Obiekty można też całkowicie odseparować, a do komunikacji między nimi używać asynchronicznego przekazywania wiadomości.
Najpierw przyjrzyjmy się kilku prostym przykładom użycia. Załóżmy, że tworzysz grę składającą się z:
"level"."level", która zawiera dwa obiekty gry: bohatera i przeciwnika.
Treść tego przykładu znajduje się w dwóch oddzielnych plikach. Jeden plik odpowiada głównej kolekcji bootstrapowej, a drugi kolekcji o identyfikatorze “level”. W Defold nazwy plików nie mają znaczenia. Liczy się tożsamość, jaką nadajesz instancjom.
W grze występuje kilka prostych mechanik wymagających komunikacji między obiektami:

"punch" jest wysyłana ze skryptu bohatera do skryptu przeciwnika. Ponieważ oba obiekty znajdują się w tej samej gałęzi hierarchii kolekcji, preferowane jest adresowanie względne:
-- Wysyłaj "punch" ze skryptu "hero" do skryptu "enemy"
msg.post("enemy#controller", "punch")
W grze jest tylko jeden wariant mocnego ciosu, więc wiadomość nie musi zawierać żadnych dodatkowych informacji poza nazwą “punch”.
W komponencie skryptowym przeciwnika tworzysz funkcję odbierającą wiadomość:
function on_message(self, message_id, message, sender)
if message_id == hash("punch") then
self.health = self.health - 100
end
end
W tym przypadku kod sprawdza tylko nazwę wiadomości, przekazywaną jako hashowany ciąg w parametrze message_id. Nie interesują go dane wiadomości ani nadawca - każdy, kto wyśle wiadomość “punch”, zada obrażenia biednemu przeciwnikowi.
"update_score" jest również wysyłana ze skryptu obiektu gry “hero” do komponentu GUI w obiekcie gry “interface”.
-- Przeciwnik pokonany. Zwiększ licznik punktów o 100.
self.score = self.score + 100
msg.post("/interface#gui", "update_score", { score = self.score })
W tym przypadku nie da się zapisać adresu względnego, ponieważ “interface” znajduje się w korzeniu hierarchii nazw, a “hero” nie. Wiadomość trafia do komponentu GUI z dołączonym skryptem, dzięki czemu może on odpowiednio zareagować. Wiadomości można swobodnie wysyłać między skryptami, skryptami GUI i skryptami do renderowania.
Wiadomość "update_score" jest powiązana z danymi o wyniku. Dane przekazywane są jako tabela Lua w parametrze message:
function on_message(self, message_id, message, sender)
if message_id == hash("update_score") then
-- Ustaw licznik punktów na nowy wynik
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
end
end
update_minimap do komponentu GUI w obiekcie gry “interface”:
-- Wyślij bieżącą pozycję, aby zaktualizować minimapę interfejsu
local pos = go.get_position()
msg.post("/interface#gui", "update_minimap", { position = pos })
Kod skryptu GUI musi śledzić pozycje każdego przeciwnika, a jeśli ten sam przeciwnik wyśle nową pozycję, poprzednia powinna zostać zastąpiona. Nadawca wiadomości, przekazany w parametrze sender, może służyć jako klucz tabeli Lua przechowującej pozycje:
function init(self)
self.minimap_positions = {}
end
local function update_minimap(self)
for url, pos in pairs(self.minimap_positions) do
-- Zaktualizuj pozycję na mapie
...
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("update_score") then
-- Ustaw licznik punktów na nowy wynik
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
elseif message_id == hash("update_minimap") then
-- Zaktualizuj minimapę o nowe pozycje
self.minimap_positions[sender] = message.position
update_minimap(self)
end
end
Mechanizm wysyłania wiadomości jest, jak już widzieliśmy, bardzo prosty. Wywołujesz funkcję msg.post(), która umieszcza wiadomość w kolejce wiadomości. Następnie silnik w każdej klatce przechodzi przez kolejkę i dostarcza każdą wiadomość do jej docelowego adresu. W przypadku niektórych wiadomości systemowych, takich jak "enable", "disable" czy "set_parent", wiadomość obsługuje kod silnika. Silnik generuje też własne wiadomości systemowe, takie jak "collision_response" przy kolizjach fizycznych, które są dostarczane do twoich obiektów. W przypadku wiadomości użytkownika wysyłanych do komponentów skryptowych silnik po prostu wywołuje specjalną funkcję Lua o nazwie on_message().
Możesz wysyłać dowolne wiadomości do dowolnego istniejącego obiektu lub komponentu, a to kod po stronie odbiorcy decyduje, jak na nie zareagować. Jeśli wyślesz wiadomość do komponentu skryptowego, który ją zignoruje, nic się nie stanie. Odpowiedzialność za obsługę wiadomości spoczywa całkowicie na odbiorcy.
Silnik sprawdza adres docelowy wiadomości. Jeśli spróbujesz wysłać wiadomość do nieznanego odbiorcy, Defold zgłosi błąd w konsoli:
-- Try to post to a non existing object
msg.post("dont_exist#script", "hello")
ERROR:GAMEOBJECT: Instance '/dont_exists' could not be found when dispatching message 'hello' sent from main:/my_object#script
Pełna sygnatura wywołania msg.post() to:
msg.post(receiver, message_id, [message])
-- Send table data containing a nested table
local inventory_table = { sword = true, shield = true, bow = true, arrows = 9 }
local stats = { score = 100, stars = 2, health = 4, inventory = inventory_table }
msg.post("other_object#script", "set_stats", stats)
Istnieje twardy limit rozmiaru tabeli przekazywanej w parametrze message. Limit ten wynosi 2 kilobajty. Obecnie nie ma prostego sposobu, aby ustalić dokładny rozmiar pamięci zajmowany przez tabelę, ale możesz użyć collectgarbage("count") przed i po wstawieniu tabeli, aby monitorować zużycie pamięci.
Defold udostępnia dwa wygodne skróty, których możesz użyć do wysyłania wiadomości bez podawania pełnego URL-a:
.#Na przykład:
-- Let this game object acquire input focus
msg.post(".", "acquire_input_focus")
-- Post "reset" to the current script
msg.post("#", "reset")
Odbieranie wiadomości sprowadza się do upewnienia się, że docelowy komponent skryptowy zawiera funkcję o nazwie on_message(). Funkcja przyjmuje cztery parametry:
function on_message(self, message_id, message, sender)
selfmessage_idmessagesenderfunction on_message(self, message_id, message, sender)
print(message_id) --> hash: [my_message_name]
pprint(message) --> {
--> score = 100,
--> value = "some string"
--> }
print(sender) --> url: [main:/my_object#script]
end
Jeśli używasz komponentu pełnomocnika kolekcji, aby załadować nowy świat gry do runtime, będziesz chciał wymieniać wiadomości między światami gry. Załóżmy, że załadowałeś kolekcję przez pełnomocnika, a właściwość Name tej kolekcji ma wartość “level”:

Gdy tylko kolekcja zostanie załadowana, zainicjalizowana i włączona, możesz wysyłać wiadomości do dowolnego komponentu lub obiektu w nowym świecie gry, podając nazwę świata gry w polu socket adresata:
-- Wyślij wiadomość do gracza w nowym świecie gry
msg.post("level:/player#controller", "wake_up")
Bardziej szczegółowy opis działania pełnomocników kolekcji znajdziesz w dokumentacji Collection Proxies.
Gdy wysłana wiadomość zostanie ostatecznie dostarczona, wywoływana jest funkcja on_message() odbiorcy. Dość często kod reakcji wysyła kolejne wiadomości, które trafiają do kolejki wiadomości.
Gdy silnik zaczyna rozsyłanie wiadomości, przechodzi przez kolejkę i wywołuje funkcję on_message() każdego odbiorcy, aż kolejka zostanie opróżniona. Jeśli podczas tego przebiegu do kolejki zostaną dodane nowe wiadomości, silnik wykona kolejny przebieg. Istnieje jednak twardy limit tego, ile razy silnik próbuje opróżnić kolejkę, co w praktyce ogranicza długość łańcuchów wiadomości, które można oczekiwać, że zostaną w pełni rozesłane w ramach jednej klatki. Możesz łatwo sprawdzić, ile przebiegów rozsyłania wykonuje silnik między kolejnymi wywołaniami update(), używając poniższego skryptu:
function init(self)
-- Zaczynamy długi łańcuch wiadomości podczas inicjalizacji obiektu
-- i utrzymujemy go przez kilka kroków update().
print("INIT")
msg.post("#", "msg")
self.updates = 0
self.count = 0
end
function update(self, dt)
if self.updates < 5 then
self.updates = self.updates + 1
print("UPDATE " .. self.updates)
print(self.count .. " dispatch passes before this update.")
self.count = 0
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("msg") then
self.count = self.count + 1
msg.post("#", "msg")
end
end
Uruchomienie tego skryptu wypisze coś w rodzaju:
DEBUG:SCRIPT: INIT
INFO:ENGINE: Defold Engine 1.2.36 (5b5af21)
DEBUG:SCRIPT: UPDATE 1
DEBUG:SCRIPT: 10 dispatch passes before this update.
DEBUG:SCRIPT: UPDATE 2
DEBUG:SCRIPT: 75 dispatch passes before this update.
DEBUG:SCRIPT: UPDATE 3
DEBUG:SCRIPT: 75 dispatch passes before this update.
DEBUG:SCRIPT: UPDATE 4
DEBUG:SCRIPT: 75 dispatch passes before this update.
DEBUG:SCRIPT: UPDATE 5
DEBUG:SCRIPT: 75 dispatch passes before this update.
Widzimy, że ta konkretna wersja silnika Defold wykonuje 10 przebiegów rozsyłania wiadomości między init() a pierwszym wywołaniem update(). Następnie wykonuje 75 przebiegów podczas każdej kolejnej pętli aktualizacji.