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

Адресація

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