This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
У цьому уроці ми почнемо з порожнього проєкту й створимо повноцінну гру-раннер з анімованим персонажем, фізичними колізіями, збиранням предметів і підрахунком очок.
Під час знайомства з новим ігровим рушієм потрібно засвоїти чимало інформації, тому ми створили цей урок, щоб допомогти вам розпочати. Це досить докладний урок, що пояснює роботу рушія й редактора. Ми припускаємо, що ви вже трохи знайомі з програмуванням.
Якщо вам потрібне знайомство з програмуванням мовою Lua, перегляньте наш посібник «Lua в Defold».
Якщо цей урок здається вам заскладним для початку, перегляньте нашу сторінку уроків, де зібрано уроки різної складності.
Якщо ви віддаєте перевагу відеоурокам, перегляньте відеоверсію на Youtube.
Ми використовуємо ігрові ресурси з двох інших уроків з невеликими змінами. Урок поділено на кілька кроків, кожен з яких суттєво наближає нас до готової гри.
У результаті ви отримаєте гру, у якій керуватимете героєм, що біжить ігровим світом, збирає монети й уникає перешкод. Герой біжить зі сталою швидкістю, а гравець керує лише його стрибками, натискаючи одну кнопку (або торкаючись екрана на мобільному пристрої). Рівень складається з нескінченного потоку платформ, на які можна стрибати, і монет, які можна збирати.
Якщо на будь-якому етапі цього уроку або під час створення власної гри ви натрапите на труднощі, не соромтеся звернутися по допомогу на форумі Defold. На форумі ви можете обговорювати Defold, звертатися по допомогу до команди Defold, дізнаватися, як інші розробники ігор розв’язували свої проблеми, і знаходити нове натхнення. Долучайтеся вже зараз.
Упродовж уроку докладні описи понять і способів виконання окремих дій позначено так само, як цей абзац. Якщо ці розділи здаються вам надто докладними, можете їх пропускати.
Тож почнімо. Сподіваємося, цей урок буде для вас цікавим і допоможе розпочати роботу з Defold.
Завантажте ресурси для цього уроку тут.
Спочатку завантажте ці файли.
Якщо ви ще не завантажили й не встановили редактор Defold, саме час це зробити:
Перейдіть на сторінку завантаження Defold, де ви знайдете кнопки Download для macOS, Windows і Linux (Ubuntu):

Щоб запустити редактор, відкрийте папку «Applications» і двічі клацніть файл «Defold».

D:\Defold). Не переміщуйте Defold у C:\Program Files (x86)\ або C:\Program Files\, оскільки це завадить редактору оновлюватися.Щоб запустити редактор, відкрийте папку «Defold» і двічі клацніть файл «Defold.exe».

У терміналі знайдіть файл архіву «Defold-x86_64-linux.zip» і розпакуйте його в цільовий каталог «Defold».
$ unzip Defold-x86_64-linux.zip -d Defold
Щоб запустити редактор, перейдіть до каталогу, у який ви розпакували застосунок, і запустіть виконуваний файл Defold або двічі клацніть його в графічному середовищі.
$ cd Defold
$ ./Defold
У меню Help > Create Desktop Entry є засіб створення ярлика застосунку.
Якщо виникнуть проблеми із запуском редактора, відкриттям проєкту або запуском гри Defold, зверніться до розділу поширених запитань про Linux.
Усі бета-версії та стабільні версії Defold також доступні на GitHub.
Коли редактор встановлено й запущено, час створити новий проєкт і підготувати його до роботи. Створіть новий проєкт із шаблону “Empty Project”.
У цьому уроці використовуються можливості Spine з розширення Spine. Додайте розширення до розділу залежностей у game.project.
Під час першого запуску редактор відкривається порожнім, без відкритого проєкту, тож виберіть у меню Open Project і вкажіть щойно створений проєкт. Вам також буде запропоновано створити «гілку» для проєкту.
Тепер на панелі Assets pane ви побачите всі файли, що належать до проєкту. Якщо двічі клацнути файл “main/main.collection”, він відкриється в центральній області редактора:

Редактор складається з таких основних областей:
print() і pprint() зі скриптів. Якщо застосунок або гра не запускається, насамперед перевірте консоль. Поруч із консоллю є вкладки з інформацією про помилки та редактором кривих, який використовується під час створення ефектів частинок.Шаблон проєкту “Empty” справді повністю порожній. Утім, виберіть Project ▸ Build, щоб зібрати проєкт і запустити гру.

Чорний екран, мабуть, не дуже захоплює, але це запущений ігровий застосунок Defold, який можна легко перетворити на щось цікавіше. Тож зробімо це.
Редактор Defold працює з файлами. Двічі клацнувши файл на панелі Assets pane, ви відкриваєте його у відповідному редакторі. Після цього можна працювати з вмістом файлу.
Завершивши редагування файлу, збережіть його. Виберіть File ▸ Save у головному меню. Редактор нагадує про незбережені зміни, додаючи зірочку ‘*’ до імені файлу на його вкладці.

Перш ніж почати, задаймо кілька налаштувань проєкту. Відкрийте ресурс game.project на панелі Assets Pane і прокрутіть до розділу Display. Установіть width і height проєкту в 1280 і 720 відповідно.
Також потрібно додати до проєкту розширення Spine, щоб анімувати героя. Додайте версію розширення Spine, сумісну зі встановленою у вас версією редактора Defold. Доступні версії Spine можна переглянути тут:
https://github.com/defold/extension-spine/releases
Клацніть правою кнопкою миші посилання на zip-файл випуску, який хочете використати:

Додайте посилання на випуск до списку залежностей game.project. Після додавання розширення Spine потрібно також перезапустити редактор, щоб активувати інтеграцію з редактором, яка входить до розширення Spine.
Зробімо перші невеликі кроки й створімо майданчик для нашого персонажа, а точніше — ділянку землі, що прокручується. Це займе кілька кроків.
Atlas — це файл, який об’єднує набір окремих зображень в одне більше зображення. Це робиться для економії місця й підвищення продуктивності. Докладніше про атласи та інші можливості 2D-графіки читайте в документації з 2D-графіки.


Чому це не працює!? Поширена проблема тих, хто починає працювати з Defold, — забувати зберігати файли! Після додавання зображень до атласу потрібно зберегти файл, щоб ці зображення стали доступними.
Створіть файл колекції (collection) ground.collection для землі й додайте до нього 7 ігрових об’єктів (game objects): клацніть правою кнопкою миші кореневий вузол колекції на панелі Outline і виберіть Add Game Object. Назвіть об’єкти “ground0”, “ground1”, “ground2” тощо, змінивши властивість Id на панелі Properties. Зверніть увагу, що Defold автоматично призначає новим ігровим об’єктам унікальні ідентифікатори.
До кожного об’єкта додайте компонент (component) спрайта: клацніть правою кнопкою миші ігровий об’єкт на панелі Outline і виберіть Add Component, потім виберіть Sprite та натисніть OK. Установіть для властивості Image компонента спрайта щойно створений атлас, а як стандартну анімацію спрайта виберіть одне з двох зображень землі. Установіть позицію X компонента спрайта (а не ігрового об’єкта) в 190, а позицію Y — у 40. Оскільки ширина зображення становить 380 пікселів і ми зміщуємо його вбік на половину цієї відстані, опорна точка ігрового об’єкта опиниться на лівому краї зображення спрайта.


Мабуть, найпростіше створити один повністю налаштований ігровий об’єкт із потрібним масштабом і компонентом спрайта, а потім скопіювати його. Виберіть його на панелі Outline, потім виберіть Edit ▸ Copy, а далі — Edit ▸ Paste.
Якщо вам потрібні більші або менші плитки, достатньо змінити масштаб. Однак у такому разі також потрібно змінити позиції X усіх ігрових об’єктів землі на значення, кратні новій ширині.
Збережіть файл, а потім додайте ground.collection до файлу main.collection: спочатку двічі клацніть файл main.collection, потім клацніть правою кнопкою миші кореневий об’єкт на панелі Outline і виберіть Add Collection From File. У діалоговому вікні виберіть ground.collection і натисніть OK. Обов’язково розмістіть ground.collection у позиції 0, 0, 0, інакше вона буде візуально зміщена. Збережіть файл.
Запустіть гру (Project ▸ Build), щоб переконатися, що все на своїх місцях.

Можливо, ви вже трохи заплуталися й замислилися, чим насправді є всі ці елементи, які ми створюємо. Тож зупинімося на мить і розгляньмо основні будівельні блоки будь-якого проєкту Defold:
Наразі цих пояснень, імовірно, достатньо. Значно докладніше ці поняття розглянуто в посібнику «Будівельні блоки». Радимо повернутися до нього згодом, щоб глибше зрозуміти, як усе працює в Defold.
Тепер, коли всі ділянки землі розташовано на своїх місцях, змусити їх рухатися досить просто. Ідея така: рухати ділянки справа наліво, а коли ділянка досягне крайньої лівої позиції за межами екрана, переносити її в крайню праву позицію. Для руху всіх цих ігрових об’єктів потрібен скрипт Lua, тож створімо його:
-- ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Speed in pixels/s
end
function update(self, dt) -- <3>
for i, p in ipairs(pieces) do -- <4>
local pos = go.get_position(p)
if pos.x <= -228 then -- <5>
pos.x = 1368 + (pos.x + 228)
end
pos.x = pos.x - self.speed * dt -- <6>
go.set_position(pos, p) -- <7>
end
end
init() викликається, коли ігровий об’єкт з’являється у грі. Ми ініціалізуємо локальну для об’єкта змінну-член, що містить швидкість руху землі.update() викликається один раз за кадр, зазвичай 60 разів на секунду. dt містить кількість секунд, що минули від попереднього виклику.dt, щоб отримати швидкість у пікселях за секунду, незалежну від частоти кадрів.Defold — це швидке ядро рушія, що керує вашими даними й ігровими об’єктами. Будь-яку логіку або поведінку, потрібну для гри, ви створюєте мовою Lua. Lua — швидка й легка мова програмування, яка чудово підходить для написання ігрової логіки. Для її вивчення є чудові матеріали, наприклад книга Programming in Lua та офіційний довідник Lua.
Defold доповнює Lua набором API та системою передавання повідомлень, за допомогою якої можна програмувати взаємодію між ігровими об’єктами. Докладніше про її роботу читайте в посібнику з передавання повідомлень.
Ви можете показувати й приховувати панелі Assets Pane, Console та Outline редактора клавішами F6, F7 і F8 відповідно
Тепер, коли файл скрипту готовий, потрібно додати посилання на нього до компонента ігрового об’єкта. Так скрипт виконуватиметься в межах життєвого циклу цього ігрового об’єкта. Для цього створимо новий ігровий об’єкт у ground.collection і додамо до нього компонент Script, що посилається на щойно створений файл скрипту Lua:

Тепер під час запуску гри ігровий об’єкт “controller” виконуватиме скрипт у своєму компоненті Script, і земля плавно прокручуватиметься екраном.
Герой буде ігровим об’єктом, що складається з таких компонентів:
Почніть з імпорту зображень частин тіла, а потім додайте їх до нового атласу, який назвемо hero.atlas:

Також потрібно імпортувати дані анімації Spine й налаштувати для них Spine Scene:

Файл hero.spinejson експортовано у форматі Spine JSON. Для створення таких файлів потрібна програма анімації Spine. Якщо ви хочете використовувати іншу програму анімації, можна експортувати анімації як аркуші спрайтів і використовувати їх як покадрові анімації з ресурсів Tile Source або Atlas. Докладніше читайте в посібнику з анімації.
Тепер можемо почати створювати ігровий об’єкт героя:

Тепер час додати фізику, щоб запрацювали колізії:
Тип колізії “Kinematic” означає, що ми хочемо реєструвати колізії, але фізичний рушій не розв’язуватиме їх автоматично й не симулюватиме об’єкти. Фізичний рушій підтримує кілька різних типів об’єктів колізій. Докладніше про них читайте в документації з фізики.
Важливо вказати, із чим має взаємодіяти об’єкт колізій:
Насамкінець створіть новий файл hero.script і додайте його до ігрового об’єкта.
handle_geometry_contact().)
Ми обробляємо колізії самостійно, тому що якби натомість установили для об’єкта колізій персонажа динамічний тип, рушій виконував би ньютонівську симуляцію тіл, що взаємодіють. Для такої гри ця симуляція далеко не оптимальна, тож замість того, щоб боротися з фізичним рушієм за допомогою різних сил, ми беремо керування повністю на себе.
Для цього й належної обробки колізій потрібні певні знання векторної математики. Докладне пояснення розв’язання кінематичних колізій наведено в документації з фізики.
-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20
-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900
function init(self)
-- this tells the engine to send input to on_input() in this script
msg.post(".", "acquire_input_focus")
-- save the starting position
self.position = go.get_position()
-- keep track of movement vector and if there is ground contact
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- Return input focus when the object is deleted
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- Apply gravity if there's no ground contact
self.velocity = self.velocity + gravity
end
-- apply velocity to the player character
go.set_position(go.get_position() + self.velocity * dt)
-- reset volatile state
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- project the correction vector onto the contact normal
-- (the correction vector is the 0-vector for the first contact point)
local proj = vmath.dot(self.correction, normal)
-- calculate the compensation we need to make for this contact point
local comp = (distance - proj) * normal
-- add it to the correction vector
self.correction = self.correction + comp
-- apply the compensation to the player character
go.set_position(go.get_position() + comp)
-- check if the normal points enough up to consider the player standing on the ground
-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
if normal.y > 0.7 then
self.ground_contact = true
end
-- project the velocity onto the normal
proj = vmath.dot(self.velocity, normal)
-- if the projection is negative, it means that some of the velocity points towards the contact point
if proj < 0 then
-- remove that component in that case
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- check if we received a contact point message. One message for each contact point
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- only allow jump from ground
if self.ground_contact then
-- set take-off speed
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- cut the jump short if we are still going up
if self.velocity.y > 0 then
-- scale down the upwards speed
self.velocity.y = self.velocity.y * 0.5
end
end
function on_input(self, action_id, action)
if action_id == hash("jump") or action_id == hash("touch") then
if action.pressed then
jump(self)
elseif action.released then
abort_jump(self)
end
end
end
За бажанням тепер можна тимчасово додати героя до головної колекції й запустити гру, щоб побачити, як він провалюється крізь світ.
Останнє, чого бракує герою для роботи, — введення. Наведений вище скрипт уже містить функцію on_input(), яка реагує на дії “jump” і “touch” (для сенсорних екранів). Додаймо прив’язки введення для цих дій.

Тепер, коли героя налаштовано, зокрема й колізії, потрібно додати колізії також до землі, щоб герою було із чим стикатися (або чим бігати). Зробимо це за мить, але спершу проведемо невеликий рефакторинг: помістимо все, що стосується рівня, в окрему колекцію й трохи впорядкуємо структуру файлів:
Як ви, можливо, вже помітили, ієрархія файлів на панелі Assets pane не пов’язана зі структурою вмісту, яку ви будуєте у своїх колекціях. На окремі файли посилаються файли колекцій та ігрових об’єктів, але їхнє розташування цілком довільне.
Якщо ви хочете перемістити файл в інше місце, Defold допоможе, автоматично оновивши посилання на нього (рефакторинг). Під час створення складного програмного забезпечення, як-от гри, надзвичайно корисно мати змогу змінювати структуру проєкту в міру його зростання й розвитку. Defold сприяє цьому й робить процес зручним, тож не бійтеся переміщувати файли!
Також потрібно додати до колекції рівня ігровий об’єкт контролера з компонентом-скриптом:
Відкрийте файл скрипту, скопіюйте до нього наведений нижче код і збережіть файл:
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

Ігровий об’єкт “controller” не існує в окремому файлі, а створений на місці (in-place) у колекції рівня. Це означає, що екземпляр ігрового об’єкта створюється з даних, заданих безпосередньо в колекції. Це підходить для об’єктів з одним конкретним призначенням, як цей. Якщо вам потрібні кілька екземплярів певного ігрового об’єкта й можливість змінювати прототип або шаблон, з якого створюється кожен екземпляр, просто створіть файл ігрового об’єкта й додайте ігровий об’єкт із файлу до колекції. Так буде створено ігровий об’єкт із посиланням на файл як на прототип або шаблон.
Призначення цього ігрового об’єкта “controller” — керувати всім, що стосується запущеного рівня. Невдовзі цей скрипт відповідатиме за створення платформ і монет, з якими взаємодіятиме герой, але поки що він лише задаватиме швидкість рівня.
У функції init() скрипт контролера рівня надсилає повідомлення компоненту-скрипту об’єкта контролера землі, адресуючи його за ідентифікатором:
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
Ідентифікатор ігрового об’єкта контролера — "ground/controller", оскільки він міститься в колекції “ground”. Далі додаємо ідентифікатор компонента "controller" після символу решітки "#", який відділяє ідентифікатор об’єкта від ідентифікатора компонента. Зверніть увагу, що у скрипті землі ще немає коду для реагування на повідомлення set_speed, тож потрібно додати до ground.script функцію on_message() і відповідну логіку.
-- ground.script
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then -- <1>
self.speed = message.speed -- <2>
end
end

На цьому етапі потрібно додати фізичні колізії для землі:

Тепер можете спробувати запустити гру (Project ▸ Build). Герой має бігти землею й стрибати після натискання клавіші Space. Якщо ви запускаєте гру на мобільному пристрої, стрибати можна, торкаючись екрана.
Щоб життя в нашому ігровому світі стало трохи цікавішим, додаймо платформи, на які можна стрибати.
Створіть файл Script із назвою platform.script (клацніть правою кнопкою миші на панелі Assets pane і виберіть New ▸ Script File), додайте до файлу наведений нижче код і збережіть його:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
go.delete() -- <1>
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
end
end

Зверніть увагу, що і platform.go, і platform_long.go мають компоненти Script, які посилаються на той самий файл скрипту. Це зручно, адже будь-які зміни в цьому файлі впливатимуть на поведінку як звичайних, так і довгих платформ.
Наша гра має бути простим нескінченним раннером. Це означає, що ігрові об’єкти платформ не можна заздалегідь розмістити в колекції в редакторі. Натомість їх потрібно створювати динамічно:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 } -- <1>
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
end
function update(self, dt) -- <2>
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
update() викликається один раз за кадр. Ми використовуємо її, щоб вирішувати, чи створювати звичайну або довгу платформу з певними інтервалами (щоб уникати перекриття) і на певних висотах. Можна легко експериментувати з різними алгоритмами створення платформ, змінюючи ігровий процес.Тепер запустіть гру (Project ▸ Build).
Ого, це вже починає перетворюватися на щось, у що (майже) можна грати…

Спочатку ми оживимо героя. Наразі бідолаха застряг у циклі бігу й належно не реагує ні на стрибки, ні на інші події. Файл Spine, який ми додали з пакета ресурсів, насправді містить набір потрібних для цього анімацій.
update(): -- hero.script
local function play_animation(self, anim)
-- only play animations which are not already playing
if self.anim ~= anim then
-- tell the spine model to play the animation
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- remember which animation is playing
self.anim = anim
end
end
local function update_animation(self)
-- make sure the right animation is playing
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() і додайте виклик update_animation: ...
-- apply it to the player character
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

У Lua локальні змінні мають «лексичну область видимості», тому порядок розташування функцій local має значення. Функція update() викликає локальні функції update_animation() і play_animation(), а отже, середовище виконання має вже знати про них, щоб їх викликати. Саме тому ці функції потрібно розмістити перед update(). Якщо змінити порядок функцій, виникне помилка. Зверніть увагу, що це стосується лише змінних local. Докладніше про правила областей видимості й локальні функції Lua читайте на http://www.lua.org/pil/6.2.html
Це все, що потрібно, аби додати герою анімації стрибка й падіння. Якщо запустити гру, ви помітите, що грати стало значно приємніше. Можливо, ви також помітите, що платформи, на жаль, можуть виштовхнути героя за межі екрана. Це побічний ефект обробки колізій, але виправити його просто — додамо трохи жорстокості й зробимо краї платформ небезпечними!
Збережіть файл.

Відкрийте hero.go, виберіть Collision Object і додайте назву “danger” до властивості Mask. Потім збережіть файл.

Відкрийте hero.script і змініть функцію on_message() так, щоб отримувати реакцію, коли герой стикається з краєм “danger”:
-- hero.script
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then
self.velocity = vmath.vector3(0, 0, 0)
self.correction = vmath.vector3()
self.ground_contact = false
self.anim = nil
go.set(".", "euler.z", 0)
go.set_position(self.position)
msg.post("#collisionobject", "enable")
elseif message_id == hash("contact_point_response") then
-- check if we received a contact point message
if message.group == hash("danger") then
-- Die and restart
play_animation(self, hash("death"))
msg.post("#collisionobject", "disable")
-- <1>
go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7)
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("#", "reset")
end)
elseif message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
Змініть функцію init() так, щоб вона надсилала повідомлення “reset” для ініціалізації об’єкта, а потім збережіть файл:
-- hero.script
function init(self)
-- this lets us handle input in this script
msg.post(".", "acquire_input_focus")
-- save position
self.position = go.get_position()
msg.post("#", "reset")
end
Якщо зараз спробувати пограти, швидко стане зрозуміло, що механізм скидання не працює як слід. Герой скидається правильно, але легко може опинитися в ситуації, коли одразу впаде на край платформи й знову загине. Ми хочемо належно скидати весь рівень після смерті. Оскільки рівень — це лише послідовність створених платформ, достатньо відстежувати всі створені платформи, а під час скидання видаляти їх:
Відкрийте файл controller.script і змініть код так, щоб зберігати ідентифікатори всіх створених платформ:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 }
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
self.spawns = {} -- <1>
end
function update(self, dt)
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
table.insert(self.spawns, p) -- <1>
end
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then -- <2>
-- Tell the hero to reset.
msg.post("hero#hero", "reset")
-- Delete all platforms
for i,p in ipairs(self.spawns) do
go.delete(p)
end
self.spawns = {}
elseif message_id == hash("delete_spawn") then -- <3>
for i,p in ipairs(self.spawns) do
if p == message.id then
table.remove(self.spawns, i)
go.delete(p)
end
end
end
end
Відкрийте platform.script і змініть його так, щоб замість безпосереднього видалення платформи, яка досягла крайнього лівого краю, він надсилав контролеру рівня повідомлення із запитом на її видалення:
-- platform.script
...
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
...

-- hero.script
...
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("controller#controller", "reset")
end)
...

Тепер основний цикл перезапуску й смерті готовий!
Далі — те, заради чого варто жити: монети!
Ідея полягає в тому, щоб додати на рівень монети, які гравець збиратиме. Перше питання — як саме розміщувати їх на рівні. Наприклад, можна розробити схему створення монет, яка певним чином узгоджується з алгоритмом створення платформ. Утім, ми зрештою обрали значно простіший підхід: нехай самі платформи створюють монети:
Створіть новий файл скрипту coin.script: клацніть правою кнопкою миші level на панелі Assets pane і виберіть New ▸ Script File. Замініть код шаблону на такий:
-- coin.script
function init(self)
self.collected = false
end
function on_message(self, message_id, message, sender)
if self.collected == false and message_id == hash("collision_response") then
self.collected = true
msg.post("#sprite", "disable")
elseif message_id == hash("start_animation") then
pos = go.get_position()
go.animate(go.get_id(), "position.y", go.PLAYBACK_LOOP_PINGPONG, pos.y + 24, go.EASING_INOUTSINE, 0.75, message.delay)
end
end
Додайте файл скрипту як компонент Script до об’єкта монети: клацніть правою кнопкою миші кореневий вузол на панелі Outline і виберіть Add Component from File.

Ми плануємо створювати монети з об’єктів платформ, тож додайте фабрики (factories) монет до platform.go і platform_long.go.

Тепер потрібно змінити platform.script, щоб він створював і видаляв монети:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
self.coins = {}
end
function final(self)
for i,p in ipairs(self.coins) do
go.delete(p)
end
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function create_coins(self, params)
local spacing = 56
local pos = go.get_position()
local x = pos.x - params.coins * (spacing*0.5) - 24
for i = 1, params.coins do
local coin = factory.create("#coin_factory", vmath.vector3(x + i * spacing , pos.y + 64, 1))
msg.post(coin, "set_parent", { parent_id = go.get_id() }) -- <1>
msg.post(coin, "start_animation", { delay = i/10 }) -- <2>
table.insert(self.coins, coin)
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
elseif message_id == hash("create_coins") then
create_coins(self, message)
end
end
Батьківсько-дочірні зв’язки змінюють лише граф сцени. Дочірній об’єкт трансформується (переміщується, масштабується або повертається) разом зі своїм батьківським об’єктом. Якщо вам потрібні додаткові зв’язки «належності» між ігровими об’єктами, їх потрібно окремо відстежувати в коді.
Останній крок у цьому уроці — додати кілька рядків до controller.script:
-- controller.script
...
local platform_heights = { 100, 200, 350 }
local coins = 3 -- <1>
...
-- controller.script
...
local coins = coins
if math.random() > 0.5 then
f = "#platform_long_factory"
coins = coins * 2 -- Twice the number of coins on long platforms
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

Тепер у нас є проста, але робоча гра! Якщо ви дійшли до цього місця, можливо, захочете продовжити самостійно й додати:
Завантажте готову версію проєкту тут
На цьому вступний урок завершено. Тепер продовжуйте знайомство з Defold. Ми підготували багато посібників і уроків, що допоможуть вам, а якщо натрапите на труднощі — запрошуємо на форум.
Приємної роботи з Defold!