Первый скрипт ребёнка в 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(igrok)
igrok.CharacterAdded:Connect(function(person)
print(igrok.Name, 'готов к игре')
end)
end)
Вложенная схема — подписка на событие внутри другого события — сначала выглядит слишком сложной, но это стандартный способ, и дальше он встретится десятки раз. CharacterAdded к тому же срабатывает после каждого возрождения, а не только раз на вход, и это именно то, что нужно для награды или начального инвентаря.
RemoteEvent: разговор клиента с сервером
Клиентский скрипт не может менять то, что видят другие игроки, а серверный не знает, на что нажал конкретный игрок. Мост между ними — RemoteEvent, объект, который кладут в ReplicatedStorage.
| Направление | Кто отправляет | Кто ловит |
|---|---|---|
| Клиент серверу | FireServer() | OnServerEvent |
| Сервер одному | FireClient(igrok) | 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 soedinenie = chast.Touched:Connect(function() end)
soedinenie:Disconnect()
Пока объект жив, соединение тоже живёт. Если скрипт подписывается на событие в цикле и не отключает старые подписки, их становятся сотни, каждая делает свою работу, и игра постепенно тормозит без видимой причины. Для действия, которое нужно один раз, удобнее сразу отключать подписку внутри самой функции.
События — тот момент, после которого проект начинает ощущаться игрой, а не набором деталей. Если синтаксис ещё не привычен, начните с базового синтаксиса Lua, сохранение прогресса разобрано в материале про DataStore, а то, что ломает первый проект чаще всего, собрано в ошибках новичков.