Перший скрипт дитини в Roblox Studio майже завжди робить щось один раз: ставить колір, рухає деталь, друкує рядок. Гра від цього ще не з'являється. Гра з'являється тоді, коли скрипт перестає діяти сам і починає чекати — на дотик, на клік, на вхід гравця. Цей механізм очікування і називається подією.
Нижче — події, яких вистачає на будь-який перший проєкт, із прикладами й тими пастками, через які вони працюють «не так». Довідник розрахований на те, що базовий синтаксис уже знайомий: що таке функція, змінна і де в Studio лежить скрипт.
Як виглядає підключення
Запис завжди однаковий: об'єкт, крапка, ім'я події, двокрапка, Connect і функція, яку треба викликати.
local dver = script.Parent
dver.Touched:Connect(function(hit)
print('хтось торкнувся:', hit.Name)
end)
Два місця тут бувають незрозумілими. Крапка перед іменем події означає «візьми в цього об'єкта», а двокрапка перед Connect — «виклич метод у самої події». Переплутати їх легко, і Studio підкреслить рядок, але повідомлення про помилку мало що пояснює. Друге — слово hit у дужках: це не щось, що треба оголосити, а подарунок від самої події. Вона сама передає туди те, що саме доторкнулося, і назвати цей параметр можна як завгодно.
Події, які потрібні найчастіше
| Подія | Коли спрацьовує | Що передає |
|---|---|---|
Touched | деталі щось торкнулося | деталь, що торкнулася |
TouchEnded | дотик скінчився | та сама деталь |
MouseClick | клік по ClickDetector | гравця, який клікнув |
MouseButton1Click | клік по кнопці інтерфейсу | нічого |
PlayerAdded | гравець зайшов у гру | об'єкт гравця |
PlayerRemoving | гравець виходить | об'єкт гравця |
CharacterAdded | персонаж з'явився на карті | модель персонажа |
Died | персонаж загинув | нічого |
Changed | властивість змінилася | ім'я властивості |
Heartbeat | кожен кадр на сервері | час з минулого кадру |
Touched: найчастіша і найкапризніша
Дотик здається простим, поки дитина не поставить на нього видачу монет. Тоді з'ясовується, що за один прохід персонажа гравець отримує не одну монету, а двадцять: подія спрацьовує на кожну частину тіла і повторюється, поки контакт триває. Для гри це виглядає як поломка економіки, а насправді подія працює рівно так, як мусить.
Лікується прапорцем, який у розробників зветься дебаунс. Сенс у тому, щоб скрипт сам пам'ятав, що вже видав нагороду, і до кінця паузи ігнорував повторні дотики.
local moneta = script.Parent
local zaniato = false
moneta.Touched:Connect(function(hit)
if zaniato then return end
local humanoid = hit.Parent:FindFirstChild('Humanoid')
if not humanoid then return end
zaniato = true
print('монета зібрана')
task.wait(1)
zaniato = false
end)
Другий важливий рядок тут — пошук Humanoid. Без нього монету «збере» будь-що: впала деталь, кинутий предмет, інша монета. Перевірка на наявність гуманоїда відсікає все, що не є персонажем, і це та сама перевірка, якої майже ніколи немає в перших скриптах з відеоуроків.
Кліки: два різні механізми
Клік по об'єкту в світі й клік по кнопці інтерфейсу — це різні події, і їх часто плутають. Для предмета на карті потрібно спершу вкласти в нього ClickDetector, інакше клікати буде нічим: сама деталь подій мишки не має. Після цього працює MouseClick, і вона зручно передає в функцію того гравця, який клікнув, — не треба здогадуватися, хто це був.
Кнопка інтерфейсу влаштована інакше: вона вже кнопка, детектор їй не потрібен, і подія в неї своя — MouseButton1Click. Нічого вона не передає, бо натиснути кнопку на своєму екрані може тільки її власник. Саме через це скрипт для кнопки має бути клієнтським і лежати в StarterPlayerScripts або поруч із самою кнопкою, а не в ServerScriptService.
Гравець зайшов, персонаж народився
Тут пастка, яка з'їдає найбільше часу. PlayerAdded спрацьовує, коли людина підключилася, але персонажа на карті в цю мить ще немає — модель з'явиться пізніше. Скрипт, який одразу після входу намагається знайти Humanoid, нічого не знаходить і тихо нічого не робить.
local Players = game:GetService('Players')
Players.PlayerAdded:Connect(function(gravec)
gravec.CharacterAdded:Connect(function(person)
print(gravec.Name, 'готовий до гри')
end)
end)
Вкладена схема — підписка на подію всередині іншої події — спочатку виглядає надто складною, але це стандартний спосіб, і далі він зустрінеться десятки разів. CharacterAdded до того ж спрацьовує після кожного відродження, а не лише раз на вхід, і це саме те, що потрібно для нагород чи початкового інвентарю.
RemoteEvent: розмова клієнта з сервером
Клієнтський скрипт не може змінювати те, що бачать інші гравці, а серверний не знає, на що натиснув конкретний гравець. Міст між ними — RemoteEvent, об'єкт, який кладуть у ReplicatedStorage.
| Напрямок | Хто надсилає | Хто ловить |
|---|---|---|
| Клієнт серверу | FireServer() | OnServerEvent |
| Сервер одному | FireClient(gravec) | OnClientEvent |
| Сервер усім | FireAllClients() | OnClientEvent |
Одна деталь ламає половину перших спроб: у OnServerEvent першим параметром сервер завжди підставляє гравця сам, автоматично. Тобто якщо клієнт надіслав одне число, функція на сервері отримає два аргументи — спершу гравця, потім число. Дитина, яка цього не знає, читає замість числа незрозумілий об'єкт і не розуміє, звідки він узявся.
І правило, без якого гра стає зламаною за годину після публікації: ніколи не вірити тому, що прийшло з клієнта. Якщо клієнт надсилає «додай мені сто монет», сервер мусить сам вирішити, чи заслужив гравець ці монети. Інакше будь-хто з редактором скриптів надішле собі мільйон.
Коли змінилася властивість
Подія Changed зручна для здоров'я, очок, рівня — усього, що треба відслідковувати. Але в неї є розбіжність, на якій спотикаються всі. У звичайного об'єкта вона передає ім'я властивості, що змінилася, а в об'єктів-значень на кшталт IntValue — саме нове значення. Той самий код на двох об'єктах поводиться по-різному, і виглядає це як містика.
Надійніше просити конкретну властивість окремо, тоді гадати не доведеться:
humanoid:GetPropertyChangedSignal('Health'):Connect(function()
print('рівень життя тепер', humanoid.Health)
end)
Кожен кадр: коли подія замінює цикл
Окрема категорія — події, що спрацьовують постійно. Heartbeat зі служби RunService викликається кожен кадр і передає, скільки часу минуло з попереднього. Виглядає це як цикл, але принципово відрізняється: гра сама вирішує, коли викликати функцію, і не блокує все інше.
Різниця важлива саме тому, що новачок замість цієї події пише while true do без паузи. Такий цикл не віддає керування грі, і Studio або повисає, або вивалює помилку про перевантаження. Якщо щось має відбуватися безперервно — обертання платформи, відлік таймера, перевірка відстані — беріть подію кадру, а не нескінченний цикл.
Паралельно варто знати межу: обчислення всередині такої функції виконуються шістдесят разів на секунду, тому важка робота там дуже швидко просаджує гру. Усе, що можна порахувати один раз заздалегідь, рахують до підписки, а не в ній.
Чому подія не спрацювала: порядок перевірки
Коли скрипт мовчить, перевіряти варто за порядком, а не навмання. Перше — чи взагалі виконується скрипт: поставте print у перший рядок, до всіх підписок. Якщо в Output нічого немає, проблема не в події, а в тому, що скрипт лежить не там, де його запускають.
Друге — чи правильний об'єкт. Запис script.Parent повертає те, у що вкладено скрипт, і якщо дитина перетягнула скрипт на рівень вище, Parent став іншим. Третє — серверний скрипт чи клієнтський: подія кнопки інтерфейсу на сервері не спрацює ніколи, і помилки при цьому не буде, бо сама підписка цілком законна.
Відключення: про що забувають
Виклик Connect повертає з'єднання, і його можна розірвати:
local zednannia = chastyna.Touched:Connect(function() end)
zednannia:Disconnect()
Поки об'єкт живий, з'єднання теж живе. Якщо скрипт підписується на подію в циклі й не відключає старі підписки, їх стає сотні, кожна робить свою роботу, і гра поступово гальмує без видимої причини. Для дії, яка потрібна один раз, зручніше одразу відключати підписку всередині самої функції.
Події — той момент, після якого проєкт починає відчуватися грою, а не набором деталей. Якщо синтаксис ще не звичний, почніть із базового синтаксису Lua, збереження прогресу розібране в матеріалі про DataStore, а те, що ламає перший проєкт найчастіше, зібране в помилках початківців.