События в Roblox Studio: как использовать Touched и ClickDetector в Lua-скриптах

На чтение
6 мин
Дата обновления
05.11.2026

Интерактивному объекту не обязательно каждую долю секунды спрашивать, не коснулся ли его персонаж. В Roblox можно подписать функцию на событие и выполнять нужную реакцию при его возникновении. Для первого упражнения удобно сравнить два сигнала: Touched у детали и MouseClick у ClickDetector.

Соберём авторскую учебную дверь, которая на короткое время становится проходимой по касанию кнопки или клику. Это демонстрация событий и общего состояния, без ключей, наград и защищённого доступа. Код написан на Luau, языке сценариев Roblox. Он предназначен для отдельной тестовой сцены; поведение нужно проверить в Play, а не только прочитать в редакторе.

Сигнал и обработчик — разные вещи

Событие предоставляет объекту возможность сообщить о происходящем. Connect присоединяет функцию, которая будет вызвана при событии. При подключении передают функцию, а не результат её немедленного вызова:

local part = script.Parent

local function onTouched(otherPart)
    print("Коснулась деталь:", otherPart.Name)
end

part.Touched:Connect(onTouched)

Этот маленький пример предполагает обычный Script внутри Part. В onTouched поступает другая деталь, а не сразу Player. У персонажа это может быть нога или другая часть модели. Именно поэтому обращаться к otherPart как к игроку и искать у него UserId было бы ошибкой.

Подписку в примере создают один раз при запуске скрипта. Если создавать новую подписку внутри каждого срабатывания, число обработчиков начнёт расти. Connect возвращает соединение, которое можно сохранить и отключить через Disconnect, когда слушатель больше не нужен.

Подготовить сцену и не перепутать объекты

Создайте в Workspace модель EventDoor. Внутри разместите две обычные детали: Door — преграду в проходе и Button — кнопку рядом. У обеих установите Anchored=true. У Door исходно CanCollide=true; у Button оставьте CanTouch=true. В Button добавьте ClickDetector и один обычный Script. Старый пробный скрипт печати удалите или отключите, чтобы при проверке не смешивать результаты двух упражнений.

Workspace
  EventDoor
    Door
    Button
      ClickDetector
      Script

В Properties у ClickDetector задайте MaxActivationDistance=12 как условное значение нашей небольшой сцены. Это настройка опыта, а не «правильная дистанция» для любой игры. Разместите кнопку так, чтобы персонаж мог подойти к ней и коснуться. Скрипт ниже рассчитывает на эти имена и расположение.

Что отличает касание от клика

Touched связан с физической симуляцией. Официальное руководство Roblox отдельно предупреждает: простая установка Position или CFrame так, чтобы детали пересеклись, не является тем же событием физического касания. CanTouch влияет на touch-события, а CanCollide — на физическое столкновение; отключённый CanCollide сам по себе не означает, что Touched никогда не возникнет.

MouseClick у ClickDetector передаёт Player, который взаимодействовал с ним. Для обработки не нужно самостоятельно угадывать игрока по детали. У Touched такой готовой ссылки нет: её нужно получить из модели персонажа, если касание действительно относится к игроку.

Касание может срабатывать несколько раз подряд, например из-за разных частей персонажа и небольших физических столкновений. Поэтому «один раз подошёл» не равно «один вызов функции». Debounce — явное состояние, которое не позволяет повторять действие слишком часто; оно решает другую задачу, чем поиск игрока.

Одна функция открытия для двух событий

Замените содержимое Script внутри Button следующим кодом. Три секунды — авторская длительность демонстрации. Сохраняем исходные свойства двери, чтобы вернуть именно их, а не произвольные значения.

local Players = game:GetService("Players")
local button = script.Parent
local model = button.Parent
local door = model:FindFirstChild("Door")
local detector = button:FindFirstChildOfClass("ClickDetector")

assert(button:IsA("BasePart"), "Script должен быть внутри Button")
assert(door and door:IsA("BasePart"), "Нужна деталь Door")
assert(detector, "В Button нужен ClickDetector")

local busy = false
local closedTransparency = door.Transparency
local closedCanCollide = door.CanCollide
local OPEN_SECONDS = 3

local function openDoor(player)
    if busy then
        return
    end
    busy = true
    print("Открытие для:", player.Name)
    door.CanCollide = false
    door.Transparency = 0.65

    task.wait(OPEN_SECONDS)

    door.Transparency = closedTransparency
    door.CanCollide = closedCanCollide
    busy = false
end

local function onTouched(otherPart)
    local character = otherPart.Parent
    if not character or not character:IsA("Model") then
        return
    end
    local player = Players:GetPlayerFromCharacter(character)
    if not player then
        return
    end
    openDoor(player)
end

local function onClicked(player)
    openDoor(player)
end

button.Touched:Connect(onTouched)
detector.MouseClick:Connect(onClicked)

Здесь GetPlayerFromCharacter сопоставляет модель с игроком. Если кнопку коснулся обычный ящик, подходящего Player не будет, и обработчик завершится. Для простоты проверяется непосредственный Parent детали: касание ручкой аксессуара, родителем которой является Accessory, этот учебный вариант не распознаёт как касание тела персонажа. Не объявляйте такую проверку универсальным распознавателем любых вложенных моделей.

Переменная busy общая у обоих обработчиков. Пока дверь открыта, дополнительные клики и касания не создают новые циклы и не продлевают таймер. Это правило именно нашего мини-проекта. Для другой механики, например индивидуального cooldown каждого игрока, понадобится другое состояние, а не простое переименование busy.

Как проверить сценарий по шагам

  1. Запустите Play и откройте Output. Убедитесь, что нет сообщения assert об отсутствующем объекте.
  2. Подойдите к кнопке и кликните. Дверь должна стать полупрозрачной и проходимой, затем вернуть исходные свойства.
  3. После закрытия коснитесь Button телом персонажа. Должен выполниться тот же путь открытия.
  4. Во время открытого состояния повторяйте взаимодействие. В рамках одного цикла сообщение об открытии должно появиться один раз, поскольку busy уже true.
  5. Проверьте касание посторонней деталью. Для модели, не связанной с Player, перехода к openDoor не должно быть.

Это ожидаемые результаты авторского теста, а не отчёт о запуске сцены на вашем устройстве. В реальном проекте дополнительно проверяйте нескольких игроков, удаление объектов во время ожидания и ситуацию закрытия в занятом проходе. Наш таймер просто возвращает свойства и не содержит системы безопасного закрывания или анимации.

Ошибки, которые стоит искать раньше сложной логики

СимптомПроверка
attempt to index nilИмена Door и Button, расположение Script, наличие ClickDetector
Клик работает, касание нетCanTouch, реальное движение персонажа, какой объект пришёл в otherPart
На один контакт много действийОбщий debounce и отсутствие нескольких одинаковых подписок
Функция выполняется при запускеНе написано ли Connect(onClicked()) вместо Connect(onClicked)
Открывается только локальная картинкаГде выполняется код и соответствует ли выбранный тип скрипта серверному сценарию

Наконец, debounce не является защитой прав доступа. Roblox предупреждает о необходимости серверной проверки действий, которые клиент может инициировать через Touched и ClickDetector, и об ограничении частоты. Не выдавайте за защищённую дверь конструкцию, где единственное условие — существование Player. Если взаимодействие открывает платную область, выдаёт награду или меняет важные данные, сервер должен отдельно проверять допустимость действия. Учебный пример помогает понять события; правила доступа строятся поверх этой механики.