Manuals
Manuals




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

Адресація

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

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

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

local id = factory.create("#enemy_factory")
go.set("my_gameobject#my_label", "text", "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(".")

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

квасоля в редакторі

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

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

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

квасоля

Ідентифікатори в цій структурі визначає розробник, і вони мають бути унікальними в межах свого контексту іменування. Ми вирішили назвати ігровий об’єкт “bean”, його компонент-спрайт — “body”, а компонент-скрипт, що керує персонажем, — “controller”. Ідентифікатори, які використовуються в рядкових URL-адресах, не повинні містити : або #, оскільки синтаксис URL резервує : як роздільник сокета, а # — як роздільник ігрового об’єкта й компонента. Інші знаки пунктуації синтаксичний аналізатор URL не відхиляє.

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

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

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

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

квасоля

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

квасоля

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

квасоля

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

квасоля

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

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

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

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

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

Колекції

Колекції (collection) дають змогу створювати групи або ієрархії ігрових об’єктів і повторно використовувати їх контрольованим чином. У редакторі файли колекцій використовуються як шаблони (або «прототипи», або «префаби»), коли ви наповнюєте гру вмістом.

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

квасоля

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

квасоля

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

квасоля

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

Адреса "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” мають повідомляти менеджеру свій активний статус, а менеджер — приймати тактичні рішення та віддавати їм накази залежно від цього статусу. Логічно створити єдиний ігровий об’єкт-менеджер зі скриптом і розмістити його поруч із колекціями команд (“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!")