Загрузка данных
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Lighting = game:GetService("Lighting")
local GuiService = game:GetService("GuiService")
local GameSettings = UserSettings():GetService("UserGameSettings")
-- ================= НАСТРОЙКИ =================
local Settings = {
-- Позиция камеры относительно головы
CameraHeight = -0.25,
CameraBack = -1.9, -- минус = вперёд, плюс = назад
CameraSide = 0,
DownPullIn = 0.3,
-- Мышка
MouseSensitivity = 0.003,
MaxDeltaPerFrame = 120,
MaxPitchDeg = 80,
SettingsKey = Enum.KeyCode.P, -- меню настроек
-- FOV (максимум в Roblox = 120)
IdleFOV = 100,
WalkFOV = 110,
SprintFOV = 120,
FOVSmoothing = 7,
-- Движение
WalkSpeed = 16,
SprintSpeed = 26,
SprintKey = Enum.KeyCode.LeftShift,
-- Покачивание при ходьбе и беге
BobEnabled = true,
BobVertical = 0.28,
BobHorizontal = 0.2,
BobSpeed = 0.45,
BobRoll = 0.035,
BobPitch = 0.025,
SprintBobMultiplier = 1.8,
BobSmoothing = 10,
-- Динамическая камера
StrafeTiltDeg = 2.5, -- наклон при шаге вбок
TurnTiltDeg = 1.5, -- наклон при резком повороте мыши
RunLeanDeg = 1.5, -- наклон вперёд на бегу
-- Прыжок и приземление
JumpEnabled = true,
JumpLift = 1.2,
LandDip = 5,
LandPitch = 0.5,
-- Дыхание в покое
IdleEnabled = true,
IdleVertical = 0.03,
IdleHorizontal = 0.015,
IdleRotation = 0.004,
IdleSpeed = 0.3,
IdleDelay = 300,
IdleFade = 5,
-- Усталость от долгого бега
FatigueEnabled = true,
BlurAfter = 30,
BlurDuration = 10,
BlurMaxSize = 8,
DimAfter = 60,
DimDuration = 30,
DimBrightness = -0.2,
DimSaturation = -0.35,
-- Предмет в руке (вид от первого лица)
ViewmodelEnabled = true,
ViewmodelOffset = Vector3.new(0.8, -0.7, -2.2), -- вправо, вниз, вперёд
ViewmodelRotation = Vector3.new(0, 0, 0), -- в градусах, если предмет смотрит не туда
ViewmodelSway = 0.0015,
ViewmodelBob = 1,
-- Третье лицо (только после смерти)
ThirdPersonDistance = 10,
ThirdPersonSide = 1.5,
ThirdPersonFOV = 70,
-- Видимость тела
ViewAccessories = false,
ViewModels = false,
HeadLocalTransparency = 1,
HiddenParts = {},
}
-- =============================================
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera
local character, humanoid, root
local sprinting = false
local bobTime, idleTime, standTime = 0, 0, 0
local bobOffset = Vector3.zero
local moveAlpha = 0
local locked = {}
local yaw, pitch = 0, 0
local jumpY, jumpVel, jumpPitch, jumpPitchVel, fallSpeed = 0, 0, 0, 0, 0
local dynRoll, dynPitch = 0, 0
local sensMult = 1
local fovOffset = 0
local menuOpen = false
local viewmodel = nil
local swayCur = Vector2.zero
local vmSprint = 0
-- ================= ЭФФЕКТЫ УСТАЛОСТИ =================
for _, name in ipairs({ "FatigueBlur", "FatigueDim" }) do
local old = Lighting:FindFirstChild(name)
if old then old:Destroy() end
end
local blur = Instance.new("BlurEffect")
blur.Name = "FatigueBlur"
blur.Size = 0
blur.Parent = Lighting
local dim = Instance.new("ColorCorrectionEffect")
dim.Name = "FatigueDim"
dim.Parent = Lighting
local sprintTime = 0
local blurT = nil
local dimT = nil
local blurFired = false
local function envelope(t, duration, fadeIn, fadeOut)
return math.clamp(t / fadeIn, 0, 1) * math.clamp((duration - t) / fadeOut, 0, 1)
end
local function resetFatigue()
sprintTime = 0
blurT = nil
dimT = nil
blurFired = false
blur.Size = 0
dim.Brightness = 0
dim.Saturation = 0
end
-- ================= МЕНЮ НАСТРОЕК =================
local playerGui = player:WaitForChild("PlayerGui")
local oldGui = playerGui:FindFirstChild("CameraSettings")
if oldGui then oldGui:Destroy() end
local gui = Instance.new("ScreenGui")
gui.Name = "CameraSettings"
gui.ResetOnSpawn = false
gui.DisplayOrder = 50
gui.Enabled = false
gui.Parent = playerGui
local panel = Instance.new("Frame")
panel.Size = UDim2.fromOffset(340, 210)
panel.AnchorPoint = Vector2.new(0.5, 0.5)
panel.Position = UDim2.fromScale(0.5, 0.5)
panel.BackgroundColor3 = Color3.fromRGB(20, 22, 28)
panel.BackgroundTransparency = 0.08
panel.BorderSizePixel = 0
panel.Parent = gui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 10)
corner.Parent = panel
local function makeLabel(text, y, size)
local l = Instance.new("TextLabel")
l.BackgroundTransparency = 1
l.Position = UDim2.fromOffset(20, y)
l.Size = UDim2.new(1, -40, 0, size)
l.Font = Enum.Font.GothamMedium
l.TextSize = 16
l.TextColor3 = Color3.fromRGB(235, 235, 240)
l.TextXAlignment = Enum.TextXAlignment.Left
l.Text = text
l.Parent = panel
return l
end
makeLabel("Настройки камеры (P — закрыть)", 14, 24)
local function makeSlider(y, title, min, max, start, decimals, onChange)
local label = makeLabel("", y, 22)
local track = Instance.new("Frame")
track.Position = UDim2.fromOffset(20, y + 34)
track.Size = UDim2.new(1, -40, 0, 8)
track.BackgroundColor3 = Color3.fromRGB(70, 74, 86)
track.BorderSizePixel = 0
track.Parent = panel
local fill = Instance.new("Frame")
fill.BackgroundColor3 = Color3.fromRGB(90, 150, 255)
fill.BorderSizePixel = 0
fill.Size = UDim2.new(0, 0, 1, 0)
fill.Parent = track
local knob = Instance.new("Frame")
knob.AnchorPoint = Vector2.new(0.5, 0.5)
knob.Size = UDim2.fromOffset(16, 16)
knob.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
knob.BorderSizePixel = 0
knob.Parent = track
local kc = Instance.new("UICorner")
kc.CornerRadius = UDim.new(1, 0)
kc.Parent = knob
local hit = Instance.new("TextButton")
hit.BackgroundTransparency = 1
hit.Text = ""
hit.AutoButtonColor = false
hit.Position = UDim2.fromOffset(20, y + 22)
hit.Size = UDim2.new(1, -40, 0, 32)
hit.Parent = panel
local function setValue(v)
v = math.clamp(v, min, max)
local m = 10 ^ decimals
v = math.floor(v * m + 0.5) / m
local a = (v - min) / (max - min)
fill.Size = UDim2.new(a, 0, 1, 0)
knob.Position = UDim2.new(a, 0, 0.5, 0)
label.Text = string.format("%s: %." .. decimals .. "f", title, v)
onChange(v)
end
local function fromX(x)
local a = math.clamp((x - track.AbsolutePosition.X) / track.AbsoluteSize.X, 0, 1)
setValue(min + (max - min) * a)
end
local dragging = false
hit.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
fromX(input.Position.X)
end
end)
UserInputService.InputChanged:Connect(function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
fromX(input.Position.X)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = false
end
end)
setValue(start)
end
makeSlider(56, "Чувствительность", 0.1, 3, 1, 2, function(v) sensMult = v end)
makeSlider(124, "FOV (смещение)", -30, 10, 0, 0, function(v) fovOffset = v end)
local function setMenu(open)
menuOpen = open
gui.Enabled = open
end
-- ================= ТЕЛО И ПРЕДМЕТЫ =================
local function classify(part)
if not part:IsA("BasePart") or not character then return nil end
if part.Name == "Head" and part.Parent == character then
return Settings.HeadLocalTransparency
end
if part:FindFirstAncestorOfClass("Accessory") then
return Settings.ViewAccessories and 0 or 1
end
if part:FindFirstAncestorOfClass("Tool") then
return Settings.ViewmodelEnabled and 1 or 0
end
if part.Parent == character then
if table.find(Settings.HiddenParts, part.Name) then
return 1
end
return 0
end
if part:FindFirstAncestorOfClass("Model") ~= character and Settings.ViewModels then
return 0
end
return nil
end
local function register(part)
local value = classify(part)
if value ~= nil then
locked[part] = value
end
end
local function setTransparency(fp)
for part, value in pairs(locked) do
part.LocalTransparencyModifier = fp and value or 0
end
end
local function clearViewmodel()
if viewmodel then
viewmodel.model:Destroy()
viewmodel = nil
end
end
local function buildViewmodel(tool)
clearViewmodel()
if not Settings.ViewmodelEnabled then return end
local handle = tool:FindFirstChild("Handle") or tool:FindFirstChildWhichIsA("BasePart", true)
if not handle then return end
local model = Instance.new("Model")
model.Name = "ViewmodelTool"
local items = {}
for _, d in ipairs(tool:GetDescendants()) do
if d:IsA("BasePart") then
local c = d:Clone()
if c then
for _, x in ipairs(c:GetDescendants()) do
if x:IsA("LuaSourceContainer") or x:IsA("JointInstance") or x:IsA("WeldConstraint") or x:IsA("Sound") then
x:Destroy()
end
end
c.Anchored = true
c.CanCollide = false
c.CanQuery = false
c.CanTouch = false
c.CastShadow = false
c.Massless = true
c.Parent = model
table.insert(items, { part = c, rel = handle.CFrame:ToObjectSpace(d.CFrame) })
end
end
end
model.Parent = camera
viewmodel = { model = model, items = items, tool = tool }
end
local function updateViewmodel(dt, moving, dx, dy, mult)
if not viewmodel then return end
swayCur = swayCur:Lerp(Vector2.new(-dx, dy) * Settings.ViewmodelSway, math.clamp(dt * 10, 0, 1))
vmSprint += (((sprinting and moving) and 1 or 0) - vmSprint) * math.clamp(dt * 8, 0, 1)
local bob = Vector3.new(
math.sin(bobTime) * 0.03 * mult,
-math.abs(math.sin(bobTime)) * 0.04 * mult,
0
) * moveAlpha * Settings.ViewmodelBob
local pos = Settings.ViewmodelOffset
+ bob
+ Vector3.new(swayCur.X, swayCur.Y, 0)
+ Vector3.new(0.15, -0.25, 0.15) * vmSprint
local r = Settings.ViewmodelRotation
local base = camera.CFrame
* CFrame.new(pos)
* CFrame.Angles(math.rad(r.X), math.rad(r.Y), math.rad(r.Z))
* CFrame.Angles(swayCur.Y * 3 + 0.2 * vmSprint, swayCur.X * 3 - 0.5 * vmSprint, 0.15 * vmSprint)
for _, item in ipairs(viewmodel.items) do
item.part.CFrame = base * item.rel
end
end
local function syncLook()
if not root then return end
local look = root.CFrame.LookVector
yaw = math.atan2(-look.X, -look.Z)
pitch = 0
end
-- ================= ПЕРСОНАЖ =================
local function setupCharacter(char)
character = char
humanoid = char:WaitForChild("Humanoid")
root = char:WaitForChild("HumanoidRootPart")
locked = {}
sprinting = false
bobTime = 0
standTime = 0
bobOffset = Vector3.zero
moveAlpha = 0
jumpY, jumpVel, jumpPitch, jumpPitchVel, fallSpeed = 0, 0, 0, 0, 0
dynRoll, dynPitch = 0, 0
swayCur = Vector2.zero
vmSprint = 0
clearViewmodel()
resetFatigue()
humanoid.WalkSpeed = Settings.WalkSpeed
humanoid.AutoRotate = false
humanoid.CameraOffset = Vector3.zero
camera.CameraType = Enum.CameraType.Scriptable
syncLook()
for _, d in ipairs(char:GetDescendants()) do
register(d)
end
char.DescendantAdded:Connect(register)
char.DescendantRemoving:Connect(function(d)
if d:IsA("BasePart") then
d.LocalTransparencyModifier = 0
end
locked[d] = nil
end)
local function onChildAdded(child)
if child:IsA("Tool") then
buildViewmodel(child)
end
end
char.ChildAdded:Connect(onChildAdded)
char.ChildRemoved:Connect(function(child)
if viewmodel and viewmodel.tool == child then
clearViewmodel()
end
end)
for _, c in ipairs(char:GetChildren()) do
onChildAdded(c)
end
humanoid.StateChanged:Connect(function(_, new)
if not Settings.JumpEnabled then return end
if new == Enum.HumanoidStateType.Jumping then
jumpVel += Settings.JumpLift
elseif new == Enum.HumanoidStateType.Landed then
local impact = math.clamp(fallSpeed / 50, 0.25, 1)
jumpVel -= Settings.LandDip * impact
jumpPitchVel -= Settings.LandPitch * impact
fallSpeed = 0
end
end)
char:GetAttributeChangedSignal("Ragdoll"):Connect(function()
if not char:GetAttribute("Ragdoll") then
humanoid.AutoRotate = false
syncLook()
end
end)
end
-- ================= УПРАВЛЕНИЕ =================
UserInputService.InputBegan:Connect(function(input, processed)
if processed then return end
if input.KeyCode == Settings.SettingsKey then
setMenu(not menuOpen)
return
end
if not humanoid then return end
if input.KeyCode == Settings.SprintKey then
sprinting = true
humanoid.WalkSpeed = Settings.SprintSpeed
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == Settings.SprintKey and humanoid then
sprinting = false
humanoid.WalkSpeed = Settings.WalkSpeed
end
end)
-- ================= УСТАЛОСТЬ =================
RunService.Heartbeat:Connect(function(dt)
if not Settings.FatigueEnabled or not root or not humanoid then return end
local speed = (root.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude
local running = sprinting and speed > 10 and humanoid.Health > 0
if running then
sprintTime += dt
if sprintTime >= Settings.BlurAfter and not blurFired then
blurFired = true
blurT = 0
end
if sprintTime >= Settings.DimAfter then
dimT = 0
sprintTime = 0
blurFired = false
end
else
sprintTime = math.max(0, sprintTime - dt * 2)
if sprintTime < Settings.BlurAfter then
blurFired = false
end
end
if blurT then
blurT += dt
if blurT >= Settings.BlurDuration then
blurT = nil
blur.Size = 0
else
blur.Size = Settings.BlurMaxSize * envelope(blurT, Settings.BlurDuration, 1.5, 3)
end
end
if dimT then
dimT += dt
if dimT >= Settings.DimDuration then
dimT = nil
dim.Brightness = 0
dim.Saturation = 0
else
local e = envelope(dimT, Settings.DimDuration, 3, 6)
dim.Brightness = Settings.DimBrightness * e
dim.Saturation = Settings.DimSaturation * e
end
end
end)
-- ================= КАМЕРА КАЖДЫЙ КАДР =================
pcall(function() RunService:UnbindFromRenderStep("FirstPersonBody") end)
pcall(function() RunService:UnbindFromRenderStep("LockBodyTransparency") end)
RunService:BindToRenderStep("FirstPersonBody", Enum.RenderPriority.Camera.Value + 1, function(dt)
if not humanoid or not root or not character then return end
local head = character:FindFirstChild("Head")
if not head then return end
if camera.CameraType ~= Enum.CameraType.Scriptable then
camera.CameraType = Enum.CameraType.Scriptable
end
local ragdoll = character:GetAttribute("Ragdoll") == true
local alive = humanoid.Health > 0
local fp = alive
setTransparency(fp)
-- предмет в руке виден только в первом лице
if viewmodel then
local want = (fp and not ragdoll) and camera or nil
if viewmodel.model.Parent ~= want then
viewmodel.model.Parent = want
end
end
-- мышка
local dx, dy = 0, 0
if menuOpen or GuiService.MenuIsOpen then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
else
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
local delta = UserInputService:GetMouseDelta()
local m = Settings.MaxDeltaPerFrame
dx = math.clamp(delta.X, -m, m)
dy = math.clamp(delta.Y, -m, m)
local sens = Settings.MouseSensitivity * sensMult * GameSettings.MouseSensitivity
yaw -= dx * sens
local lim = math.rad(Settings.MaxPitchDeg)
pitch = math.clamp(pitch - dy * sens, -lim, lim)
end
-- пружины прыжка и приземления
local vy = root.AssemblyLinearVelocity.Y
if vy < 0 then fallSpeed = math.max(fallSpeed, -vy) end
local sdt = math.min(dt, 1 / 30)
jumpVel += (-120 * jumpY - 14 * jumpVel) * sdt
jumpY += jumpVel * sdt
jumpPitchVel += (-120 * jumpPitch - 14 * jumpPitchVel) * sdt
jumpPitch += jumpPitchVel * sdt
-- рагдол: камера едет за головой
if ragdoll and fp then
local target = head.CFrame * CFrame.new(0, 0.15, -0.6)
camera.CFrame = camera.CFrame:Lerp(target, math.clamp(dt * 18, 0, 1))
return
end
-- персонаж смотрит туда же, куда камера
if alive and not ragdoll then
root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0, yaw, 0)
end
local speed = (root.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude
local onGround = humanoid.FloorMaterial ~= Enum.Material.Air
local moving = onGround and speed > 1 and humanoid.MoveDirection.Magnitude > 0
-- третье лицо (после смерти)
if not fp then
local pivot = head.Position + Vector3.new(0, 0.3, 0)
local rot = CFrame.fromEulerAnglesYXZ(pitch, yaw, 0)
local desired = pivot + rot:VectorToWorldSpace(Vector3.new(Settings.ThirdPersonSide, 0, Settings.ThirdPersonDistance))
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
local dir = desired - pivot
local hit = workspace:Raycast(pivot, dir, params)
local pos = desired
if hit then
pos = hit.Position - dir.Unit * 0.4
end
camera.CFrame = CFrame.new(pos) * rot
camera.FieldOfView += (Settings.ThirdPersonFOV - camera.FieldOfView) * math.clamp(dt * Settings.FOVSmoothing, 0, 1)
return
end
-- первое лицо
moveAlpha += ((moving and 1 or 0) - moveAlpha) * math.clamp(dt * 8, 0, 1)
local mult = sprinting and Settings.SprintBobMultiplier or 1
local walkOffset = Vector3.zero
local roll, bobPitch = 0, 0
if Settings.BobEnabled and moving then
bobTime += dt * speed * Settings.BobSpeed
end
if Settings.BobEnabled then
walkOffset = Vector3.new(
math.sin(bobTime) * Settings.BobHorizontal * mult,
math.abs(math.sin(bobTime)) * Settings.BobVertical * mult - Settings.BobVertical * mult * 0.5,
0
) * moveAlpha
roll = math.sin(bobTime) * Settings.BobRoll * mult * moveAlpha
bobPitch = math.sin(bobTime * 2) * Settings.BobPitch * mult * moveAlpha
end
local idleOffset = Vector3.zero
local idleRoll, idlePitch = 0, 0
if Settings.IdleEnabled then
idleTime += dt * Settings.IdleSpeed
if moving then standTime = 0 else standTime += dt end
local idleAlpha = (1 - moveAlpha) * math.clamp((standTime - Settings.IdleDelay) / Settings.IdleFade, 0, 1)
idleOffset = Vector3.new(
math.sin(idleTime * 0.6) * Settings.IdleHorizontal,
math.sin(idleTime) * Settings.IdleVertical,
0
) * idleAlpha
idleRoll = math.sin(idleTime * 0.7) * Settings.IdleRotation * idleAlpha
idlePitch = math.sin(idleTime * 0.9 + 1) * Settings.IdleRotation * idleAlpha
end
-- динамика: наклон при шаге вбок, при повороте мыши, наклон вперёд на бегу
local rel = root.CFrame:VectorToObjectSpace(root.AssemblyLinearVelocity)
local strafe = math.clamp(rel.X / Settings.WalkSpeed, -1, 1)
local forward = math.clamp(-rel.Z / Settings.SprintSpeed, -1, 1)
local targetRoll = -strafe * math.rad(Settings.StrafeTiltDeg)
- math.clamp(dx / 40, -1, 1) * math.rad(Settings.TurnTiltDeg)
local targetPitch = -math.max(forward, 0) * math.rad(Settings.RunLeanDeg)
dynRoll += (targetRoll - dynRoll) * math.clamp(dt * 8, 0, 1)
dynPitch += (targetPitch - dynPitch) * math.clamp(dt * 6, 0, 1)
bobOffset = bobOffset:Lerp(walkOffset + idleOffset, math.clamp(dt * Settings.BobSmoothing, 0, 1))
local downFactor = 1 - math.clamp(-math.sin(pitch), 0, 1) * Settings.DownPullIn
local localOffset = Vector3.new(Settings.CameraSide, Settings.CameraHeight + jumpY, Settings.CameraBack * downFactor) + bobOffset
local yawCF = CFrame.Angles(0, yaw, 0)
local pos = head.Position + yawCF:VectorToWorldSpace(localOffset)
camera.CFrame = CFrame.new(pos)
* CFrame.fromEulerAnglesYXZ(pitch, yaw, 0)
* CFrame.Angles(bobPitch + idlePitch + jumpPitch + dynPitch, 0, roll + idleRoll + dynRoll)
-- предмет в руке
updateViewmodel(dt, moving, dx, dy, mult)
-- FOV
local targetFov = Settings.IdleFOV
if moving then
targetFov = sprinting and Settings.SprintFOV or Settings.WalkFOV
end
targetFov = math.clamp(targetFov + fovOffset, 1, 120)
camera.FieldOfView += (targetFov - camera.FieldOfView) * math.clamp(dt * Settings.FOVSmoothing, 0, 1)
end)
-- прозрачность тела в самый последний момент перед отрисовкой
RunService:BindToRenderStep("LockBodyTransparency", Enum.RenderPriority.Last.Value, function()
setTransparency(humanoid ~= nil and humanoid.Health > 0)
end)
player.CharacterAdded:Connect(setupCharacter)
if player.Character then
setupCharacter(player.Character)
end