Roblox · Хоррор · урок 7
Итог курса на одной странице: полная структура хоррора и финальные версии всех скриптов. Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Lighting — ClockTime 0, туман, Atmosphere, ColorCorrection, Bloom, Blur Workspace — StreamingEnabled выкл. ├─ Corridor (Model) — пол, стены, потолок, лампы ├─ Eyes (Model) ├─ EnvironmentTrigger (Part) — Footsteps, EnvironmentTrigger ├─ ShadowTrigger (Part) — ShadowTrigger ├─ JumpscareTrigger (Part) ├─ MonsterSpawn (Part) ├─ MonsterTrigger (Part) — MonsterTrigger ├─ SafeZone (Part) — PointLight, LightTrigger └─ Dust (Part) — ParticleEmitter ServerStorage ├─ Shadow (Model) — Sound └─ Monster (Model) — MonsterAnim SoundService — Scream, Ambient ServerScriptService └─ ScreamScript (Script) StarterPlayer → StarterCharacterScripts └─ RunScript, CrouchScript (LocalScript) — RunAnim, CrouchAnim StarterGui └─ JumpscareGui (ScreenGui) — Face, Scream, JumpscareLocal
Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Бег на Shift.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- скрипт лежит в персонаже
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- проигрывает анимации
local animator = humanoid:WaitForChild("Animator")
-- скорость бега
local RUN_SPEED = 25
-- обычная скорость
local WALK_SPEED = 16
-- Animation внутри скрипта
local runAnim = script:WaitForChild("RunAnim")
-- дорожка анимации (если есть своя)
local runTrack = nil
-- ID вставлен
if runAnim.AnimationId ~= "" then
-- загружаем
runTrack = animator:LoadAnimation(runAnim)
-- важнее обычной ходьбы
runTrack.Priority = Enum.AnimationPriority.Movement
-- по кругу
runTrack.Looped = true
-- конец проверки
end
-- сейчас бежим?
local running = false
-- включает и выключает анимацию бега
local function UpdateAnim()
-- своей анимации нет — бежим стандартной
if not runTrack then return end
-- персонаж двигается
local moving = humanoid.MoveDirection.Magnitude > 0
-- бежим, а анимация не играет
if running and moving and not runTrack.IsPlaying then
-- включаем плавно
runTrack:Play(0.2)
-- остановились
elseif (not running or not moving) and runTrack.IsPlaying then
-- выключаем
runTrack:Stop(0.2)
-- конец проверки
end
-- конец функции
end
-- начать или закончить бег
local function SetRunning(value)
-- погибшие не бегают
if humanoid.Health <= 0 then return end
-- сидим? (ставит скрипт приседания)
local crouching = character:GetAttribute("Crouching")
-- сидя не бегаем
running = value and not crouching
-- скоростью сидя управляет другой скрипт
if not crouching then
-- бег или шаг
humanoid.WalkSpeed = running and RUN_SPEED or WALK_SPEED
-- конец проверки
end
-- анимация
UpdateAnim()
-- конец функции
end
-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
-- левый Shift, и не в чате
if not processed and input.KeyCode == Enum.KeyCode.LeftShift then
-- бежим
SetRunning(true)
-- конец проверки
end
-- конец функции
end)
-- отпустили клавишу
UIS.InputEnded:Connect(function(input)
-- это Shift
if input.KeyCode == Enum.KeyCode.LeftShift then
-- идём шагом
SetRunning(false)
-- конец проверки
end
-- конец функции
end)
-- встали или пошли — обновляем анимацию
humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(UpdateAnim)
-- присели во время бега
character:GetAttributeChangedSignal("Crouching"):Connect(function()
-- бег прекращается
if character:GetAttribute("Crouching") then SetRunning(false) end
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Присед на C.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- анимации
local animator = humanoid:WaitForChild("Animator")
-- скорость сидя
local CROUCH_SPEED = 6
-- обычная скорость
local WALK_SPEED = 16
-- Animation внутри скрипта
local crouchAnim = script:WaitForChild("CrouchAnim")
-- дорожка анимации
local track = nil
-- своя анимация есть
if crouchAnim.AnimationId ~= "" then
-- загружаем
track = animator:LoadAnimation(crouchAnim)
-- поверх ходьбы и стояния
track.Priority = Enum.AnimationPriority.Action
-- по кругу
track.Looped = true
-- конец проверки
end
-- сидим?
local crouching = false
-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
-- нужна C и не в чате
if processed or input.KeyCode ~= Enum.KeyCode.C then return end
-- погибшие не приседают
if humanoid.Health <= 0 then return end
-- C переключает: сесть / встать
crouching = not crouching
-- сообщаем скрипту бега
character:SetAttribute("Crouching", crouching)
-- сели
if crouching then
-- медленно
humanoid.WalkSpeed = CROUCH_SPEED
-- камера ниже — видно и без анимации
humanoid.CameraOffset = Vector3.new(0, -1.5, 0)
-- анимация приседа
if track then track:Play(0.2) end
-- встали
else
-- обычная скорость
humanoid.WalkSpeed = WALK_SPEED
-- камера на место
humanoid.CameraOffset = Vector3.zero
-- выключаем анимацию
if track then track:Stop(0.2) end
-- конец проверки
end
-- конец функции
end)Где: Workspace → ShadowTrigger · Script
Зачем: Тень.
-- здесь лежит тень
local SS = game:GetService("ServerStorage")
-- игроки
local Players = game:GetService("Players")
-- невидимая зона
local trigger = script.Parent
-- сколько секунд тень стоит
local SHOW_TIME = 5
-- срабатывает один раз за игру
local used = false
-- касание зоны
trigger.Touched:Connect(function(hit)
-- уже было или это не игрок
if used or not Players:GetPlayerFromCharacter(hit.Parent) then return end
-- больше не срабатывает
used = true
-- копия тени — на том месте, где её построили
local shadow = SS:WaitForChild("Shadow"):Clone()
-- появляется
shadow.Parent = workspace
-- звук внутри тени
local sound = shadow:FindFirstChildWhichIsA("Sound", true)
-- шёпот
if sound then sound:Play() end
-- стоит
task.wait(SHOW_TIME)
-- исчезает
shadow:Destroy()
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Крик вдалеке.
-- звуки отсюда слышат все игроки
local SoundService = game:GetService("SoundService")
-- крик
local scream = SoundService:WaitForChild("Scream")
-- не раньше чем через 45 секунд
local MIN_TIME = 45
-- и не позже чем через 90
local MAX_TIME = 90
-- бесконечно
while true do
-- случайная пауза — нельзя привыкнуть
task.wait(math.random(MIN_TIME, MAX_TIME))
-- крик где-то вдалеке
scream:Play()
-- конец цикла
endГде: Workspace → EnvironmentTrigger · Script
Зачем: Глаза и шаги.
-- игроки
local Players = game:GetService("Players")
-- зона
local trigger = script.Parent
-- глаза в темноте
local eyes = workspace:WaitForChild("Eyes")
-- шаги
local footsteps = trigger:WaitForChild("Footsteps")
-- один раз
local used = false
-- касание
trigger.Touched:Connect(function(hit)
-- уже было или не игрок
if used or not Players:GetPlayerFromCharacter(hit.Parent) then return end
-- больше не срабатывает
used = true
-- глаза пропали
eyes:Destroy()
-- и кто-то пошёл
footsteps:Play()
-- конец функции
end)Где: StarterGui → JumpscareGui · LocalScript
Зачем: Скример.
-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- JumpscareGui
local gui = script.Parent
-- звук скримера
local scream = gui:WaitForChild("Scream")
-- зона
local trigger = workspace:WaitForChild("JumpscareTrigger")
-- сколько секунд картинка на экране
local SHOW_TIME = 1.5
-- каждый игрок пугается один раз
local used = false
-- касание зоны
trigger.Touched:Connect(function(hit)
-- уже было или это не мы
if used or hit.Parent ~= player.Character then return end
-- больше не срабатывает
used = true
-- картинка на весь экран
gui.Enabled = true
-- крик
scream:Play()
-- держим
task.wait(SHOW_TIME)
-- прячем, но не удаляем
gui.Enabled = false
-- конец функции
end)Где: ServerStorage → Monster · Script
Зачем: Ходьба монстра.
-- модель монстра
local monster = script.Parent
-- Humanoid
local humanoid = monster:WaitForChild("Humanoid")
-- Animator (создаём, если нет)
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- стандартная ходьба R15; для R6 — 180426354
local WALK_ID = "rbxassetid://507777826"
-- анимация
local walk = Instance.new("Animation")
-- какая
walk.AnimationId = WALK_ID
-- загружаем
local track = animator:LoadAnimation(walk)
-- по кругу
track.Looped = true
-- скорость монстра изменилась
humanoid.Running:Connect(function(speed)
-- пошёл
if speed > 0.5 and not track.IsPlaying then
-- шагаем
track:Play()
-- остановился
elseif speed <= 0.5 and track.IsPlaying then
-- стоим
track:Stop()
-- конец проверки
end
-- конец функции
end)Где: Workspace → MonsterTrigger · Script
Зачем: Погоня.
-- образец монстра
local SS = game:GetService("ServerStorage")
-- игроки
local Players = game:GetService("Players")
-- зона
local trigger = script.Parent
-- где появляется монстр
local spawnPoint = workspace:WaitForChild("MonsterSpawn")
-- ближе — поймал
local KILL_DISTANCE = 5
-- монстр уже гонится
local active = false
-- погоня за игроком
local function Chase(monster, player)
-- Humanoid монстра
local mHumanoid = monster:WaitForChild("Humanoid")
-- центр монстра
local mRoot = monster:WaitForChild("HumanoidRootPart")
-- движение считает сервер — без рывков
mRoot:SetNetworkOwner(nil)
-- пока монстр в игре
while monster.Parent and mHumanoid.Health > 0 do
-- персонаж цели
local character = player.Character
-- её Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- её центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- цель погибла или ушла — конец погони
if not humanoid or not root or humanoid.Health <= 0 then break end
-- бежим к игроку
mHumanoid:MoveTo(root.Position)
-- догнал
if (root.Position - mRoot.Position).Magnitude <= KILL_DISTANCE then
-- поймал
humanoid.Health = 0
-- конец погони
break
-- конец проверки
end
-- 10 раз в секунду
task.wait(0.1)
-- конец цикла
end
-- монстр ещё секунду-две стоит
task.wait(2)
-- и исчезает
monster:Destroy()
-- конец функции
end
-- касание зоны
trigger.Touched:Connect(function(hit)
-- монстр уже бежит
if active then return end
-- кто коснулся
local player = Players:GetPlayerFromCharacter(hit.Parent)
-- образец
local sample = SS:FindFirstChild("Monster")
-- не игрок или монстра нет
if not player or not sample then return end
-- занято
active = true
-- копия
local monster = sample:Clone()
-- на точку появления
monster:PivotTo(spawnPoint.CFrame)
-- в игру
monster.Parent = workspace
-- гонимся (ждём, пока закончится)
Chase(monster, player)
-- можно снова
active = false
-- конец функции
end)Где: Workspace → SafeZone · Script
Зачем: Свет прогоняет монстра.
-- игроки
local Players = game:GetService("Players")
-- светлая зона
local trigger = script.Parent
-- касание
trigger.Touched:Connect(function(hit)
-- только игрок
if not Players:GetPlayerFromCharacter(hit.Parent) then return end
-- монстр в игре?
local monster = workspace:FindFirstChild("Monster")
-- свет его прогоняет — погоня закончится
if monster then monster:Destroy() end
-- конец функции
end)Запусти Play (или Run) и убедись: