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
Передавання повідомлень — це механізм у Defold, який дає ігровим об’єктам змогу спілкуватися один з одним. Цей посібник передбачає, що ви знайомі з адресацією у Defold та базовими будівельними блоками.
У Defold не використовується об’єктно-орієнтований підхід у тому сенсі, що ваш застосунок складається з ієрархій класів з успадкуванням та функціями-членами в об’єктах (як, наприклад, у Java, C++ або C#). Натомість Defold доповнює Lua простим і потужним об’єктно-орієнтованим дизайном: стан об’єкта зберігається всередині компонентів-скриптів і доступний через посилання self. Крім того, об’єкти можна повністю відокремити один від одного, використовуючи асинхронне передавання повідомлень як засіб комунікації.
Спочатку розгляньмо декілька прикладів використання. Уявіть, що ви створюєте гру з такою структурою:

Вміст цього прикладу міститься у двох окремих файлах: один призначений для головної стартової колекції, а другий — для колекції “level”. Однак імена файлів не мають значення у Defold. Значення мають призначені ідентифікатори.
Гра складається з декількох простих механік, які потребують комунікації між об’єктами:

"punch" надсилається від скрипту, що належить “hero”, до скрипту, що належить “enemy”. Віддамо перевагу відносній адресації, тому що обидва об’єкти перебувають на одному рівні ієрархії колекції:
-- Send "punch" from the "hero" script to "enemy" script
msg.post("enemy#controller", "punch")
Усі удари в грі мають однакову силу, тому повідомлення не міститиме жодної додаткової інформації, крім своєї назви — “punch”.
У скрипті, що належить ворогу (enemy), створимо функцію, щоб отримати повідомлення:
function on_message(self, message_id, message, sender)
if message_id == hash("punch") then
self.health = self.health - 100
end
end
У цьому випадку код перевіряє лише назву повідомлення (надіслану як хешований рядок у параметрі message_id). Дані повідомлення та відправник не мають значення: будь-хто може надіслати повідомлення “punch” і завдати шкоди бідолашному ворогу.
"update_score" також надсилається від скрипту “hero” до компонента “gui”, що належить ігровому об’єкту “interface”.
-- Enemy defeated. Increase score counter by 100.
self.score = self.score + 100
msg.post("/interface#gui", "update_score", { score = self.score })
Написати відносну адресу в цьому випадку неможливо, тому що “interface” перебуває в корені ієрархії іменування, а “hero” — ні. Повідомлення надсилається компоненту GUI, до якого прикріплено скрипт, тому він може належно відреагувати. Повідомлення можна вільно надсилати між скриптами, скриптами GUI та скриптами рендерингу.
Повідомлення "update_score" містить дані про рахунок. Дані передаються як таблиця Lua в параметрі message:
function on_message(self, message_id, message, sender)
if message_id == hash("update_score") then
-- set the score counter to new score
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
end
end
"update_minimap" компоненту “gui” в ігровому об’єкті “interface”:
-- Send the current position to update the interface minimap
local pos = go.get_position()
msg.post("/interface#gui", "update_minimap", { position = pos })
Код скрипту GUI має стежити за позицією кожного ворога. Якщо той самий ворог надсилає нову позицію, стару позицію слід оновити. Відправника повідомлення (параметр sender) можна використовувати як ключ у таблиці Lua з позиціями:
function init(self)
self.minimap_positions = {}
end
local function update_minimap(self)
for url, pos in pairs(self.minimap_positions) do
-- update position on map
...
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("update_score") then
-- set the score counter to new score
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
elseif message_id == hash("update_minimap") then
-- update the minimap with new positions
self.minimap_positions[sender] = message.position
update_minimap(self)
end
end
Як ми вже побачили, механіка надсилання повідомлень дуже проста. Вам достатньо викликати функцію msg.post(), яка додає ваше повідомлення до черги. Потім кожного кадру рушій обробляє чергу та доставляє кожне повідомлення за вказаною адресою. Деякі системні повідомлення (наприклад, "enable", "disable", "set_parent" тощо) обробляються кодом рушія. Сам рушій також створює деякі системні повідомлення для ваших об’єктів (наприклад, "collision_response" під час фізичних колізій). Для обробки користувацьких повідомлень у компонентах-скриптах рушій викликає спеціальну функцію Defold Lua on_message().
Ви можете надсилати будь-які повідомлення будь-якому наявному об’єкту або компоненту, а код отримувача вирішує, чи відповідати на них. Якщо ви надіслали повідомлення, а скрипт отримувача проігнорував його, це нормально. За обробку повідомлень повністю відповідає отримувач.
Рушій перевіряє адресу повідомлення. Якщо ви намагаєтеся надіслати повідомлення невідомому отримувачу, Defold повідомить про помилку в консолі:
-- 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
Повна сигнатура функції msg.post() така:
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)
Наразі розмір таблиці параметра message суворо обмежено 2 кілобайтами. Простого способу визначити точний розмір таблиці немає, але можна викликати collectgarbage("count") до та після додавання таблиці, щоб відстежити використання пам’яті.
Defold надає два корисних скорочення для надсилання повідомлень без повного URL:
.#Наприклад:
-- Let this game object acquire input focus
msg.post(".", "acquire_input_focus")
-- Post "reset" to the current script
msg.post("#", "reset")
Щоб отримувати повідомлення, цільовий компонент-скрипт повинен містити функцію on_message(). Вона отримує чотири аргументи:
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
Якщо ви використовуєте компонент проксі колекції для завантаження нового світу в середовище виконання, вам може знадобитися надсилати повідомлення між світами. Наприклад, ви завантажили нову колекцію через проксі, а її властивість Name дорівнює “level”:

Щойно колекцію буде завантажено, ініціалізовано та активовано, ви зможете надсилати повідомлення будь-яким компонентам та об’єктам у новому світі. Для цього треба вказати ім’я ігрового світу в полі “socket” адреси отримувача:
-- Send a message to the player in the new game world
msg.post("level:/player#controller", "wake_up")
Докладніший опис роботи проксі наведено в посібнику з проксі колекцій.
Коли надіслане повідомлення дістається адресата, викликається функція отримувача on_message(). Код, що реагує на повідомлення, часто створює нові повідомлення, які додаються до черги.
Коли рушій починає обробляти чергу повідомлень (dispatching messages), він проходить нею й викликає on_message() отримувача кожного повідомлення. Це триває, доки черга не спорожніє. Якщо під час такого проходу до черги додано нові повідомлення, рушій проходить нею знову. Однак кількість спроб спорожнити чергу суворо обмежена. Це, своєю чергою, обмежує довжину ланцюгів повідомлень, які можна повністю обробити за поточний кадр. Наступний скрипт дає змогу перевірити, скільки проходів обробки черги рушій виконує між викликами update():
function init(self)
-- We’re starting a long message chain during object init
-- and keeps it running through a number of update() steps.
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
Виконання цього скрипту виведе приблизно таке:
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.
Бачимо, що ця версія рушія Defold виконує 10 проходів між викликом init() і першим викликом update(). Потім рушій виконує 75 проходів під час кожного оновлення.