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


-- LocalScript
-- Положи в: StarterPlayer > StarterPlayerScripts
-- Управление: Shift - бег, V - переключить вид от 1-го лица / обычная камера

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

local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

-- ========= НАСТРОЙКИ =========
local CFG = {
	ToggleKey = Enum.KeyCode.V,
	SprintKey = Enum.KeyCode.LeftShift,

	WalkSpeed = 16,
	SprintSpeed = 26,

	BaseFOV = 75,
	SprintFOV = 88,
	FOVSmooth = 8,

	-- покачивание головы (в стадах)
	WalkBobFreq = 9,
	WalkBobAmp = 0.10,
	SprintBobFreq = 13,
	SprintBobAmp = 0.20,

	-- наклон камеры вбок при шаге (в градусах)
	WalkRoll = 0.4,
	SprintRoll = 1.0,

	-- дрожание камеры при беге (в градусах)
	SprintShake = 0.25,

	Smooth = 10, -- плавность включения/выключения эффектов
	LandKick = 0.35, -- "толчок" камеры при приземлении

	-- видимость тела
	BodyForward = 0.6, -- насколько камера сдвинута вперёд (чтобы верх торса/плечи не мешали)
	BodyUp = 0.05, -- небольшой подъём камеры
}

-- типы аксессуаров на голове/шее, которые надо скрывать
local HIDE_ACCESSORY_TYPES = {
	Hat = true, Hair = true, Face = true, Neck = true,
	Eyebrow = true, Eyelash = true,
}
-- =============================

local humanoid, root
local firstPerson = true
local sprinting = false

local bobTime = 0
local intensity = 0 -- 0..1, плавно растёт при движении
local sprintBlend = 0 -- 0..1, плавно растёт при беге
local landOffset = 0

local function setFirstPerson(state)
	firstPerson = state
	if state then
		player.CameraMode = Enum.CameraMode.LockFirstPerson
	else
		player.CameraMode = Enum.CameraMode.Classic
		-- отодвигаем камеру назад, чтобы выйти из первого лица
		player.CameraMaxZoomDistance = 15
		player.CameraMinZoomDistance = 10
		task.wait()
		player.CameraMinZoomDistance = 0.5
		if humanoid then
			humanoid.CameraOffset = Vector3.zero
		end
	end
end

local function setSprint(state)
	sprinting = state
	if humanoid then
		humanoid.WalkSpeed = state and CFG.SprintSpeed or CFG.WalkSpeed
	end
end

-- кэш частей тела: [часть] = true, если её надо скрыть (голова и аксессуары на голове)
local bodyParts = {}

local function shouldHide(part)
	if part.Name == "Head" then
		return true
	end
	local acc = part:FindFirstAncestorWhichIsA("Accessory")
	if acc and HIDE_ACCESSORY_TYPES[tostring(acc.AccessoryType):match("%w+$")] then
		return true
	end
	return false
end

local function register(inst)
	if inst:IsA("BasePart") then
		task.defer(function()
			if inst.Parent then
				bodyParts[inst] = shouldHide(inst)
			end
		end)
	end
end

local function onCharacter(char)
	bodyParts = {}
	for _, d in ipairs(char:GetDescendants()) do
		if d:IsA("BasePart") then
			bodyParts[d] = shouldHide(d)
		end
	end
	char.DescendantAdded:Connect(register)
	char.DescendantRemoving:Connect(function(d)
		bodyParts[d] = nil
	end)

	humanoid = char:WaitForChild("Humanoid")
	root = char:WaitForChild("HumanoidRootPart")
	humanoid.WalkSpeed = CFG.WalkSpeed
	sprinting = false

	humanoid.StateChanged:Connect(function(_, new)
		if new == Enum.HumanoidStateType.Landed then
			landOffset = -CFG.LandKick
		end
	end)

	if firstPerson then
		player.CameraMode = Enum.CameraMode.LockFirstPerson
	end
end

player.CameraMaxZoomDistance = 15
player.CameraMinZoomDistance = 0.5
setFirstPerson(true)

if player.Character then
	task.spawn(onCharacter, player.Character)
end
player.CharacterAdded:Connect(onCharacter)

UserInputService.InputBegan:Connect(function(input, processed)
	if processed then return end
	if input.KeyCode == CFG.ToggleKey then
		setFirstPerson(not firstPerson)
	elseif input.KeyCode == CFG.SprintKey then
		setSprint(true)
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == CFG.SprintKey then
		setSprint(false)
	end
end)

local function lerp(a, b, t)
	return a + (b - a) * t
end

RunService:BindToRenderStep("FirstPersonFX", Enum.RenderPriority.Camera.Value + 1, function(dt)
	if not humanoid or not root or humanoid.Health <= 0 then return end

	local alpha = 1 - math.exp(-CFG.Smooth * dt)

	-- движение по земле?
	local vel = root.AssemblyLinearVelocity
	local speed = Vector3.new(vel.X, 0, vel.Z).Magnitude
	local onGround = humanoid.FloorMaterial ~= Enum.Material.Air
	local moving = speed > 1 and onGround

	intensity = lerp(intensity, moving and 1 or 0, alpha)
	sprintBlend = lerp(sprintBlend, (sprinting and moving) and 1 or 0, alpha)

	-- FOV
	local targetFOV = lerp(CFG.BaseFOV, CFG.SprintFOV, sprintBlend)
	camera.FieldOfView = lerp(camera.FieldOfView, targetFOV, 1 - math.exp(-CFG.FOVSmooth * dt))

	-- эффекты только в первом лице
	local isFirst = (camera.CFrame.Position - camera.Focus.Position).Magnitude < 1.5
	if not isFirst then
		humanoid.CameraOffset = Vector3.zero
		return
	end

	-- показываем тело (руки, торс, ноги, инструменты), скрываем голову и её аксессуары
	for part, hide in pairs(bodyParts) do
		part.LocalTransparencyModifier = hide and 1 or 0
	end

	-- сдвигаем камеру вперёд по направлению тела: верх торса и плечи остаются позади камеры,
	-- а при взгляде вниз видно нижнюю часть торса и ноги
	local look = root.CFrame.LookVector
	local flat = Vector3.new(look.X, 0, look.Z)
	if flat.Magnitude > 0.01 then
		camera.CFrame = camera.CFrame + flat.Unit * CFG.BodyForward + Vector3.new(0, CFG.BodyUp, 0)
	end

	local freq = lerp(CFG.WalkBobFreq, CFG.SprintBobFreq, sprintBlend)
	local amp = lerp(CFG.WalkBobAmp, CFG.SprintBobAmp, sprintBlend)
	local roll = lerp(CFG.WalkRoll, CFG.SprintRoll, sprintBlend)

	bobTime += dt * freq * (speed / CFG.WalkSpeed) * intensity

	-- покачивание вверх-вниз и вбок (как шаги)
	local bobY = math.sin(bobTime * 2) * amp * 0.5 * intensity
	local bobX = math.cos(bobTime) * amp * 0.6 * intensity

	-- приземление
	landOffset = lerp(landOffset, 0, 1 - math.exp(-8 * dt))

	humanoid.CameraOffset = Vector3.new(bobX, bobY + landOffset, 0)

	-- наклон + дрожание
	local t = os.clock()
	local shake = CFG.SprintShake * sprintBlend
	local shakeX = math.noise(t * 14, 0.1) * shake
	local shakeY = math.noise(t * 14, 7.3) * shake
	local shakeZ = math.noise(t * 11, 3.7) * shake

	local rollAngle = math.rad(math.cos(bobTime) * roll * intensity + shakeZ)
	local pitchAngle = math.rad(shakeX)
	local yawAngle = math.rad(shakeY)

	camera.CFrame = camera.CFrame * CFrame.Angles(pitchAngle, yawAngle, rollAngle)
end)