Read this manual in English

Адресація

Код, який контролює гру під час виконання, повинен мати змогу досягти будь-який обʼєкт або компонент щоб рухати, масштабувати, анімувати, видаляти та маніпулювати все що гравець бачить та чує. Механізм адресації Defold робить це можливим.

Ідентифікатори

Defold використовує адреси (або URL, але про це потім) щоб посилатися на обʼєкти та компоненти. Ці адреси складаються з ідентифікаторів. Далі наведені приклади того як Defold використовує адреси. В цьому посібнику ми детально розглянемо як вони працюють:

local id = factory.create("#enemy_factory")
label.set_text("my_gameobject#my_label", "Hello World!")

local pos = go.get_position("my_gameobject")
go.set_position(pos, "/level/stuff/other_gameobject")

msg.post("#", "hello_there")
local id = go.get_id(".")

Почнемо з простого прикладу. Уявіть що у вас є ігровий обʼєкт з одним компонентом-спрайтом. Також ви маєте компонент-скрипт, що контролює ігровий обʼєкт. Така структура в редакторі буде виглядати приблизно так:

bean в редакторі

Тепер ви хочете відключити спрайт коли гра починається, а пізніше показати його. Це без проблем робиться таким кодом в “controller.script”:

function init(self)
    msg.post("#body", "disable") -- <1>
end
  1. Не хвилюйтеся, якщо символ’#’ вас спантеличив. Ми повернемося до нього пізніше.

Цей код працюватиме, як і очікується. Коли гра починається, скрипт адресує спрайт за його ідентифікатором “body” та використовує цю адресу щоб відправити спрайту повідомлення “disable”. Це спеціальне повідомлення, і його ефект полягає в тому що спрайт сховає свою графіку. Схематично, структура виглядає так:

bean

Ідентифікатори в цій структурі довільні. Ми вирішили назвати ігровий обʼєкт - “bean”, його спрайт компонент - “body”, а компонент-скрипт, що контролює персонажа, назвали “controller”.

Якщо ви не оберете назву, за вас це зробить редактор. Коли ви створюєте новий обʼєкт або компонент, властивості Id буде автоматично призначене унікальне значення.

  • Ігрові обʼєкти автоматично отримують назву “go” разом із лічильником (“go2”, “go3” і т.д.).
  • Компоненти отрумають назву, яка відповідає типу компонента (“sprite”, “sprite2” і т.д.).

Якщо хочете, можете залишити ці автоматично згенеровані назви, але ми заохочуємо вас змінювати їх на описові імена.

Тепер давайте додамо ще один спрайт, та дамо квасолі щит:

bean

Новий компонент повинен мати уникальний ідентифікатор в межах ігрового обʼєкта. Якщо назвати його “body” тоді код скрипту не може визначити якому спрайту відправити повідомлення “disable”. Отож ми маємо обрати унікальний (та описовий) ідентифікатор “shield”. Тепер ми можемо вмикати та вимикати “body” та “shield” коли забажаємо.

bean

Якщо ви спробуєте використати один і той же ідентифікатор більше одного разу, редактор повідомить про це помилкою, тому, на практиці, це ніколи не проблема:

bean

А тепер давайте подивимося що станется, якщо ви додасте більше ігрових обʼєктів. Припустимо, що треба обʼєднати дві квасолі (“beans”) в маленьку команду. Ви вирішили один ігровий обʼєкт назвати “bean”, а інший - “buddy”. Окрім того, після деякого часу бездіяльності, “bean” має наказати свому другу “buddy” почати танцювати. Для цього треба відправити cвоє власне повідомлення “dance” від скрипту “controller”, що належить обʼєкту “bean”, до скрипту “controller”, який належить обʼєекту “buddy”:

bean

Хоча ми й маємо два різних компоненти названих “controller”, це не буде помилкою, оскільки кожен ігровий обʼєкт створює окремий контекст іменування.

Через те, що адресат повідомлення знаходиться за межами ігрового обʼєкта, який відправляє повідомлення (“bean”), в коді треба вказати який з “controller”-ів має отримати повідомлення. Потрібно вказати ідентифікатор ігрового обʼєкта разом із ідентифікатором компонента, якому належить отримати повідомлення. Повна адреса компонента буде "buddy#controller", і вона складається з двох окремих частин.

  • Першим вказано ідентифікатор ігрового обʼєкта, що містить бажаний компонент (“buddy”),
  • потім додається символ-роздільник обʼєктів та компонентів (“#”),
  • і, нарешті, ми вказуємо ідентифікатор бажаного компонента (“controller”).

Повертаючись до попереднього прикладу з єдиним обʼєктом, ми бачимо, що пропустивши ідентифікатор ігрового обʼєкта, код може адресувати компоненти в поточному ігровому обʼєкті.

Наприклад, "#body" позначає адрес компонента “body” в поточному ігровому обʼєкті. Такий код буде працювати у будь-якому ігровому обʼєкті, до тих пір, поки в ньому є компонент “body”.

Колекції

Колекції дозволяють створювати групи, або ієрархії, ігрових обʼєктів та повторно їх використовувати контрольованим чином. Колекції використовуються в якості шаблонів (або “прототипів”, або “prefabs”-ів) в редакторі коли ви наповнюєте свою гру змістом.

Наприклад, ви хочете створити велику кількість “bean/buddy” команд. Цього можна досягти створивши шаблон в новому файлі колекції (назвемо його “team.collection”). В цьому файлі треба побудувати ігрові обʼєкти команди та зберегти файл. Потім створити екземпляр змісту цього файла-колекції в вашій головній колекції (bootstrap collection) і надати новому екземпляру імʼя(наприклад, “team_1”):

bean

З такою структурою, ігровий обʼєкт “bean” все ще може посилатися на компонент “controller” в обʼєкті “buddy” за адресою "buddy#controller".

bean

А якщо ми додамо другий екземпляр “team.collection” (назвемо його “team_2”), то код, який виконується в скриптах “team_2” також буде працювати. Ігровий обʼєкт “bean”, що належить колекції “team_2”, все ще може адресувати компонетн “controller” в обʼєкті “buddy” за адресою "buddy#controller".

bean

Відносна адресація

Адреса "buddy#controller" працює для обох обʼєктів з обох колекцій тому що вона відносна. Обидві колекції “team_1” та “team_2” створюють новий контекст іменування, або “простір імен”. Defold враховує контекст іменування під час адресації, і таким чином уникає колізій в іменах:

відносний ідентифікатор

  • В контексті “team_1”, ігрові обʼєекти “bean” та “buddy” унікально ідентифіковані.
  • Так само, в контексті “team_2”, ігрові обʼєкти “bean” та “buddy” теж унікально ідентифіковані.

Під час вирішення фінальної адреси через відносну адресацію, поточний контекст іменування буде автоматично доданий в початок відносної адреси. Це дуже корисно, бо дозволяє вам створювати групи ігрових обʼєктів з кодом, та ефективно використовувати їх скрізь у вашому проєкті.

Скорочення

В Defold є два скорочення, які можна використовувати для відправки повідомлень, не вказуючи повний URL:

.
Скорочення для поточного ігрового обʼєкта.
#
Скорочення для поточного компонента.

Наприклад:

   -- Let this game object acquire input focus
   msg.post(".", "acquire_input_focus")
   -- Post "reset" to the current script
   msg.post("#", "reset")

Шляхи ігрових обʼєктів

Щоб зрозуміти механізм іменування, давайте подивимося що відбувається коли ви зберете та виконаєте проєкт:

  1. Редактор читає стартову колекцію (“main.collection”) та весь її зміст (ігрові обʼєкти та інші колекції).
  2. Компілятор створює ідентифікатор для кожного статичного ігрового обʼєкта. Ідентифікатори будуються як “шляхи”, що починаються в корені стартової колекції, та прямують ієрархією колекцій до самого обʼєекта. На кожному рівні додається символ ‘/’.

Для нашого прикладу вище, гра буде виконуватися із наступними чотирьма ігровими обʼєктами:

  • /team_1/bean
  • /team_1/buddy
  • /team_2/bean
  • /team_2/buddy

Ідентифікатори зберігаються як хешовані значення. Середовище виконання зберігає хеші ідентифікаторів кожної колекції, та використовує їх при перетворенні відносних адрес на хеші абсолютних.

Групування в колекції не існує під час виконання. Немає можливості дізнатися до якої колекції належав обʼєкт до компіляції. Виконувати дії над усіма обʼєктами в колекції водночас теж неможливо. Якщо ви потребуєте таку можливість, то вам доведеться самостійно відстежувати всі бажані обʼєкти в коді. Ідентифікатор кожного обʼєкта статичний, і він гарантовано буде незмінним на протязі всього життєвого циклу обʼєкта. Це означає що ви можете без ризику зберігати ідентифікатор обʼєкта та використовувати його у будь-який час.

Абсолютна адресація

Під час адресації, можна використовувати повні ідентифікатори. В більшості випадків краще використовувати відносну адресацію, тому що вона дозволяє повторно використовувати частини проєкту, але існують випадки коли абсолютна адресаціє буде необхідною.

Наприклад, якщо ви хочете створити менеджер ШІ, який відстежує стан кожного обʼєкта “bean”. Ви хочете щоб “bean”-и повідомляли свій активний статус менеджеру, а менеджер приймав тактичні рішення та віддавав накази “bean”-ам в залежності від їх статусу. Логічно було б створити єдиний ігровий обʼєкт-менеджер із скриптом, та покласти його поряд з колекціями команд (“team”) в стартовій колекції.

обʼєкт менеджер

Кожен “bean” тепер відповідає за відправку повідомлень менеджеру: “contact” якщо він подбачив ворога, або “ouch!” якщо він отримав ушкодження. Для цього скрипт “controller” в обʼєкті “bean” використовує абсолютну адресацію щоб віправляти повідомлення компоненту “controller” в обʼєкті “manager”.

Будь-яка адреса що починається з ‘/’ буде вирішуватись від кореня ігрового світу. А це відповідає кореню стартової колекції, яка завантажується на початку гри.

Абсолютною адресою скрипту менеджера буде "/manager#controller" і ця адреса буде вказувати на вірний компонент незалежно від того, де вона використовується.

команди та менеджер

абсолютна адресація

Хешовані ідентифікатори

Рушій зберігає всі ідентифікатори як хешовані значення. Усі функції, що приймають компонент або ігровий обʼєкт в якості аргументу, приймають рядок, хеш або URL обʼєкта. Вище ми вже побачили як використовувати рядки для адресації.

Коли ви отримуєте ідентифікатор ігрового обʼєкта, рушій завжди поверне хеш абсолютного шляху:

local my_id = go.get_id()
print(my_id) --> hash: [/path/to/the/object]

local spawned_id = factory.create("#some_factory")
print(spawned_id) --> hash: [/instance42]

Такий хеш можна використовувати замість рядка ідентифікатора, або можна зібрати самому. Зауважте, що хешований ідентифікатор відповідає шляху до обʼєкта, тобто повній адресі:

Відносні адреси мають надаватися рядками, тому що рушій буде обчислювати новий хеш ідентифікатора шляхом хешування заданого рядка разом з хешом поточного контексту іменування (колекції).

local spawned_id = factory.create("#some_factory")
local pos = vmath.vector3(100, 100, 0)
go.set_position(pos, spawned_id)

local other_id = hash("/path/to/the/object")
go.set_position(pos, other_id)

-- This will not work! Relative addresses must be given as strings.
local relative_id = hash("my_object")
go.set_position(pos, relative_id)

URL

Для повноти картини подивимося на загальний формат адрес в Defold: URL.

URL - це обʼєкт, зазвичай рядок, у спеціальному форматі. Звичайний URL складається з трьох частин:

[socket:][path][#fragment]

Сокет (socket)
Ідентифікує ігровий світ цілі. Він важливий коли ви працюєте з проксі колекцій (Collection Proxies) і ідентифікує в такому випадку динамічно завантажену колекцію.
Шлях (path)
Ця частина URL містить повний ідентифікатор цільового ігрового обʼєкта.
Фрагмент (fragment)
Ідентифікатор цільового компонента в межах вказаного ігрового обʼєкта.

Як ми вже бачили, частину, або навіть більшість, цієї інформації часто можна пропускати. Вам майже ніколи не треба вказувати сокет, і вам часто, але не завжди, треба вказувати шлях. Сокет потрібно вказати, якщо вам треба адресувати речі в іншому ігровому світі. Наприклад, повний URL до скрипту “controller” в ігровому обʼєкті “manager” буде:

"main:/manager#controller"

а до “controller”-а в “buddy” в “team_2” буде:

"main:/team_2/buddy#controller"

Ми можемо відправляти їм повідомлення:

-- Send "hello" to the manager script and team buddy bean
msg.post("main:/manager#controller", "hello_manager")
msg.post("main:/team_2/buddy#controller", "hello_buddy")

Будування обʼєктів URL

URL обʼєкти можна створювати програмно в Lua коді:

-- Construct URL object from a string:
local my_url = msg.url("main:/manager#controller")
print(my_url) --> url: [main:/manager#controller]
print(my_url.socket) --> 786443 (internal numeric value)
print(my_url.path) --> hash: [/manager]
print(my_url.fragment) --> hash: [controller]

-- Construct URL from parameters:
local my_url = msg.url("main", "/manager", "controller")
print(my_url) --> url: [main:/manager#controller]

-- Build from empty URL object:
local my_url = msg.url()
my_url.socket = "main" -- specify by valid name
my_url.path = hash("/manager") -- specify as string or hash
my_url.fragment = "controller" -- specify as string or hash

-- Post to target specified by URL
msg.post(my_url, "hello_manager!")