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 від 1.12.0. У версії 1.12.0 було внесено зміни до життєвого циклу та додано нову функцію late_update().

Огляд життєвого циклу

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

Застосунок починає роботу з ініціалізації всього необхідного для роботи рушія. Він завантажує головну колекцію (collection) і викликає init() для всіх завантажених компонентів (component), які мають функцію Lua init() (компонентів-скриптів і компонентів GUI зі скриптами GUI). Це дає змогу виконати власну ініціалізацію.

Потім застосунок входить у цикл оновлення, де проведе більшу частину свого часу існування. У кожному кадрі оновлюються ігрові об’єкти (game object) та їхні компоненти. Викликаються всі функції update() у скриптах і скриптах GUI. Під час циклу оновлення повідомлення доставляються отримувачам, відтворюються звуки та виконується рендеринг усієї графіки.

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

Для наочності кроки проходу «доставлення повідомлень» показано на окремій діаграмі в кінці цього посібника, а на діаграмах їх позначено невеликою піктограмою «конверт зі стрілкою» 📩.

Ініціалізація

Саме тут починається ваша гра — це перший крок її виконання. Його можна розділити на 3 етапи:

Ініціалізація

Попередня ініціалізація

На етапі попередньої ініціалізації (Preinitialization) рушій виконує багато кроків, перш ніж завантажити головну, або стартову, колекцію (bootstrap collection). Налаштовуються профайлер пам’яті, сокети, графіка, HID (пристрої введення), звук, фізика та багато іншого. Також завантажується та налаштовується конфігурація застосунку (game.project).

Попередня ініціалізація

Перша точка входу, якою може керувати користувач, — це виклик функції init() поточного скрипту рендерингу наприкінці ініціалізації рушія.

Потім завантажується та ініціалізується головна колекція.

Ініціалізація колекції

На етапі ініціалізації колекції (Collection Init) усі ігрові об’єкти в колекції застосовують свої трансформації до дочірніх об’єктів: переміщення (зміну позиції), поворот і масштабування. Потім викликаються всі наявні функції init() компонентів.

Ініціалізація колекції

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

Дії після оновлення під час ініціалізації

Потім рушій виконує повний прохід дій після оновлення (Post Update) — такий самий, який надалі виконується після кожного кроку циклу оновлення (Update Loop). Він виконується наприкінці ініціалізації, оскільки ваш код init() може надсилати нові повідомлення, наказувати фабрикам (factory) створювати нові об’єкти, позначати об’єкти для видалення та виконувати інші дії.

Дії після оновлення

Цей прохід виконує доставлення повідомлень, фактичне створення ігрових об’єктів фабриками та видалення об’єктів. Зверніть увагу, що прохід Post Update містить послідовність «доставлення повідомлень», яка не лише доставляє повідомлення з черги, а й обробляє повідомлення, надіслані проксі колекцій (collection proxy). Під час цих кроків виконуються всі подальші оновлення проксі (увімкнення, вимкнення, ініціалізація, фіналізація, завантаження та позначення для вивантаження).

Цілком можливо завантажити проксі колекції під час init(), забезпечити ініціалізацію всіх об’єктів, які вона містить, а потім вивантажити колекцію через проксі — усе це до першого виклику update() компонента, тобто перш ніж рушій завершить етап ініціалізації та ввійде в цикл оновлення:

function init(self)
    print("init()")
    msg.post("#collectionproxy", "load")
end

function update(self, dt)
    -- The proxy collection is unloaded before this code is reached.
    print("update()")
end

function on_message(self, message_id, message, sender)
    if message_id == hash("proxy_loaded") then
        print("proxy_loaded. Init, enable and then unload.")
        msg.post("#collectionproxy", "init")
        msg.post("#collectionproxy", "enable")
        msg.post("#collectionproxy", "unload")
        -- The proxy collection objects’ init() and final() functions
        -- are called before we reach this object’s update()
    end
end

Цикл оновлення

Цикл оновлення (Update Loop) виконує визначену послідовність дій один раз за кадр. Цю послідовність можна поділити на 5 основних етапів:

Цикл оновлення

  1. Введення (опрацювання та обробка)
  2. Оновлення (включно з оновленням із фіксованим кроком, звичайним і пізнім оновленням та оновленням компонентів рушія)
  3. Оновлення рендерингу
  4. Дії після оновлення (вивантаження проксі колекцій, створення та видалення ігрових об’єктів)
  5. Рендеринг кадру (виконується рендеринг остаточної графіки)

Етап введення

Введення зчитується з доступних пристроїв, зіставляється з прив’язками введення, а потім передається далі. Кожен ігровий об’єкт, який отримав фокус введення, отримує введення у функціях on_input() усіх своїх компонентів. Ігровий об’єкт із компонентом-скриптом і компонентом GUI зі скриптом GUI отримуватиме введення у функціях on_input() обох компонентів — за умови, що ці функції визначено та компоненти отримали фокус введення.

Етап введення

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

Етап оновлення

Етап оновлення (Update) є частиною циклу оновлення (Update Loop). Він запускається один раз для кореневої колекції, а потім рекурсивно виконується для кожного ввімкненого проксі колекції.

У межах колекції Defold обробляє зворотні виклики за типом компонента: перебирає всі екземпляри (instance) типу компонента, який реалізує відповідний етап, викликає зворотний виклик Lua для кожного екземпляра, доставляє повідомлення з черги, а потім переходить до наступного типу компонента.

Загальний порядок етапів зворотних викликів Lua для компонентів-скриптів такий:

  1. fixed_update() — викликається 0..N разів за кадр (якщо використовується фіксований крок часу)
  2. update() — викликається 1 раз за кадр
  3. late_update() — викликається 1 раз за кадр

Етап оновлення

Виконується обхід кожного компонента ігрових об’єктів у головній колекції. Якщо будь-який із цих компонентів має скрипт із функцією fixed_update()/update()/late_update(), її буде викликано. Якщо компонент є проксі колекції, кожен компонент у колекції проксі оновлюється рекурсивно з виконанням усіх кроків етапу Update.

Порядок виклику функцій update() компонентів ігрових об’єктів не визначено. Не слід припускати, що рушій оновлює об’єкти однієї колекції в певному порядку. Те саме стосується fixed_update() і late_update() (починаючи з 1.12.0).

Фізика

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

Якщо для симуляції фізики використовується фіксований крок часу, у всіх компонентах-скриптах також може викликатися функція fixed_update(). Ця функція корисна в іграх, що ґрунтуються на фізиці, коли ви хочете змінювати фізичні об’єкти через регулярні проміжки часу, щоб забезпечити стабільну симуляцію фізики.

Трансформації

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

Наприкінці циклу оновлення (Update Loop) за потреби виконується ще одне остаточне оновлення трансформацій.

Етап оновлення рушія (без оновлень із фіксованим кроком)

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

  • fixed_update() виконується перед update()
  • late_update() виконується після update()
  • надіслані повідомлення доставляються між оновленнями типів компонентів, а також між етапами зворотних викликів скриптів

Якщо Use Fixed Timestep має значення false та/або Fixed Update Frequency дорівнює 0, на початку етапу готується dt, а далі виконання відбувається за наведеною нижче таблицею:

:::sidenote Зверніть увагу, що після оновлення кожного типу компонента доставляються всі повідомлення — для наочності це не позначено в таблиці нижче. :::

Крок Етап рушія Зворотний виклик Lua Коментар
1 Оновлення update() Викликається один раз за кадр для кожного типу компонента, який реалізує оновлення, у внутрішньому порядку пріоритетів. Крім того, тут як окремий тип компонента оновлюються анімації властивостей ігрових об’єктів, запущені за допомогою go.animate(). Тут оновлюються компоненти фізики. Для кожного ввімкненого проксі колекції весь етап Update викликається рекурсивно з кроку 1.
2 Пізнє оновлення late_update() Викликається один раз за кадр для кожного типу компонента, який реалізує пізнє оновлення, у внутрішньому порядку пріоритетів.
3 Трансформації   Наприкінці за потреби виконується ще одне остаточне оновлення трансформацій для кожного компонента.

Етап оновлення рушія з фіксованим кроком часу

Якщо Use Fixed Timestep має значення true, а Fixed Update Frequency не дорівнює нулю, на початку етапу готуються dt (проміжок часу), fixed_dt і num_fixed_steps (0..N) — кількість викликів оновлення з фіксованим кроком, яка визначається за часом від останнього оновлення, щоб забезпечити фіксовану кількість оновлень.

:::sidenote Зверніть увагу, що після оновлення кожного типу компонента доставляються всі повідомлення — для наочності це не позначено в таблиці нижче. :::

Потім виконується цикл:

Крок Етап рушія Зворотний виклик Lua Коментар
1 Оновлення з фіксованим кроком fixed_update() Викликається 0..N разів за кадр залежно від часу для кожного типу компонента, який реалізує оновлення з фіксованим кроком, у внутрішньому порядку пріоритетів. Включає кроки оновлення з фіксованим кроком для компонентів фізики.
2 Оновлення update() Викликається один раз за кадр для кожного типу компонента, який реалізує оновлення, у внутрішньому порядку пріоритетів. Крім того, тут як окремий тип компонента оновлюються анімації властивостей ігрових об’єктів, запущені за допомогою go.animate(). Для кожного ввімкненого проксі колекції етап Update викликається рекурсивно з кроку 1.
3 Пізнє оновлення late_update() Викликається один раз за кадр для кожного типу компонента, який реалізує пізнє оновлення, у внутрішньому порядку пріоритетів.
4 Трансформації   Наприкінці за потреби виконується ще одне остаточне оновлення трансформацій для кожного компонента.

Якщо вам знадобляться докладніші відомості про внутрішню роботу Defold під час етапу оновлення, варто прочитати сам код gameobject.cpp.

Етап оновлення рендерингу

Блок оновлення рендерингу спочатку доставляє всі повідомлення, надіслані в сокет @render (наприклад, повідомлення set_view_projection компонента камери, повідомлення set_clear_color тощо). Потім викликається update() скрипту рендерингу.

Етап оновлення рендерингу

Етап дій після оновлення

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

Етап дій після оновлення

Далі кожен компонент-фабрика, якому було наказано створити ігровий об’єкт, виконує це. Насамкінець ігрові об’єкти, позначені для видалення, фактично видаляються.

Етап рендерингу

Останній крок циклу оновлення передбачає доставлення повідомлень @system (повідомлення exit, reboot, перемикання профайлера, запуск і зупинка захоплення відео тощо).

Етап рендерингу

Потім виконується рендеринг графіки, а також візуального профайлера (див. документацію з налагодження). Після рендерингу графіки виконується захоплення відео.

Частота кадрів і крок часу колекції

Кількість оновлень кадру за секунду (яка дорівнює кількості виконань циклу оновлення за секунду) можна встановити в налаштуваннях проєкту або програмно, надіславши повідомлення set_update_frequency у сокет @system. Крім того, можна окремо встановити крок часу для кожного проксі колекції, надіславши йому повідомлення set_time_step. Зміна кроку часу колекції не впливає на частоту кадрів. Вона впливає на крок часу оновлення фізики, а також на змінну dt, що передається в update(). Також зауважте, що зміна кроку часу не змінює кількість викликів update() у кожному кадрі — ця функція завжди викликається рівно один раз.

(Докладніше див. у посібнику з проксі колекцій та описі set_time_step)

Обмеження активності рушія

У Defold 1.12.0 з’явився API обмеження активності рушія, який може повністю пропускати оновлення рушія та рендеринг, продовжуючи виявляти введення. Будь-яке введення знову активує рушій, а після періоду очікування рушій може знову перейти в режим обмеження активності.

Докладні відомості та приклади використання див. в описі API sys.set_engine_throttle().

Фіналізація

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

Після цього рушій переходить до послідовності фіналізації, яка обробляє головну колекцію та її об’єкти:

Фіналізація

Спочатку викликаються функції final() компонентів. Далі відбувається доставлення повідомлень. Насамкінець усі ігрові об’єкти видаляються, а головна колекція вивантажується.

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

Тепер застосунок повністю завершив роботу.

Доставлення повідомлень

Доставлення повідомлень — це спеціальний прохід, який виконується після оновлення кожного типу компонента, наприклад після оновлення спрайтів, оновлення скриптів, а також після будь-якої іншої дії, що може надсилати повідомлення. Під час його виконання доставляються всі надіслані повідомлення, накопичені в черзі. На діаграмах ці проходи позначено невеликими піктограмами «конверт зі стрілкою» 📩.

Доставлення повідомлень

Після доставлення всіх користувацьких повідомлень через виклики on_message() для кожного компонента спеціальні повідомлення Defold обробляються для кожного проксі колекції в такому порядку (також показаному на діаграмі):

  1. Повідомлення load — завантажують проксі колекцій, позначені для завантаження, і надсилають у відповідь повідомлення proxy_loaded.
  2. Повідомлення unload — вивантажують проксі колекцій, позначені для вивантаження, і надсилають у відповідь повідомлення proxy_unloaded.
  3. Повідомлення init — запускають етап Collection Init для всіх проксі колекцій, які потрібно ініціалізувати.
  4. Повідомлення final — викликають final() для всіх компонентів проксі, позначеного для фіналізації.
  5. Повідомлення enable — вмикають проксі колекції, щоб цикл оновлення (Update Loop) виконувався для нього в наступному кадрі; це неявно викликає init() для кожного компонента колекції.
  6. Повідомлення disable — вимикають проксі колекції, щоб цикл оновлення (Update Loop) не виконувався для нього в наступному кадрі; виконання Update Loop для нього повністю припиняється.

Оскільки код on_message() будь-якого компонента-отримувача може надсилати додаткові повідомлення, диспетчер повідомлень продовжує рекурсивно доставляти надіслані повідомлення, доки черга не спорожніє. Однак кількість проходів диспетчера через чергу повідомлень обмежена. Докладніше див. у розділі Ланцюги повідомлень.