NPC и диалоги: персонаж, с которым можно поговорить
Как поставить в мир живого на вид персонажа, повесить на него подсказку «нажми E», показать реплику и выдать награду так, чтобы её нельзя было выпросить у сервера из консоли.
Средне 22 минуты 6 шагов проверен 1 месяц назад
Что понадобится
Roblox Studio, понимание разницы между Script и LocalScript. Код в гайде готовый.
Текст гайда «NPC и диалоги: персонаж, с которым можно поговорить»
Содержание
NPC оживляет карту сильнее, чем любой декор: пустой город с домами выглядит заброшенным, а тот же город с тремя стоящими персонажами — обитаемым. Хорошая новость в том, что первый говорящий NPC делается без единой строки искусственного интеллекта.
Из чего состоит NPC#
NPC — это обычная модель персонажа: части тела, Humanoid и HumanoidRootPart. Ставится так же, как риг для анимаций: вкладка Avatar → Rig Builder. Дальше остаётся закрепить его на месте и убрать лишнее.
- выдели все части модели и включи
Anchored— иначе игроки растолкают NPC по карте - у
HumanoidпоставьDisplayDistanceTypeвNone, если имя над головой не нужно HealthDisplayType— тожеNone: полоска здоровья у продавца выглядит странно- переименуй модель по-человечески:
NPC_Shopkeeper, а неDummy
Оденься в редакторе
Внешность NPC меняется так же, как своя: перетащи в модель объекты одежды и аксессуаров из библиотеки. Пять минут работы отличают «манекен из урока» от персонажа, которого запоминают.
ProximityPrompt вместо клика#
Раньше взаимодействие делали через ClickDetector, и на телефоне это работало плохо. Сейчас правильный способ один — ProximityPrompt: он сам рисует подсказку, сам понимает, что игрок подошёл достаточно близко, и сам работает и на клавиатуре, и на пальце.
Добавь ProximityPrompt внутрь HumanoidRootPart и настрой его свойства:
| Свойство | Что задаёт |
|---|---|
ActionText | Что произойдёт: «Поговорить», «Купить», «Взять» |
ObjectText | С кем или с чем: имя NPC |
KeyboardKeyCode | Клавиша на компьютере, обычно E |
MaxActivationDistance | С какого расстояния видно подсказку |
RequiresLineOfSight | Прятать ли подсказку, когда NPC за стеной — почти всегда стоит включить |
Реплика#
Самый простой диалог — облачко над головой. Его делают через BillboardGui: он всегда развёрнут к камере и не требует возни с позиционированием.
local npc = script.Parent
local root = npc:WaitForChild("HumanoidRootPart")
local prompt = root:WaitForChild("ProximityPrompt")
local bubble = root:WaitForChild("Bubble") -- BillboardGui
local label = bubble:WaitForChild("Text") -- TextLabel внутри него
local lines = {
"Привет. Давно тут никого не было.",
"За мостом сейчас лучше не ходить.",
"Если найдёшь ключ — возвращайся.",
}
local index = 0
local speaking = false
bubble.Enabled = false
prompt.Triggered:Connect(function(player)
if speaking then
return -- пока фраза висит, повторные нажатия игнорируем
end
speaking = true
index = index % #lines + 1
label.Text = lines[index]
bubble.Enabled = true
task.delay(4, function()
bubble.Enabled = false
speaking = false
end)
end)Реплики держи списком
Один и тот же текст при каждом подходе превращает NPC в табличку. Список из трёх-четырёх фраз по кругу стоит десяти минут и читается совсем иначе. Хранить их лучше в ModuleScript: тогда тексты правит человек, который не боится сломать код.
Награда: где всё ломается#
Как только NPC начинает что-то выдавать — монеты, предмет, доступ, — появляется вопрос безопасности. Клиенту доверять нельзя: игрок может вызвать событие сам, сколько угодно раз и откуда угодно.
- выдачу делает сервер, и только он:
Script, а неLocalScript ProximityPrompt.Triggeredуже приходит на сервер и приносит игрока — этого достаточно, отдельный RemoteEvent тут не нужен- сервер сам проверяет расстояние: подсказка ограничивает дистанцию на клиенте, но клиент — не доказательство
- сервер сам проверяет условие: выполнил ли игрок задание, хватает ли у него монет
- состояние (выдано / не выдано) хранится на сервере, а не в интерфейсе
local MAX_DISTANCE = 12
prompt.Triggered:Connect(function(player)
local character = player.Character
local playerRoot = character and character:FindFirstChild("HumanoidRootPart")
if not playerRoot then
return
end
-- Клиент показывает подсказку только вблизи, но верить в это нельзя:
-- расстояние пересчитывается на сервере ещё раз.
if (playerRoot.Position - root.Position).Magnitude > MAX_DISTANCE then
return
end
if given[player.UserId] then
return -- награда одноразовая
end
given[player.UserId] = true
giveReward(player)
end)Не храни счётчики в интерфейсе
Если количество монет живёт в TextLabel, а сервер верит тому, что там написано, — игру сломают в первый же день. Интерфейс только показывает число, которое посчитал сервер. Это правило не имеет исключений.
Чтобы NPC не выглядел статуей#
- повесь на него зацикленную анимацию стойки — даже лёгкое покачивание меняет впечатление
- разверни лицом к тому, откуда приходит игрок, а не в стену
- поставь рядом что-то, объясняющее, почему он тут стоит: прилавок, костёр, ящики
- добавь короткий звук на срабатывание подсказки — он делает нажатие «ощутимым»
Пошагово
6 шагов-
1
Поставь персонажа
Вкладка Avatar → Rig Builder, выбери тип скелета, поставь модель на место и включи
Anchoredу всех частей. -
2
Убери лишнее
У
Humanoidотключи показ имени и полоски здоровья, переименуй модель понятно. -
3
Добавь подсказку
Внутрь
HumanoidRootPartвложиProximityPrompt, задайActionText«Поговорить» и включиRequiresLineOfSight. -
4
Сделай облачко
Туда же добавь
BillboardGuiс именемBubble, внутрь —TextLabelс именемText. ПоставьEnabledв false: показывать его будет скрипт.Подними StudsOffset по Y, чтобы облачко висело над головой, а не в груди.
-
5
Вставь скрипт
Положи
Scriptиз гайда внутрь модели NPC и замени фразы на свои. -
6
Проверь вдвоём
Запусти тест на двух игроков (Test → Players: 2) и убедись, что реплика показывается тому, кто нажал, а награда выдаётся один раз.

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