--[[ ПЕРВОЕ ЛИЦО + РУКИ + ФОНАРИК + ИНВЕНТАРЬ (LocalScript → StarterPlayer → StarterPlayerScripts, имя "HorrorFirstPerson") E — подобрать/открыть, 1–5 — предмет в руку, ЛКМ/R — использовать, G — выбросить, F — фонарик, Shift — бег, C/Ctrl — присесть, Q/Z — выглянуть влево/вправо Наблюдателя держит твой ВЗГЛЯД, Ползуна — СВЕТ фонарика. ]] local Players = game:GetService("Players") local RunService = game:GetService("RunService") local UIS = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local PPS = game:GetService("ProximityPromptService") local player = Players.LocalPlayer local remote = ReplicatedStorage:WaitForChild("ItemRemote") local models = ReplicatedStorage:WaitForChild("ItemModels") local WALK, RUN, CROUCH = 9, 16, 5 local BASE_FOV, RUN_FOV = 70, 78 local STAMINA_MAX, DRAIN, REGEN = 100, 22, 12 local MAX_SLOTS = 5 local TORCH_BRIGHT, BATTERY_DRAIN = 3, 0.6 local TORCH_ANGLE, TORCH_RANGE = 28, 45 -- в этом конусе фонарик «держит» Ползуна local SLEEVE, SKIN, LIGHT = Color3.fromRGB(38, 36, 34), Color3.fromRGB(234, 184, 146), Color3.fromRGB(255, 240, 215) local SOUNDS = {heart = "", click = "", rip = "", scream = ""} -- вставь свои id звуков: "rbxassetid://..." local sin, cos, abs, min, max, clamp, rad = math.sin, math.cos, math.abs, math.min, math.max, math.clamp, math.rad player.CameraMode = Enum.CameraMode.LockFirstPerson UIS.MouseIconEnabled = false local function sound(id, vol, looped) if id == "" then return nil end local s = Instance.new("Sound") s.SoundId, s.Volume, s.Looped = id, vol or 0.6, looped or false s.Parent = workspace.CurrentCamera return s end local function playOnce(id, vol) local s = sound(id, vol) if s then s:Play(); s.Ended:Once(function() s:Destroy() end) end end local heartSnd = sound(SOUNDS.heart, 0, true) if heartSnd then heartSnd:Play() end ---------------------------------------------------------------- РУКИ local vm = Instance.new("Model") vm.Name = "Arms" local vmParts, skinParts = {}, {} local function add(size, offset, color, mat, shape, group) local p = Instance.new("Part") if shape then p.Shape = shape end p.Size, p.Color, p.Material = size, color, mat or Enum.Material.SmoothPlastic p.Anchored, p.CanCollide, p.CanQuery, p.CanTouch, p.CastShadow = true, false, false, false, false p.Parent = vm table.insert(vmParts, {p, offset, group}) return p end local FWD = CFrame.Angles(0, rad(90), 0) local rArm = CFrame.new(0.7, -0.7, -0.55) * CFrame.Angles(rad(8), rad(12), 0) local lArm = CFrame.new(-0.75, -0.8, -0.55) * CFrame.Angles(rad(12), rad(-14), 0) add(Vector3.new(0.36, 0.36, 1.3), rArm, SLEEVE, Enum.Material.Fabric, nil, "R") table.insert(skinParts, add(Vector3.new(0.32, 0.34, 0.36), rArm * CFrame.new(0, 0, -0.8), SKIN, nil, nil, "R")) add(Vector3.new(0.36, 0.36, 1.3), lArm, SLEEVE, Enum.Material.Fabric, nil, "L") table.insert(skinParts, add(Vector3.new(0.32, 0.34, 0.36), lArm * CFrame.new(0, 0, -0.8), SKIN, nil, nil, "L")) for i = -1, 2 do -- пальцы левой руки table.insert(skinParts, add(Vector3.new(0.07, 0.07, 0.22), lArm * CFrame.new(i * 0.075 + 0.02, 0.12, -1.02), SKIN, nil, nil, "L")) end local torchCF = rArm * CFrame.new(0, 0.17, -0.85) local DARK = Color3.fromRGB(30, 30, 30) add(Vector3.new(0.7, 0.2, 0.2), torchCF * FWD, DARK, Enum.Material.Metal, Enum.PartType.Cylinder, "R") add(Vector3.new(0.25, 0.28, 0.28), torchCF * CFrame.new(0, 0, -0.45) * FWD, Color3.fromRGB(40, 40, 40), Enum.Material.Metal, Enum.PartType.Cylinder, "R") local button = add(Vector3.new(0.08, 0.05, 0.1), torchCF * CFrame.new(0, 0.11, -0.1), Color3.fromRGB(150, 30, 30), nil, nil, "R") local lens = add(Vector3.new(0.04, 0.24, 0.24), torchCF * CFrame.new(0, 0, -0.59) * FWD, LIGHT, Enum.Material.Neon, Enum.PartType.Cylinder, "R") local lightPart = add(Vector3.new(0.1, 0.1, 0.1), CFrame.new(0.4, -0.3, -1.3), Color3.new(), nil, nil, "R") lightPart.Transparency = 1 local spot = Instance.new("SpotLight") spot.Face, spot.Angle, spot.Range, spot.Brightness, spot.Shadows, spot.Color = Enum.NormalId.Front, 50, TORCH_RANGE, TORCH_BRIGHT, true, LIGHT spot.Parent = lightPart local glow = Instance.new("PointLight") glow.Range, glow.Brightness, glow.Color = 5, 0.25, LIGHT glow.Parent = lightPart ---------------------------------------------------------------- ИНТЕРФЕЙС local gui = Instance.new("ScreenGui") gui.Name, gui.IgnoreGuiInset, gui.ResetOnSpawn = "HorrorUI", true, false gui.Parent = player:WaitForChild("PlayerGui") local function frame(props, parent) local f = Instance.new(props.Class or "Frame") f.BorderSizePixel = 0 for k, v in pairs(props) do if k ~= "Class" then f[k] = v end end f.Parent = parent or gui return f end local function label(props, parent) props.Class, props.BackgroundTransparency = "TextLabel", 1 props.TextStrokeTransparency = props.TextStrokeTransparency or 0.6 return frame(props, parent) end for _, e in ipairs({{90, 1, 0.3, 0, 0}, {-90, 1, 0.3, 0, 0.7}, {0, 0.25, 1, 0, 0}, {180, 0.25, 1, 0.75, 0}}) do local f = frame({BackgroundColor3 = Color3.new(), Size = UDim2.fromScale(e[2], e[3]), Position = UDim2.fromScale(e[4], e[5])}) local g = Instance.new("UIGradient") g.Rotation, g.Transparency = e[1], NumberSequence.new(0.05, 1) g.Parent = f end local dot = frame({AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5), Size = UDim2.fromOffset(4, 4), BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 0.5}) Instance.new("UICorner", dot).CornerRadius = UDim.new(1, 0) local barBack = frame({AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -92), Size = UDim2.fromOffset(200, 4), BackgroundColor3 = Color3.new(), BackgroundTransparency = 0.5, Visible = false}) local barFill = frame({Size = UDim2.fromScale(1, 1)}, barBack) local chargeLabel = label({AnchorPoint = Vector2.new(0, 1), Position = UDim2.new(0.5, 176, 1, -30), Size = UDim2.fromOffset(90, 30), Font = Enum.Font.GothamBold, TextSize = 16, TextXAlignment = Enum.TextXAlignment.Left}) local toastLabel = label({Size = UDim2.new(1, 0, 0, 30), Position = UDim2.new(0, 0, 1, -130), Font = Enum.Font.GothamMedium, TextSize = 20, TextColor3 = Color3.fromRGB(235, 230, 220), TextTransparency = 1}) local fearFrame = frame({Size = UDim2.fromScale(1, 1), BackgroundColor3 = Color3.fromRGB(120, 0, 0), BackgroundTransparency = 1}) local toastId = 0 local function toast(text) toastId += 1 local my = toastId toastLabel.Text, toastLabel.TextTransparency, toastLabel.TextStrokeTransparency = text, 0, 0.5 task.delay(2.5, function() if toastId == my then TweenService:Create(toastLabel, TweenInfo.new(0.6), {TextTransparency = 1, TextStrokeTransparency = 1}):Play() end end) end local hint = label({Size = UDim2.new(1, 0, 0, 30), Position = UDim2.new(0, 0, 1, -165), Font = Enum.Font.Gotham, TextSize = 15, TextColor3 = Color3.fromRGB(200, 200, 200), Text = "E — подобрать 1–5 — в руку ЛКМ/R — использовать G — выбросить F — фонарик Shift — бег C — присесть Q/Z — выглянуть"}) task.delay(14, function() TweenService:Create(hint, TweenInfo.new(2), {TextTransparency = 1, TextStrokeTransparency = 1}):Play() end) local paper = frame({AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.45), Size = UDim2.fromOffset(440, 320), BackgroundColor3 = Color3.fromRGB(225, 214, 182), Visible = false, ZIndex = 5}) local paperText = label({Position = UDim2.fromOffset(24, 20), Size = UDim2.new(1, -48, 1, -60), Font = Enum.Font.SourceSansItalic, TextSize = 24, TextWrapped = true, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, TextColor3 = Color3.fromRGB(45, 30, 20), TextStrokeTransparency = 1, ZIndex = 6}, paper) label({AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -8), Size = UDim2.new(1, 0, 0, 24), Font = Enum.Font.Gotham, TextSize = 14, TextColor3 = Color3.fromRGB(110, 90, 70), TextStrokeTransparency = 1, Text = "R / ЛКМ — закрыть", ZIndex = 6}, paper) local reading = false local function openNote(text) paperText.Text = text or "…"; paper.Visible = true; reading = true end local function closeNote() paper.Visible = false; reading = false end local function bigText(text, color, hold) local f = label({Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Text = text, Font = Enum.Font.Fantasy, TextSize = 46, TextColor3 = color, TextTransparency = 1, ZIndex = 10}) f.BackgroundColor3 = Color3.new() TweenService:Create(f, TweenInfo.new(1.5), {BackgroundTransparency = 0.25, TextTransparency = 0}):Play() task.delay(hold, function() local t = TweenService:Create(f, TweenInfo.new(1.5), {BackgroundTransparency = 1, TextTransparency = 1}) t:Play(); t.Completed:Wait(); f:Destroy() end) end ---------------------------------------------------------------- ФОНАРИК local torchOn, charge = true, 100 local clickT = 0 local function setTorch(on) if on and charge <= 0 then toast("Батарейка села. Найди новую"); on = false end torchOn = on spot.Enabled, glow.Enabled = on, on lens.Material = on and Enum.Material.Neon or Enum.Material.SmoothPlastic lens.Color = on and LIGHT or Color3.fromRGB(70, 70, 70) clickT = 0.25 -- большой палец жмёт кнопку playOnce(SOUNDS.click, 0.5) end task.spawn(function() -- мигает сам, чаще при слабой батарейке while true do task.wait(charge < 20 and math.random(2, 5) or math.random(10, 25)) if torchOn then for _ = 1, math.random(3, 7) do spot.Enabled = false; task.wait(0.03 + math.random() * 0.08) spot.Enabled = torchOn; task.wait(0.03 + math.random() * 0.12) end end end end) ---------------------------------------------------------------- ИНВЕНТАРЬ (модели из ItemModels) local inventory, selected, heldEntries, heldModel = {}, nil, {}, nil local slots, selectSlot = {}, nil local reachT, inspectT = 0, 0 local HOLD = { battery = CFrame.Angles(rad(-70), 0, 0), key = CFrame.Angles(rad(10), rad(20), 0), note = CFrame.Angles(rad(70), 0, 0), amulet = CFrame.Angles(rad(60), 0, 0), } local function cloneItem(id, scale) local t = models:FindFirstChild(id) if not t then return nil end local m = t:Clone() if scale ~= 1 then m:ScaleTo(scale) end for _, p in ipairs(m:GetDescendants()) do if p:IsA("BasePart") then p.Anchored, p.CanCollide, p.CanQuery, p.CanTouch, p.CastShadow = true, false, false, false, false end if p:IsA("ProximityPrompt") or p:IsA("WeldConstraint") then p:Destroy() end end m:PivotTo(CFrame.new()) return m end local function fillViewport(vp, data) vp:ClearAllChildren() local m = data and cloneItem(data.id, 1) if not m then return end m.Parent = vp local _, size = m:GetBoundingBox() local d = max(size.X, size.Y, size.Z) * 1.4 local cam = Instance.new("Camera") cam.FieldOfView, cam.CFrame = 40, CFrame.lookAt(Vector3.new(d * 0.6, d * 0.8, d), Vector3.zero) cam.Parent = vp vp.CurrentCamera = cam end local function setHeld(data) for _, e in ipairs(heldEntries) do table.remove(vmParts, table.find(vmParts, e)) e[1]:Destroy() end table.clear(heldEntries) if heldModel then heldModel:Destroy(); heldModel = nil end local m = data and cloneItem(data.id, 0.7) if not m then return end local base = lArm * CFrame.new(0.05, 0.25, -1.05) * (HOLD[data.id] or CFrame.new()) for _, p in ipairs(m:GetDescendants()) do if p:IsA("BasePart") then local e = {p, base * p.CFrame, "L"} table.insert(heldEntries, e); table.insert(vmParts, e) end end m.Parent = vm heldModel = m inspectT = 1.2 -- достаёт и рассматривает end local bar = frame({AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -16), Size = UDim2.fromOffset(MAX_SLOTS * 68 - 8, 60), BackgroundTransparency = 1}) local layout = Instance.new("UIListLayout") layout.FillDirection, layout.Padding, layout.SortOrder = Enum.FillDirection.Horizontal, UDim.new(0, 8), Enum.SortOrder.LayoutOrder layout.Parent = bar for i = 1, MAX_SLOTS do local b = frame({Class = "TextButton", LayoutOrder = i, Size = UDim2.fromOffset(60, 60), BackgroundColor3 = Color3.new(), BackgroundTransparency = 0.45, AutoButtonColor = false, Text = ""}, bar) Instance.new("UICorner", b).CornerRadius = UDim.new(0, 8) local stroke = Instance.new("UIStroke") stroke.ApplyStrokeMode, stroke.Color, stroke.Thickness, stroke.Transparency = Enum.ApplyStrokeMode.Border, Color3.new(1, 1, 1), 2, 0.85 stroke.Parent = b local vp = frame({Class = "ViewportFrame", Position = UDim2.fromOffset(4, 4), Size = UDim2.new(1, -8, 1, -8), BackgroundTransparency = 1, Ambient = Color3.fromRGB(170, 170, 170), LightColor = Color3.new(1, 1, 1)}, b) label({Position = UDim2.fromOffset(5, 3), Size = UDim2.fromOffset(14, 14), Font = Enum.Font.GothamBold, TextSize = 12, TextColor3 = Color3.fromRGB(170, 170, 170), Text = tostring(i), ZIndex = 2}, b) b.Activated:Connect(function() selectSlot(i) end) slots[i] = {vp = vp, stroke = stroke} end local function refresh() if selected and not inventory[selected] then selected = nil end for i, s in ipairs(slots) do fillViewport(s.vp, inventory[i]) s.stroke.Transparency = i == selected and 0 or 0.85 s.stroke.Color = i == selected and Color3.fromRGB(255, 215, 140) or Color3.new(1, 1, 1) end setHeld(selected and inventory[selected]) end function selectSlot(i) if reading then closeNote() end if selected == i or not inventory[i] then selected = nil else selected = i; toast(inventory[i].name) end refresh() end local function useHeld() if reading then closeNote(); return end local data = selected and inventory[selected] if not data then return end if data.id == "note" then openNote(data.text) elseif data.id == "battery" then if charge >= 99 then toast("Фонарик и так заряжен") else remote:FireServer("use", selected) end elseif data.id == "key" then toast("Старый ключ. Где-то должен быть замок…") elseif data.id == "amulet" then toast("Амулет тёплый и тихо пульсирует…") end inspectT = 0.8 end local function dropHeld() if not (selected and inventory[selected]) then return end if reading then closeNote() end remote:FireServer("drop", selected) selected = nil refresh() end ---------------------------------------------------------------- СМЕРТЬ: КАМЕРА local cutscene = nil -- {monster, t0, ripT} local function onCaught(monster) closeNote() cutscene = {monster = monster, t0 = os.clock()} workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable playOnce(SOUNDS.scream, 1) end local function onRip() if not cutscene then return end cutscene.ripT = os.clock() playOnce(SOUNDS.rip, 1) fearFrame.BackgroundColor3, fearFrame.BackgroundTransparency = Color3.fromRGB(200, 0, 0), 0 TweenService:Create(fearFrame, TweenInfo.new(2.5), {BackgroundTransparency = 0.45}):Play() task.delay(1.6, function() bigText("ТЕБЯ ПОЙМАЛИ", Color3.fromRGB(200, 20, 20), 3.5) end) end remote.OnClientEvent:Connect(function(kind, value) if kind == "inv" then if #value < #inventory then selected = nil end inventory = value refresh() elseif kind == "msg" then toast(value) elseif kind == "battery" then charge = 100; setTorch(true); toast("Фонарик заряжен") elseif kind == "win" then bigText("ТЫ НАШЁЛ АМУЛЕТ\n\nДом наконец затих…", Color3.fromRGB(230, 200, 200), 7) elseif kind == "caught" then onCaught(value) elseif kind == "rip" then onRip() end end) remote:FireServer("sync") PPS.PromptTriggered:Connect(function() reachT = 0.45 end) -- рука тянется за предметом ---------------------------------------------------------------- УПРАВЛЕНИЕ local NUM = {[Enum.KeyCode.One] = 1, [Enum.KeyCode.Two] = 2, [Enum.KeyCode.Three] = 3, [Enum.KeyCode.Four] = 4, [Enum.KeyCode.Five] = 5} local runHeld, crouch, leanL, leanR = false, false, false, false UIS.InputBegan:Connect(function(input, gp) if gp or cutscene then return end local k = input.KeyCode if k == Enum.KeyCode.F then setTorch(not torchOn) elseif k == Enum.KeyCode.LeftShift then runHeld = true elseif k == Enum.KeyCode.C or k == Enum.KeyCode.LeftControl then crouch = not crouch elseif k == Enum.KeyCode.Q then leanL = true elseif k == Enum.KeyCode.Z then leanR = true elseif NUM[k] then selectSlot(NUM[k]) elseif k == Enum.KeyCode.R or input.UserInputType == Enum.UserInputType.MouseButton1 then useHeld() elseif k == Enum.KeyCode.G then dropHeld() end end) UIS.InputEnded:Connect(function(input) local k = input.KeyCode if k == Enum.KeyCode.LeftShift then runHeld = false elseif k == Enum.KeyCode.Q then leanL = false elseif k == Enum.KeyCode.Z then leanR = false end end) if UIS.TouchEnabled then for _, b in ipairs({{"🔦", -170, -170, function() setTorch(not torchOn) end}, {"✋", -250, -170, useHeld}, {"⬇", -170, -250, dropHeld}, {"🧎", -250, -250, function() crouch = not crouch end}}) do local btn = frame({Class = "TextButton", Size = UDim2.fromOffset(64, 64), Position = UDim2.new(1, b[2], 1, b[3]), BackgroundColor3 = Color3.new(), BackgroundTransparency = 0.5, Text = b[1], TextScaled = true, TextColor3 = Color3.new(1, 1, 1)}) Instance.new("UICorner", btn).CornerRadius = UDim.new(1, 0) btn.Activated:Connect(b[4]) end end ---------------------------------------------------------------- ПЕРСОНАЖ local stamina, exhausted, lastCF = STAMINA_MAX, false, nil local landDip, wasAir = 0, false local function onCharacter(char) stamina, exhausted, lastCF, crouch, cutscene = STAMINA_MAX, false, nil, false, nil fearFrame.BackgroundTransparency = 1 local cam = workspace.CurrentCamera cam.CameraType, cam.FieldOfView = Enum.CameraType.Custom, BASE_FOV local hum = char:WaitForChild("Humanoid") hum.WalkSpeed = WALK if not player:HasAppearanceLoaded() then player.CharacterAppearanceLoaded:Wait() end local arm = char:FindFirstChild("RightHand") or char:FindFirstChild("Right Arm") if arm then for _, p in ipairs(skinParts) do p.Color = arm.Color end end end player.CharacterAdded:Connect(onCharacter) if player.Character then task.spawn(onCharacter, player.Character) end ---------------------------------------------------------------- ВЗГЛЯД И СВЕТ → СЕРВЕР local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Exclude local fear = 0 local function visible(from, part, monster) rayParams.FilterDescendantsInstances = {player.Character, vm} local hit = workspace:Raycast(from, part.Position - from, rayParams) return not hit or hit.Instance:IsDescendantOf(monster) end task.spawn(function() while true do task.wait(0.15) local folder = workspace:FindFirstChild("Monsters") local cam = workspace.CurrentCamera local looking, lit, nearest = {}, {}, 99 if folder and not cutscene then local camCF, lpos = cam.CFrame, lightPart.Position for _, m in ipairs(folder:GetChildren()) do local head, torso = m:FindFirstChild("Head"), m:FindFirstChild("Torso") if head and torso then nearest = min(nearest, (torso.Position - camCF.Position).Magnitude) for _, p in ipairs({head, torso}) do local to = p.Position - camCF.Position if to.Unit:Dot(camCF.LookVector) > 0.55 and visible(camCF.Position, p, m) then if not table.find(looking, m.Name) then table.insert(looking, m.Name) end end local tl = p.Position - lpos local ang = math.deg(math.acos(clamp(tl.Unit:Dot(lightPart.CFrame.LookVector), -1, 1))) if torchOn and ang < TORCH_ANGLE and tl.Magnitude < TORCH_RANGE and visible(lpos, p, m) then if not table.find(lit, m.Name) then table.insert(lit, m.Name) end end end end end end fear = clamp(1 - (nearest - 6) / 30, 0, 1) remote:FireServer("sight", looking, lit) end end) ---------------------------------------------------------------- КАЖДЫЙ КАДР local bobT, bobAmt, runA, push, crouchA, leanA, heartT = 0, 0, 0, 0, 0, 0, 0 local sway = CFrame.new() local RUN_POSE = CFrame.new(0.05, -0.15, 0.1) * CFrame.Angles(rad(-12), rad(10), rad(8)) local function deathCamera(camera, dt) local cs = cutscene local mon = cs.monster local head = mon and mon:FindFirstChild("Head") local char = player.Character local myHead = char and char:FindFirstChild("Head") vm.Parent = nil if not cs.ripT then -- смотрим монстру в лицо, всё трясётся local t = os.clock() - cs.t0 local from = myHead and myHead.Position or camera.CFrame.Position local shake = Vector3.new(math.random() - 0.5, math.random() - 0.5, 0) * (t > 0.5 and 0.25 or 0.08) local goal = head and head.Position or from + camera.CFrame.LookVector camera.CFrame = camera.CFrame:Lerp(CFrame.lookAt(from + shake, goal), min(1, dt * 10)) camera.FieldOfView += (55 - camera.FieldOfView) * min(1, dt * 3) fearFrame.BackgroundTransparency = 0.85 - min(t, 2) * 0.2 else -- голова в лапе монстра, тело падает на пол — камера валится вместе с ним local t = os.clock() - cs.ripT local body = char and (char:FindFirstChild("UpperTorso") or char:FindFirstChild("Torso") or char:FindFirstChild("HumanoidRootPart")) if body then local fall = clamp((t - 0.6) / 1.2, 0, 1) ^ 2 local p = body.Position + Vector3.new(0, 1.3 - fall * 1.6, 0) local tilt = CFrame.Angles(0, 0, rad(75) * fall) local look = (myHead and myHead.Position) or (head and head.Position) or p + Vector3.new(0, 0, -1) local target = CFrame.lookAt(p, look) * tilt camera.CFrame = camera.CFrame:Lerp(target, min(1, dt * 8)) end camera.FieldOfView += (80 - camera.FieldOfView) * min(1, dt * 2) end end RunService:BindToRenderStep("HorrorFirstPerson", Enum.RenderPriority.Camera.Value + 1, function(dt) local camera = workspace.CurrentCamera if cutscene then deathCamera(camera, dt); return end local char = player.Character local hum = char and char:FindFirstChildOfClass("Humanoid") if not (hum and hum.Health > 0) then vm.Parent = nil; return end vm.Parent = camera local t = os.clock() if torchOn then charge = max(0, charge - BATTERY_DRAIN * dt) if charge <= 0 then setTorch(false); toast("Фонарик сел. Найди батарейку") end end spot.Brightness = TORCH_BRIGHT * clamp(0.35 + charge / 30, 0.35, 1) chargeLabel.Text = "🔦 " .. math.ceil(charge) .. "%" chargeLabel.TextColor3 = charge < 20 and Color3.fromRGB(230, 80, 70) or Color3.fromRGB(220, 220, 220) local moving = hum.MoveDirection.Magnitude > 0.1 local running = runHeld and moving and not exhausted and not crouch if running then stamina = max(0, stamina - DRAIN * dt) if stamina <= 0 then exhausted = true end else stamina = min(STAMINA_MAX, stamina + REGEN * dt * (crouch and 1.4 or 1)) if exhausted and stamina >= STAMINA_MAX * 0.35 then exhausted = false end end hum.WalkSpeed = crouch and CROUCH or running and RUN or WALK local a = min(1, dt * 7) camera.FieldOfView += ((running and RUN_FOV or BASE_FOV) - fear * 6 - camera.FieldOfView) * a barFill.Size = UDim2.fromScale(stamina / STAMINA_MAX, 1) barFill.BackgroundColor3 = exhausted and Color3.fromRGB(170, 40, 40) or Color3.fromRGB(220, 220, 220) barBack.Visible = stamina < STAMINA_MAX -- приземление: камера проседает local inAir = hum.FloorMaterial == Enum.Material.Air if wasAir and not inAir then landDip = 0.35 end wasAir = inAir landDip = max(0, landDip - dt * 1.2) -- страх: сердцебиение, красные края, дрожь heartT += dt * (1.2 + fear * 2.2) local beat = max(0, sin(heartT * math.pi * 2)) ^ 8 * fear fearFrame.BackgroundTransparency = 1 - beat * 0.25 if heartSnd then heartSnd.Volume, heartSnd.PlaybackSpeed = fear * 0.9, 1 + fear * 0.6 end local onGround = not inAir bobAmt += (((moving and onGround) and (running and 1.8 or crouch and 0.6 or 1) or 0) - bobAmt) * a runA += ((running and 1 or 0) - runA) * a crouchA += ((crouch and 1 or 0) - crouchA) * min(1, dt * 8) leanA += (((leanL and 1 or 0) - (leanR and 1 or 0)) - leanA) * min(1, dt * 8) bobT += dt * (running and 13 or crouch and 6 or 8.5) local bx = sin(bobT) * 0.05 * bobAmt local by = abs(cos(bobT)) * 0.06 * bobAmt local breathe = sin(t * (1.7 + fear * 3 + (exhausted and 3 or 0))) * (0.015 + (exhausted and 0.02 or 0)) local tremble = fear * 0.006 local camCF = camera.CFrame if lastCF then local rx, ry = lastCF:ToObjectSpace(camCF):ToOrientation() local k = 1 / max(dt * 60, 0.001) sway = sway:Lerp(CFrame.Angles(clamp(-rx * k * 0.8, -0.15, 0.15), clamp(-ry * k * 0.8, -0.15, 0.15), 0), min(1, dt * 10)) end -- приседание, наклон и качка камеры hum.CameraOffset = Vector3.new(-leanA * 1.3, -crouchA * 1.6 - landDip * 2, 0) camera.CFrame = camCF * CFrame.new(bx * 0.3, by * 0.4, 0) * CFrame.Angles((math.random() - 0.5) * tremble, (math.random() - 0.5) * tremble, bx * 0.12 + leanA * rad(14)) lastCF = camCF rayParams.FilterDescendantsInstances = {char, vm} local hit = workspace:Raycast(camCF.Position, camCF.LookVector * 2.2, rayParams) push += ((hit and (2.2 - hit.Distance) or 0) - push) * min(1, dt * 15) -- жесты рук reachT, inspectT, clickT = max(0, reachT - dt), max(0, inspectT - dt), max(0, clickT - dt) local reach = sin(clamp(reachT / 0.45, 0, 1) * math.pi) local inspect = sin(clamp(inspectT / 1.2, 0, 1) * math.pi) local L = CFrame.new(0.25 * reach + 0.1 * inspect, 0.15 * reach + 0.12 * inspect, -0.6 * reach) * CFrame.Angles(rad(15) * inspect, rad(25) * inspect + rad(20) * reach, sin(t * 3) * 0.1 * inspect) local R = CFrame.new(0, -0.03 * sin(clamp(clickT / 0.25, 0, 1) * math.pi), 0) button.Color = torchOn and Color3.fromRGB(200, 40, 40) or Color3.fromRGB(90, 20, 20) local root = camera.CFrame * sway * CFrame.new():Lerp(RUN_POSE, runA) * CFrame.new(bx, by + breathe - push * 0.3 - crouchA * 0.05, push * 0.8) * CFrame.Angles(0, 0, -leanA * rad(6)) for _, item in ipairs(vmParts) do local g = item[3] == "L" and L or item[3] == "R" and R or CFrame.new() item[1].CFrame = root * g * item[2] end end)