Загрузка данных


--========================================================
-- REALISTIC CAMERA SYSTEM
-- Расположение: StarterPlayer > StarterPlayerScripts
-- Тип: LocalScript
--
-- F     = переключение первого / третьего лица
-- CTRL  = бег
--
-- В первом лице тело и аксессуары скрыты локально.
-- Остальные игроки продолжают видеть персонажа.
--
-- FOV: 90 при ходьбе, 120 при беге.
-- Усталость после 90 секунд непрерывного бега.
-- Затемнение к 120 секундам.
--========================================================

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer

--========================================================
-- НАСТРОЙКИ
--========================================================

local CONFIG = {
    WalkSpeed = 16,
    SprintSpeed = 24,

    WalkFOV = 90,
    SprintFOV = 120,
    FOVSmooth = 6,

    ThirdPersonMinZoom = 8,
    ThirdPersonMaxZoom = 12,

    WalkFrequency = 8,
    SprintFrequency = 11,

    WalkBobX = 0.025,
    WalkBobY = 0.035,

    SprintBobX = 0.045,
    SprintBobY = 0.060,

    WalkRoll = 0.2,
    SprintRoll = 0.45,

    BobSmooth = 7,

    TurnSwayAmount = 0.35,
    TurnSwaySmooth = 8,

    FatigueStart = 90,
    FatigueFull = 120,

    FatigueBobMultiplier = 1.7,
    FatigueFOVReduction = 5,

    DarknessStart = 110,
    MaxDarkness = 0.16,
}

--========================================================
-- СОСТОЯНИЕ
--========================================================

local character
local humanoid
local rootPart

local firstPerson = true
local sprintHeld = false

local sprintTime = 0
local bobTime = 0

local currentFOV = CONFIG.WalkFOV
local bobAmount = 0
local turnSway = 0
local previousYaw

local characterConnections = {}

--========================================================
-- ПЛАВНОСТЬ
--========================================================

local function smooth(current, target, speed, dt)
    local alpha = 1 - math.exp(-speed * dt)
    return current + (target - current) * alpha
end

local function wrapAngle(angle)
    return math.atan2(math.sin(angle), math.cos(angle))
end

local function getYaw(camera)
    local look = camera.CFrame.LookVector
    return math.atan2(-look.X, -look.Z)
end

local function getHorizontalSpeed()
    if not rootPart then
        return 0
    end

    local velocity = rootPart.AssemblyLinearVelocity

    return Vector3.new(
        velocity.X,
        0,
        velocity.Z
    ).Magnitude
end

--========================================================
-- СКРЫТИЕ ТЕЛА ТОЛЬКО НА ЛОКАЛЬНОМ КЛИЕНТЕ
--========================================================

local function updateBodyVisibility()
    if not character then
        return
    end

    for _, object in ipairs(character:GetDescendants()) do
        if object:IsA("BasePart") then

            -- Не скрываем предметы в руках,
            -- если это Tool, экипированный персонажем.
            local ancestor = object.Parent
            local belongsToTool = false

            while ancestor and ancestor ~= character do
                if ancestor:IsA("Tool") then
                    belongsToTool = true
                    break
                end

                ancestor = ancestor.Parent
            end

            if not belongsToTool then
                if firstPerson then
                    object.LocalTransparencyModifier = 1
                else
                    object.LocalTransparencyModifier = 0
                end
            end
        end
    end
end

--========================================================
-- РЕЖИМ КАМЕРЫ
--========================================================

local function setCameraMode()
    if firstPerson then
        player.CameraMode = Enum.CameraMode.LockFirstPerson

        player.CameraMinZoomDistance = 0.5
        player.CameraMaxZoomDistance = 0.5
    else
        player.CameraMode = Enum.CameraMode.Classic

        player.CameraMinZoomDistance =
            CONFIG.ThirdPersonMinZoom

        player.CameraMaxZoomDistance =
            CONFIG.ThirdPersonMaxZoom
    end

    local camera = workspace.CurrentCamera

    if camera and humanoid then
        camera.CameraType = Enum.CameraType.Custom
        camera.CameraSubject = humanoid
    end

    updateBodyVisibility()
end

local function toggleCamera()
    firstPerson = not firstPerson
    setCameraMode()
end

--========================================================
-- ЗАТЕМНЕНИЕ ПРИ УСТАЛОСТИ
--========================================================

local playerGui = player:WaitForChild("PlayerGui")

local oldGui = playerGui:FindFirstChild("RealisticCameraEffects")

if oldGui then
    oldGui:Destroy()
end

local screenGui = Instance.new("ScreenGui")
screenGui.Name = "RealisticCameraEffects"
screenGui.IgnoreGuiInset = true
screenGui.ResetOnSpawn = false
screenGui.DisplayOrder = 10
screenGui.Parent = playerGui

local darkness = Instance.new("Frame")
darkness.Name = "FatigueDarkness"
darkness.Size = UDim2.fromScale(1, 1)
darkness.Position = UDim2.fromScale(0, 0)
darkness.BackgroundColor3 = Color3.new(0, 0, 0)
darkness.BackgroundTransparency = 1
darkness.BorderSizePixel = 0
darkness.Parent = screenGui

--========================================================
-- НАСТРОЙКА ПЕРСОНАЖА
--========================================================

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")

    sprintHeld = false
    sprintTime = 0
    bobTime = 0

    currentFOV = CONFIG.WalkFOV
    bobAmount = 0
    turnSway = 0
    previousYaw = nil

    humanoid.WalkSpeed = CONFIG.WalkSpeed

    setCameraMode()

    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
            sprintTime = 0

            darkness.BackgroundTransparency = 1
        end)
    )
end

player.CharacterAdded:Connect(setupCharacter)

if player.Character then
    task.spawn(setupCharacter, player.Character)
end

--========================================================
-- УПРАВЛЕНИЕ
--========================================================

UserInputService.InputBegan:Connect(function(input, processed)
    if processed then
        return
    end

    if input.KeyCode == Enum.KeyCode.F then
        toggleCamera()

    elseif input.KeyCode == Enum.KeyCode.LeftControl then
        sprintHeld = true
    end
end)

UserInputService.InputEnded:Connect(function(input)
    if input.KeyCode == Enum.KeyCode.LeftControl then
        sprintHeld = false
    end
end)

UserInputService.WindowFocusReleased:Connect(function()
    sprintHeld = false
end)

--========================================================
-- ОБНОВЛЕНИЕ КАМЕРЫ
--========================================================

RunService:BindToRenderStep(
    "RealisticCameraEffects",
    Enum.RenderPriority.Camera.Value + 1,

    function(dt)
        if not character
            or not humanoid
            or not rootPart
            or humanoid.Health <= 0 then
            return
        end

        local camera = workspace.CurrentCamera

        if not camera then
            return
        end

        ----------------------------------------------------
        -- БЕГ
        ----------------------------------------------------

        local speed = getHorizontalSpeed()

        local moving =
            humanoid.MoveDirection.Magnitude > 0.05
            and speed > 0.5

        local sprinting = sprintHeld and moving

        if sprinting then
            humanoid.WalkSpeed = CONFIG.SprintSpeed
            sprintTime += dt
        else
            humanoid.WalkSpeed = CONFIG.WalkSpeed
            sprintTime = 0
        end

        ----------------------------------------------------
        -- УСТАЛОСТЬ
        ----------------------------------------------------

        local fatigue = math.clamp(
            (sprintTime - CONFIG.FatigueStart)
                / (CONFIG.FatigueFull - CONFIG.FatigueStart),
            0,
            1
        )

        ----------------------------------------------------
        -- FOV
        ----------------------------------------------------

        local targetFOV = sprinting
            and CONFIG.SprintFOV
            or CONFIG.WalkFOV

        targetFOV -= fatigue * CONFIG.FatigueFOVReduction

        currentFOV = smooth(
            currentFOV,
            targetFOV,
            CONFIG.FOVSmooth,
            dt
        )

        camera.FieldOfView = currentFOV

        ----------------------------------------------------
        -- ЗАТЕМНЕНИЕ
        ----------------------------------------------------

        local darknessAmount = 0

        if sprintTime > CONFIG.DarknessStart then
            local progress = math.clamp(
                (sprintTime - CONFIG.DarknessStart)
                    / (CONFIG.FatigueFull - CONFIG.DarknessStart),
                0,
                1
            )

            darknessAmount = progress * CONFIG.MaxDarkness
        end

        darkness.BackgroundTransparency = 1 - darknessAmount

        ----------------------------------------------------
        -- ПОКАЧИВАНИЕ
        ----------------------------------------------------

        local targetBob = moving
            and math.clamp(
                speed / CONFIG.SprintSpeed,
                0,
                1
            )
            or 0

        bobAmount = smooth(
            bobAmount,
            targetBob,
            CONFIG.BobSmooth,
            dt
        )

        local frequency = sprinting
            and CONFIG.SprintFrequency
            or CONFIG.WalkFrequency

        local bobX = 0
        local bobY = 0
        local bobRoll = 0

        if bobAmount > 0.001 then
            bobTime += dt * frequency

            local xAmplitude = sprinting
                and CONFIG.SprintBobX
                or CONFIG.WalkBobX

            local yAmplitude = sprinting
                and CONFIG.SprintBobY
                or CONFIG.WalkBobY

            local rollAmplitude = sprinting
                and CONFIG.SprintRoll
                or CONFIG.WalkRoll

            local fatigueMultiplier =
                1 + fatigue * (CONFIG.FatigueBobMultiplier - 1)

            bobX =
                math.cos(bobTime * 0.5)
                * xAmplitude
                * bobAmount
                * fatigueMultiplier

            bobY =
                math.sin(bobTime * 2)
                * yAmplitude
                * bobAmount
                * fatigueMultiplier

            bobRoll =
                math.sin(bobTime)
                * math.rad(rollAmplitude)
                * bobAmount
                * fatigueMultiplier
        end

        ----------------------------------------------------
        -- ИНЕРЦИЯ ПРИ ПОВОРОТЕ
        ----------------------------------------------------

        local yaw = getYaw(camera)
        local yawDelta = 0

        if previousYaw then
            yawDelta = wrapAngle(yaw - previousYaw)
        end

        previousYaw = yaw

        local targetSway = math.clamp(
            -yawDelta * CONFIG.TurnSwayAmount * 60,
            -0.025,
            0.025
        )

        turnSway = smooth(
            turnSway,
            targetSway,
            CONFIG.TurnSwaySmooth,
            dt
        )

        ----------------------------------------------------
        -- ДЫХАНИЕ ПРИ УСТАЛОСТИ
        ----------------------------------------------------

        local breathing =
            math.sin(os.clock() * 2.4)
            * 0.012
            * fatigue

        ----------------------------------------------------
        -- ПРИМЕНЕНИЕ ЭФФЕКТОВ К КАМЕРЕ
        ----------------------------------------------------

        local offset = CFrame.new(
            bobX + turnSway,
            bobY + breathing,
            0
        ) * CFrame.Angles(
            0,
            0,
            bobRoll
        )

        camera.CFrame = camera.CFrame * offset

        ----------------------------------------------------
        -- ПОВТОРНО ПРИМЕНЯЕМ ЛОКАЛЬНУЮ ВИДИМОСТЬ
        ----------------------------------------------------

        -- Roblox может менять прозрачность частей тела
        -- при обновлении стандартной камеры.
        updateBodyVisibility()
    end
)

--========================================================
-- СМЕНА CURRENTCAMERA
--========================================================

workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
    local camera = workspace.CurrentCamera

    if camera and humanoid then
        camera.CameraType = Enum.CameraType.Custom
        camera.CameraSubject = humanoid
    end
end)

--========================================================
-- ОЧИСТКА
--========================================================

script.Destroying:Connect(function()
    RunService:UnbindFromRenderStep("RealisticCameraEffects")
    disconnectCharacterConnections()

    if humanoid and humanoid.Parent then
        humanoid.WalkSpeed = CONFIG.WalkSpeed
    end

    player.CameraMode = Enum.CameraMode.Classic
    player.CameraMinZoomDistance = 0.5
    player.CameraMaxZoomDistance = 12
end)