Античит без античита: как сделать игру, в которой читеру нечего ломать
Клиент в Roblox принадлежит игроку, и остановить его подмену нельзя. Зато можно построить сервер так, что подмена ничего не даёт. Разбираем, что проверять, что считать и почему кик по подозрению — плохая идея.
Сложно 25 минут проверен 35 дней назад
Что понадобится
Понимание клиента и сервера, RemoteEvent и RemoteFunction. Стоит сначала прочитать гайды про магазин и админ-команды — здесь та же мысль, доведённая до конца.
Текст гайда «Античит без античита: как сделать игру, в которой читеру нечего ломать»
Содержание
Первое, с чем нужно смириться: всё, что работает на устройстве игрока, игроку и принадлежит. Локальные скрипты можно прочитать, изменить, остановить. Любое сообщение на сервер можно отправить руками, с любыми аргументами, сколько угодно раз. Никакой «защиты клиента» не существует, и время, потраченное на неё, потрачено зря.
Единственная работающая формула
Сервер ничему не верит и всё решает сам. Клиент сообщает о намерениях — «хочу ударить», «хочу купить», — а сервер проверяет, возможно ли это, и делает. Если после этого читер может испортить только собственный экран — античит готов, хотя ни одного детектора в игре нет.
Шаг 1. Инвентаризация: что решает клиент#
Открой каждый RemoteEvent и RemoteFunction в игре и ответь на один вопрос: что случится, если это сообщение придёт с выдуманными аргументами тысячу раз подряд? Список ниже — типичные ответы и что с ними делать.
| Клиент присылает | Почему нельзя верить | Что делать вместо |
|---|---|---|
| Сумму урона | Пришлют миллион | Сервер знает оружие игрока и считает урон сам |
| Кого ударил | Пришлют игрока на другом конце карты | Сервер проверяет расстояние и направление |
| Цену покупки | Пришлют ноль | Цена лежит на сервере, клиент шлёт только название |
| «Я прошёл уровень» | Пришлют сразу | Сервер сам знает, где стоит игрок и что он сделал |
| Позицию персонажа | В Roblox её и так реплицирует клиент | Проверять скорость перемещения, а не положение |
| Свой уровень, деньги, флаг «админ» | Любое из этого — подстановка | Никогда не принимать состояние; только намерения |
Последняя строка — самая важная. Хороший канал принимает от клиента намерение и минимум данных: имя предмета, идентификатор цели, номер слота. Всё остальное сервер знает сам, потому что сам это и выдал.
Шаг 2. Три проверки на каждом входе#
Каждый обработчик начинается с одних и тех же трёх вопросов. Не два из трёх — все три.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local attack = ReplicatedStorage:WaitForChild("MeleeAttack")
local REACH = 8 -- дальность удара в стадах, с запасом на задержку
local COOLDOWN = 0.45 -- быстрее оружие бить не умеет
local DAMAGE = 20 -- клиент эту цифру не присылает никогда
local lastHit = {}
local function rootOf(character)
return character and character:FindFirstChild("HumanoidRootPart")
end
attack.OnServerEvent:Connect(function(player, target)
-- 1. Форма: то ли это, что мы ждём?
if typeof(target) ~= "Instance" or not target:IsA("Model") then
return
end
local targetHumanoid = target:FindFirstChildOfClass("Humanoid")
local attacker = rootOf(player.Character)
local victim = rootOf(target)
if targetHumanoid == nil or attacker == nil or victim == nil then
return
end
-- 2. Право: может ли этот игрок сейчас это сделать?
local now = os.clock()
if lastHit[player] and now - lastHit[player] < COOLDOWN then
return
end
if targetHumanoid.Health <= 0 or target == player.Character then
return
end
-- 3. Возможность: правдоподобно ли это физически?
if (attacker.Position - victim.Position).Magnitude > REACH then
return
end
lastHit[player] = now
targetHumanoid:TakeDamage(DAMAGE)
end)
Players.PlayerRemoving:Connect(function(player)
lastHit[player] = nil
end)- Форма. Тип и структура аргументов.
typeof,IsA, проверка наnil. Скрипт, который падает от неверного типа, — уже уязвимость: упавший обработчик перестаёт работать для всех. - Право. Может ли игрок сделать это сейчас: перезарядка, наличие предмета, жив ли он сам, есть ли у него это оружие. Всё это сервер знает без клиента.
- Возможность. Совпадает ли просьба с миром: расстояние, направление, стена между. Здесь и живёт запас на задержку — проверка строгая, но не до пикселя.
Молчать, а не ругаться
Ни в одной ветке отказа сервер не отвечает клиенту «нельзя». Честному игроку это не нужно — его клиент такой запрос не отправит. А тому, кто ищет границы, каждый ответ — подсказка, где они проходят. Отказ — это просто return.
Шаг 3. Скорость и телепорт — проверять траекторию, а не точку#
Позицию персонажа в Roblox по умолчанию задаёт клиент, и это не изменить без перехода на серверную авторитетность. Значит, «полёт» и «спидхак» технически всегда возможны. Отбирать их напрямую сервер не может — но может не давать им ничего заработать.
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local MAX_SPEED = 30 -- обычная WalkSpeed 16 плюс запас на прыжки и задержку
local WINDOW = 1 -- считаем по секундам, а не по кадрам
local track = {}
RunService.Heartbeat:Connect(function()
local now = os.clock()
for _, player in ipairs(Players:GetPlayers()) do
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if root then
local last = track[player]
if last == nil or now - last.at >= WINDOW then
if last ~= nil then
local distance = (root.Position - last.position).Magnitude
if distance > MAX_SPEED * (now - last.at) then
-- Не кикаем. Возвращаем туда, где видели в прошлый раз,
-- и запоминаем: три таких события подряд — повод
-- посмотреть журнал, а не автоматически банить.
root.CFrame = CFrame.new(last.position)
player:SetAttribute("speedFlags", (player:GetAttribute("speedFlags") or 0) + 1)
end
end
track[player] = { at = now, position = root.Position }
end
end
end
end)
Players.PlayerRemoving:Connect(function(player)
track[player] = nil
end)Проверка нарочно грубая: секунда, а не кадр; тридцать стадов, а не шестнадцать. Телепорты внутри игры — лифты, порталы, респаун — нужно исключать явно: перед своим телепортом сбрасывай запись в track, иначе поймаешь собственный код. И главное: наказание — вернуть на место, а не выгнать.
Почему не кикать по подозрению
Честный игрок с плохим Wi-Fi выглядит для сервера в точности как читер: рывки, скачки позиции, пропавшие секунды. Автоматический бан за это — потеря живого человека ради одного пойманного. Автоматика откатывает и записывает; решает человек по журналу. Это медленнее, но игра от этого не пустеет.
Шаг 4. Считать невозможное#
Есть класс читов, которые не ломают ни одну проверку по отдельности: автокликер, авто-прицел, бот, который фармит всю ночь. Каждое действие законно, ненормальна только их сумма. Против этого работает статистика на сервере — пусть очень простая.
- Монеты в час: если игрок заработал за час больше, чем физически даёт карта, — запись в журнал
- Точность: сто попаданий из ста за вечер — повод посмотреть, а не награда
- Ритм: одинаковый интервал между нажатиями с точностью до миллисекунды — так не жмут люди
- Время без движения курсора при постоянных действиях
Здесь особенно важна дисциплина: ни одна из этих цифр не должна сама по себе выгонять. Они собираются в атрибут или в отдельный DataStore, а раз в неделю ты открываешь список и смотришь глазами. Десять минут на это — и большинство ботов находятся без единого ложного бана.
Шаг 5. Что не давать клиенту вообще#
Всё, что попало в ReplicatedStorage, Workspace и StarterPlayer, можно прочитать с клиента. Отсюда правило: там не должно быть ничего, что помогает жульничать.
- Расположение секретов и ответы на загадки — в
ServerStorage, выдаются по мере открытия - Формулы урона, шансы дропа, цены — на сервере; клиенту только результат
- Позиции других игроков вне зоны видимости движок реплицирует и так — лишний раз сообщать их не нужно
- Модули с «общей логикой» между клиентом и сервером: проверь, что в них нет того, что нужно только серверу
Чего этот гайд не делает#
Здесь нет детекторов инжекторов, проверок целостности клиента и «сканеров памяти». Они не работают: всё, что запускается на клиенте, клиент и отключит. Roblox ловит модифицированные клиенты своими средствами на уровне платформы — это его работа, и в игру её не встроить. Твоя работа — сервер, которому нечего у тебя украсть.
И обратное: как выглядят читы изнутри, где их берут и как обойти чужие проверки — не тема портала. Использование читов в чужих играх заканчивается баном аккаунта, а «бесплатные скрипты» почти всегда воруют его раньше: об этом в разделе безопасности.
Проверь себя#
- Ни один канал не принимает от клиента число урона, цену, уровень или флаг прав
- Каждый обработчик проверяет форму, право и возможность — и молча выходит при отказе
- Неверный тип аргумента не роняет скрипт: обработчик продолжает работать для остальных
- Проверка скорости считает по секундам, знает про твои телепорты и возвращает, а не кикает
- Есть журнал, который читает человек, и нет автоматического бана
- В
ReplicatedStorageнет ничего, что помогло бы выиграть
Всё это — тот же принцип, что в админ-командах и магазине, только применённый ко всей игре сразу. Если он держится в каждом канале, отдельный «античит» твоей игре не нужен.

Обсуждение
Пиши по делу и без личных данных. Ссылки на сторонние сайты, обмен и «бесплатные робуксы» мы скрываем — почему.
Комментарии пишут те, кто вошёл — так спокойнее всем.
ВойтиПока никто ничего не написал. Будь первым.