Загрузка данных
--[[
REALISTIC FPS + SPRINT + STAMINA EFFECTS
Место: StarterPlayer > StarterPlayerScripts
Тип: LocalScript
F - переключить первое / третье лицо
CTRL - удерживать для бега
Возможности:
- FOV 90 / 120
- плавное ускорение и замедление
- покачивание камеры при ходьбе и беге
- инерция камеры при поворотах
- видимое тело в первом лице
- усталость после 90 секунд бега
- усиление эффектов к 120 секундам
- лёгкое затемнение при сильной усталости
- дыхательный эффект через визуальную пульсацию
]]
--==================================================
-- SERVICES
--==================================================
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
--==================================================
-- SETTINGS
--==================================================
local SETTINGS = {
-- Управление
ToggleCameraKey = Enum.KeyCode.F,
SprintKey = Enum.KeyCode.LeftControl,
-- Скорость
WalkSpeed = 16,
SprintSpeed = 24,
-- Камера
WalkFOV = 90,
SprintFOV = 120,
FOVSmoothness = 7,
ThirdPersonMinZoom = 8,
ThirdPersonMaxZoom = 12,
-- Покачивание при ходьбе
WalkBobFrequency = 8,
WalkBobX = 0.055,
WalkBobY = 0.075,
WalkBobRotation = 0.45,
-- Покачивание при беге
SprintBobFrequency = 12,
SprintBobX = 0.085,
SprintBobY = 0.13,
SprintBobRotation = 0.8,
-- Инерция
TurnSwayAmount = 0.8,
TurnSwaySmoothness = 8,
MovementTilt = 1.1,
-- Усталость
FatigueStart = 90,
FatigueMaximum = 120,
FatigueBobMultiplier = 1.8,
FatigueFOVReduction = 5,
-- Затемнение
DarknessStart = 110,
MaximumDarkness = 0.18,
-- Скорость переходов
CameraSmoothness = 8,
}
--==================================================
-- CHARACTER
--==================================================
local character
local humanoid
local rootPart
local head
local sprintHeld = false
local isFirstPerson = true
local isExhausted = false
local sprintDuration = 0
local bobTime = 0
local currentFOV = SETTINGS.WalkFOV
local currentTurnSway = 0
local currentMoveTilt = 0
local lastCameraYaw = nil
local currentCamera = workspace.CurrentCamera
local characterConnections = {}
--==================================================
-- GUI: FATIGUE DARKENING
--==================================================
local oldGui = playerGui:FindFirstChild("RealisticCameraEffects")
if oldGui then
oldGui:Destroy()
end
local effectGui = Instance.new("ScreenGui")
effectGui.Name = "RealisticCameraEffects"
effectGui.IgnoreGuiInset = true
effectGui.ResetOnSpawn = false
effectGui.DisplayOrder = 100
effectGui.Parent = playerGui
local darkness = Instance.new("Frame")
darkness.Name = "FatigueDarkness"
darkness.Size = UDim2.fromScale(1, 1)
darkness.Position = UDim2.fromScale(0, 0)
darkness.BorderSizePixel = 0
darkness.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
darkness.BackgroundTransparency = 1
darkness.Parent = effectGui
--==================================================
-- CAMERA SETTINGS
--==================================================
local function updateCameraMode()
currentCamera = workspace.CurrentCamera
if not currentCamera then
return
end
if isFirstPerson then
player.CameraMode = Enum.CameraMode.LockFirstPerson
player.CameraMinZoomDistance = 0.5
player.CameraMaxZoomDistance = 0.5
else
player.CameraMode = Enum.CameraMode.Classic
player.CameraMinZoomDistance = SETTINGS.ThirdPersonMinZoom
player.CameraMaxZoomDistance = SETTINGS.ThirdPersonMaxZoom
end
if humanoid then
currentCamera.CameraType = Enum.CameraType.Custom
currentCamera.CameraSubject = humanoid
end
end
local function toggleCamera()
isFirstPerson = not isFirstPerson
updateCameraMode()
end
--==================================================
-- BODY VISIBILITY IN FIRST PERSON
--==================================================
local function updateBodyVisibility()
if not character then
return
end
for _, object in ipairs(character:GetDescendants()) do
if object:IsA("BasePart") then
if isFirstPerson then
-- Голова скрыта, чтобы не перекрывать обзор.
-- Остальные части тела остаются видимыми.
if object == head then
object.LocalTransparencyModifier = 1
elseif object.Name == "HumanoidRootPart" then
object.LocalTransparencyModifier = 1
elseif object.Parent and object.Parent:IsA("Accessory") then
local handle = object.Parent:FindFirstChild("Handle")
if handle == object then
local accessoryWeld =
object:FindFirstChildWhichIsA("Weld")
or object:FindFirstChildWhichIsA("WeldConstraint")
local attachedToHead = false
if accessoryWeld then
local part0 = accessoryWeld.Part0
local part1 = accessoryWeld.Part1
attachedToHead =
part0 == head or part1 == head
end
object.LocalTransparencyModifier =
attachedToHead and 1 or 0
else
object.LocalTransparencyModifier = 0
end
else
object.LocalTransparencyModifier = 0
end
else
-- В третьем лице возвращаем обычную видимость.
object.LocalTransparencyModifier = 0
end
end
end
end
--==================================================
-- CHARACTER INITIALIZATION
--==================================================
local function disconnectCharacterConnections()
for _, connection in ipairs(characterConnections) do
connection:Disconnect()
end
table.clear(characterConnections)
end
local function setupCharacter(newCharacter)
disconnectCharacterConnections()
character = newCharacter
humanoid = character:WaitForChild("Humanoid")
rootPart = character:WaitForChild("HumanoidRootPart")
head = character:WaitForChild("Head")
sprintHeld = false
sprintDuration = 0
bobTime = 0
isExhausted = false
currentTurnSway = 0
currentMoveTilt = 0
lastCameraYaw = nil
humanoid.WalkSpeed = SETTINGS.WalkSpeed
updateCameraMode()
updateBodyVisibility()
table.insert(
characterConnections,
character.DescendantAdded:Connect(function(object)
if object:IsA("BasePart") then
task.defer(updateBodyVisibility)
end
end)
)
table.insert(
characterConnections,
humanoid.Died:Connect(function()
sprintHeld = false
sprintDuration = 0
darkness.BackgroundTransparency = 1
end)
)
end
player.CharacterAdded:Connect(setupCharacter)
if player.Character then
task.spawn(setupCharacter, player.Character)
end
--==================================================
-- INPUT
--==================================================
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode == SETTINGS.ToggleCameraKey then
toggleCamera()
updateBodyVisibility()
elseif input.KeyCode == SETTINGS.SprintKey then
sprintHeld = true
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == SETTINGS.SprintKey then
sprintHeld = false
end
end)
-- Если окно потеряло фокус, не оставляем бег включённым.
UserInputService.WindowFocusReleased:Connect(function()
sprintHeld = false
end)
--==================================================
-- CAMERA HELPERS
--==================================================
local function smooth(current, target, speed, deltaTime)
local alpha = 1 - math.exp(-speed * deltaTime)
return current + (target - current) * alpha
end
local function getHorizontalVelocity()
if not rootPart then
return 0
end
local velocity = rootPart.AssemblyLinearVelocity
return Vector3.new(velocity.X, 0, velocity.Z).Magnitude
end
local function getCameraYaw(camera)
local look = camera.CFrame.LookVector
return math.atan2(-look.X, -look.Z)
end
local function wrapAngle(angle)
return math.atan2(math.sin(angle), math.cos(angle))
end
--==================================================
-- MAIN UPDATE
--==================================================
RunService:BindToRenderStep(
"RealisticCameraEffects",
Enum.RenderPriority.Camera.Value + 1,
function(deltaTime)
if not character or not humanoid or not rootPart then
return
end
if humanoid.Health <= 0 then
return
end
currentCamera = workspace.CurrentCamera
if not currentCamera then
return
end
-- Не позволяем удерживаемому Ctrl оставаться активным
-- после смерти персонажа или отпускания клавиши.
local moving =
humanoid.MoveDirection.Magnitude > 0.05
and getHorizontalVelocity() > 0.5
local actuallySprinting =
sprintHeld and moving and humanoid.Health > 0
------------------------------------------------
-- SPEED
------------------------------------------------
if actuallySprinting then
humanoid.WalkSpeed = SETTINGS.SprintSpeed
sprintDuration += deltaTime
else
humanoid.WalkSpeed = SETTINGS.WalkSpeed
sprintDuration = 0
end
------------------------------------------------
-- FATIGUE
------------------------------------------------
local fatigue = 0
if sprintDuration >= SETTINGS.FatigueStart then
fatigue = math.clamp(
(sprintDuration - SETTINGS.FatigueStart)
/ (SETTINGS.FatigueMaximum - SETTINGS.FatigueStart),
0,
1
)
end
isExhausted = sprintDuration >= SETTINGS.FatigueMaximum
------------------------------------------------
-- FOV
------------------------------------------------
local targetFOV = SETTINGS.WalkFOV
if actuallySprinting then
targetFOV = SETTINGS.SprintFOV
end
-- При сильной усталости угол обзора немного уменьшается.
targetFOV -= fatigue * SETTINGS.FatigueFOVReduction
currentFOV = smooth(
currentFOV,
targetFOV,
SETTINGS.FOVSmoothness,
deltaTime
)
currentCamera.FieldOfView = currentFOV
------------------------------------------------
-- DARKENING
------------------------------------------------
local darknessAmount = 0
if sprintDuration >= SETTINGS.DarknessStart then
darknessAmount = math.clamp(
(sprintDuration - SETTINGS.DarknessStart)
/ (SETTINGS.FatigueMaximum - SETTINGS.DarknessStart),
0,
1
) * SETTINGS.MaximumDarkness
end
-- Небольшая пульсация при сильной усталости.
local breathingPulse = 0
if fatigue > 0 then
breathingPulse =
math.sin(os.clock() * (2.2 + fatigue * 1.5))
* 0.012
* fatigue
end
darkness.BackgroundTransparency = math.clamp(
1 - darknessAmount + breathingPulse,
1 - SETTINGS.MaximumDarkness,
1
)
------------------------------------------------
-- BODY VISIBILITY
------------------------------------------------
updateBodyVisibility()
------------------------------------------------
-- REALISTIC CAMERA MOTION
------------------------------------------------
local speed = getHorizontalVelocity()
local movementAmount = math.clamp(
speed / math.max(SETTINGS.SprintSpeed, 1),
0,
1
)
local bobX = 0
local bobY = 0
local bobRoll = 0
if moving then
local frequency
local amountX
local amountY
local amountRotation
if actuallySprinting then
frequency = SETTINGS.SprintBobFrequency
amountX = SETTINGS.SprintBobX
amountY = SETTINGS.SprintBobY
amountRotation = SETTINGS.SprintBobRotation
else
frequency = SETTINGS.WalkBobFrequency
amountX = SETTINGS.WalkBobX
amountY = SETTINGS.WalkBobY
amountRotation = SETTINGS.WalkBobRotation
end
-- Усталость постепенно усиливает движения камеры.
local fatigueMultiplier =
1 + fatigue * (SETTINGS.FatigueBobMultiplier - 1)
bobTime += deltaTime * frequency
bobX =
math.cos(bobTime * 0.5)
* amountX
* movementAmount
* fatigueMultiplier
bobY =
math.abs(math.sin(bobTime))
* amountY
* movementAmount
* fatigueMultiplier
bobRoll =
math.sin(bobTime * 0.5)
* math.rad(amountRotation)
* movementAmount
* fatigueMultiplier
else
-- Не сбрасываем движение камеры мгновенно.
bobX = 0
bobY = 0
bobRoll = 0
end
------------------------------------------------
-- TURN INERTIA
------------------------------------------------
local yaw = getCameraYaw(currentCamera)
local yawDelta = 0
if lastCameraYaw then
yawDelta = wrapAngle(yaw - lastCameraYaw)
end
lastCameraYaw = yaw
local targetTurnSway = math.clamp(
-yawDelta * SETTINGS.TurnSwayAmount * 60,
-0.035,
0.035
)
currentTurnSway = smooth(
currentTurnSway,
targetTurnSway,
SETTINGS.TurnSwaySmoothness,
deltaTime
)
-- Небольшой наклон при движении в стороны.
local localMove =
currentCamera.CFrame:VectorToObjectSpace(
humanoid.MoveDirection
)
local targetMoveTilt =
-localMove.X
* math.rad(SETTINGS.MovementTilt)
currentMoveTilt = smooth(
currentMoveTilt,
targetMoveTilt,
SETTINGS.CameraSmoothness,
deltaTime
)
------------------------------------------------
-- APPLY CAMERA OFFSET
------------------------------------------------
-- Смещение применяется после стандартной камеры Roblox.
-- Мы не меняем CameraSubject и не блокируем управление мышью.
local fatigueBreathY = 0
if fatigue > 0 then
fatigueBreathY =
math.sin(os.clock() * 2.5)
* 0.015
* fatigue
end
local cameraOffset = CFrame.new(
bobX + currentTurnSway,
-bobY + fatigueBreathY,
0
) * CFrame.Angles(
0,
0,
bobRoll + currentMoveTilt
)
currentCamera.CFrame = currentCamera.CFrame * cameraOffset
end
)
--==================================================
-- KEEP CAMERA MODE VALID
--==================================================
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
currentCamera = workspace.CurrentCamera
if currentCamera and humanoid then
currentCamera.CameraType = Enum.CameraType.Custom
currentCamera.CameraSubject = humanoid
end
end)
--==================================================
-- CLEANUP
--==================================================
script.Destroying:Connect(function()
RunService:UnbindFromRenderStep("RealisticCameraEffects")
disconnectCharacterConnections()
if humanoid and humanoid.Parent then
humanoid.WalkSpeed = SETTINGS.WalkSpeed
end
player.CameraMode = Enum.CameraMode.Classic
player.CameraMinZoomDistance = 0.5
player.CameraMaxZoomDistance = 12
end)