Передача сообщений — это механизм, с помощью которого игровые объекты Defold взаимодействуют друг с другом. Предполагается, что пользователь уже имеет базовое понимание принципов адресации и основных структурных элементов Defold.
Defold не придерживается объектно-ориентированной парадигмы в том смысле, что пользователь определяет свое приложение, выстраивая иерархию классов с наследованием и функциями-членами в объектах (как в Java, C++ или C#). Вместо этого Defold расширяет Lua простым и мощным объектно-ориентированным дизайном, при котором состояние объекта хранится внутри компонентов скрипта и доступно через ссылку self
. Кроме того, объекты могут быть полностью изолированы друг от друга с возможностью асинхронной передачи сообщений в качестве средства связи между собой.
Давайте сначала рассмотрим несколько простых примеров использования. Предположим, вы разрабатываете игру, в составе которой:
Содержимое этого примера находится в двух отдельных файлах. Один файл для основной загрузочной коллекции и один для коллекции с идентификатором “level”. Однако в Defold имена файлов не имеют значения. Значение имеет идентификатор, который присваивается экземплярам.
Игра содержит несколько простых механик, которые подразумевают взаимодействие между объектами:
"punch"
отправляется из скрипта объекта “hero” в скрипт объекта “enemy”. Поскольку оба объекта находятся в одном и том же месте в иерархии коллекции, предпочтительна относительная адресация:
-- Отправить `"punch"` из скрипта объекта "hero" в скрипт объекта "enemy"
msg.post("enemy#controller", "punch")
В игре есть только один сильный удар, поэтому сообщение не должно содержать лишнюю информацию, лишь название — “punch”.
В скрипте объекта-врага необходимо создать функцию для получения сообщения:
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”.
-- Враг поражен. Увеличить счетчик очков на 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
-- выставить счетчик очков в новое значение
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
end
end
"update_minimap"
компоненту “gui” в игровом объекте “interface”:
-- Отправить текущую позицию для обновления интерфейса миникарты
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
-- обновить положение на карте
...
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("update_score") then
-- выставить счетчик очков в новое значение
local score_node = gui.get_node("score")
gui.set_text(score_node, "SCORE: " .. message.score)
elseif message_id == hash("update_minimap") then
-- обновить карту с учетом новых позиций
self.minimap_positions[sender] = message.position
update_minimap(self)
end
end
Как мы раннее убедились, техника отправки сообщения очень проста. Нужно вызвать функцию msg.post()
, которая помещает сообщение в очередь. Затем, в каждом кадре, движок просматривает очередь и доставляет каждое сообщение по целевому адресу. Некоторые системные сообщения (такие как "enable"
, "disable"
, "set_parent"
и др.) обрабатываются кодом движка. Движок также выдает некоторые системные сообщения (например, "collision_response"
при физических столкновениях), которые доставляются к объектам. Для пользовательских сообщений, посылаемых скриптам, движок просто вызывает специфичную для Defold Lua-функцию on_message()
.
Пользователь может посылать произвольные сообщения любому существующему объекту или компоненту, при этом ответ на сообщение зависит от кода на стороне получателя. Считается нормой ситуация, когда код скрипта игнорирует посланное ему сообщение. Ответственность за работу с сообщениями полностью лежит на принимающей стороне.
Движок проверит целевой адрес сообщения. Если попытаться отправить сообщение неизвестному получателю, Defold выведет в консоль сигнал об ошибке:
-- Попытаться отправить сообщение несуществующему объекту
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])
-- Отправить табличные данные, содержащие вложенную таблицу
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:
.
#
Пример:
-- Позволить игровому объекту получить фокус ввода
msg.post(".", "acquire_input_focus")
-- Передать "reset" текущему скрипту
msg.post("#", "reset")
Для получения сообщений необходимо убедиться, что целевой скрипт содержит функцию on_message()
. Функция принимает четыре параметра:
function on_message(self, message_id, message, sender)
self
message_id
message
sender
function 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
При использовании компонента Collection Proxy для загрузки нового игрового пространства в среду выполнения, возникает необходимость в передаче сообщений между игровыми пространствами. Предположим, что загружается коллекция посредством прокси и что свойство Name коллекции имеет значение “level”:
Как только коллекция будет загружена, инициирована и активирована, появляется возможность отправлять сообщения любому компоненту или объекту в новом игровом пространстве, указав имя этого пространства в поле “socket” адреса получателя:
-- Отправить сообщение игроку в новом игровом мире
msg.post("level:/player#controller", "wake_up")
Более подробное описание работы прокси можно найти в документации по прокси-коллекциям.
Когда в конечном счете происходит отправка сообщения, вызывается функцияon_message()
получателя. Часто код реакции посылает новые сообщения, добавляемые в очередь.
Когда движок запускает процедуру диспетчеризации (рассылки сообщений), он обрабатывает очередь сообщений и вызывает функцию on_message()
каждого получателя сообщения, и так до тех пор, пока очередь сообщений не станет пустой. Если в процессе диспетчеризации в очередь добавляются новые сообщения, то выполняется еще один проход. Однако существует жесткий предел количества попыток движка очистить очередь, что ограничивает длину цепочек сообщений, которые можно было бы ожидать как полностью обработанные в кадре. Есть простой способ проверить, сколько проходов диспетчеризации выполняет движок между каждым вызовом update()
с помощью следующего скрипта:
function init(self)
-- Запускаем длинную цепочку сообщений во время инициализации объекта
-- 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 проходов во время каждого последующего цикла обновления.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB