Загрузка данных
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- ================= НАСТРОЙКИ =================
local Settings = {
HoverHeight = 0.5, -- как в серверном скрипте
GroundReach = 1.2,
MaxSpeed = 42,
MaxReverse = 8,
Accel = 20,
Brake = 45,
Drag = 7,
TurnRate = 2.4,
Grip = 10,
JumpPower = 34,
AirSteer = 0.6,
FlipTime = 0.75,
-- Вили (езда на заднем колесе)
WheelieKey = Enum.KeyCode.V,
WheelieAngle = 0.55, -- наклон носа вверх (радианы, 0.55 примерно 31°)
WheelieMinSpeed = 6, -- ниже этой скорости вили срывается
WheelieSmooth = 6,
DismountKey = Enum.KeyCode.C,
BarspinKey = Enum.KeyCode.Q,
TailwhipKey = Enum.KeyCode.E,
BackflipKey = Enum.KeyCode.R,
FrontflipKey = Enum.KeyCode.T,
PoseAnimationId = "rbxassetid://89427909166247",
}
-- =============================================
local DARKCOL = Color3.fromRGB(28, 30, 38)
local ACCENT = Color3.fromRGB(230, 60, 60)
local player = Players.LocalPlayer
local trickEvent = ReplicatedStorage:WaitForChild("ScooterTrick")
local character, humanoid
local active = false
local seat, core, attach, align, poseTrack, stepConn
local ui, msgLabel, scoreLabel, wheelieBtn
local heading, speed = 0, 0
local jumpQueued = false
local jumpTimer = 0
local wasGrounded = true
local air = nil
local flipAngle, flipTarget = 0, 0
local score = 0
local msgToken = 0
local wheelieAmt, wheelieTime = 0, 0
local buttonHeld = false
-- ---------- интерфейс ----------
local function makeUI()
local pg = player:WaitForChild("PlayerGui")
local old = pg:FindFirstChild("ScooterUI")
if old then old:Destroy() end
ui = Instance.new("ScreenGui")
ui.Name = "ScooterUI"
ui.ResetOnSpawn = false
ui.Parent = pg
scoreLabel = Instance.new("TextLabel")
scoreLabel.AnchorPoint = Vector2.new(1, 0)
scoreLabel.Position = UDim2.new(1, -20, 0, 20)
scoreLabel.Size = UDim2.fromOffset(220, 36)
scoreLabel.BackgroundTransparency = 1
scoreLabel.Font = Enum.Font.GothamBold
scoreLabel.TextSize = 24
scoreLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
scoreLabel.TextStrokeTransparency = 0.5
scoreLabel.TextXAlignment = Enum.TextXAlignment.Right
scoreLabel.Text = "Очки: " .. score
scoreLabel.Parent = ui
msgLabel = Instance.new("TextLabel")
msgLabel.AnchorPoint = Vector2.new(0.5, 1)
msgLabel.Position = UDim2.new(0.5, 0, 1, -120)
msgLabel.Size = UDim2.fromOffset(700, 44)
msgLabel.BackgroundTransparency = 1
msgLabel.Font = Enum.Font.GothamBold
msgLabel.TextSize = 30
msgLabel.TextStrokeTransparency = 0.4
msgLabel.Text = ""
msgLabel.Parent = ui
local hint = Instance.new("TextLabel")
hint.AnchorPoint = Vector2.new(0, 1)
hint.Position = UDim2.new(0, 20, 1, -20)
hint.Size = UDim2.fromOffset(520, 24)
hint.BackgroundTransparency = 1
hint.Font = Enum.Font.GothamMedium
hint.TextSize = 15
hint.TextColor3 = Color3.fromRGB(230, 230, 235)
hint.TextStrokeTransparency = 0.6
hint.TextXAlignment = Enum.TextXAlignment.Left
hint.Text = "Q барспин · E тейлвип · R/T флипы · V вили · C слезть"
hint.Parent = ui
wheelieBtn = Instance.new("TextButton")
wheelieBtn.AnchorPoint = Vector2.new(1, 1)
wheelieBtn.Position = UDim2.new(1, -24, 1, -24)
wheelieBtn.Size = UDim2.fromOffset(160, 54)
wheelieBtn.BackgroundColor3 = DARKCOL
wheelieBtn.BackgroundTransparency = 0.12
wheelieBtn.AutoButtonColor = false
wheelieBtn.Font = Enum.Font.GothamBold
wheelieBtn.TextSize = 20
wheelieBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
wheelieBtn.Text = "ВИЛИ [" .. Settings.WheelieKey.Name .. "]"
wheelieBtn.Parent = ui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 14)
corner.Parent = wheelieBtn
local stroke = Instance.new("UIStroke")
stroke.Color = ACCENT
stroke.Thickness = 2
stroke.Parent = wheelieBtn
wheelieBtn.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
buttonHeld = true
end
end)
wheelieBtn.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
buttonHeld = false
end
end)
end
local function showMessage(text, color)
if not msgLabel then return end
msgLabel.Text = text
msgLabel.TextColor3 = color
msgToken += 1
local t = msgToken
task.delay(2.2, function()
if t == msgToken and msgLabel then msgLabel.Text = "" end
end)
end
local function addScore(pts)
score += pts
if scoreLabel then scoreLabel.Text = "Очки: " .. score end
end
-- ---------- трюки ----------
local function addTrick(name, pts)
table.insert(air.tricks, { name = name, pts = pts })
end
local function doTrick(name)
if not air then return end
if name == "Barspin" then
if air.bar then return end
air.bar = true
trickEvent:FireServer("Barspin")
addTrick("Барспин", 50)
elseif name == "Tailwhip" then
if air.whip then return end
air.whip = true
trickEvent:FireServer("Tailwhip")
addTrick("Тейлвип", 100)
elseif name == "Backflip" then
if math.abs(flipTarget - flipAngle) > 0.1 then return end
flipTarget += 2 * math.pi
addTrick("Бэкфлип", 200)
elseif name == "Frontflip" then
if math.abs(flipTarget - flipAngle) > 0.1 then return end
flipTarget -= 2 * math.pi
addTrick("Фронтфлип", 200)
end
end
local function endWheelie()
if wheelieTime >= 1 then
local pts = math.floor(wheelieTime) * 50
addScore(pts)
showMessage(string.format("Вили %.1f c +%d", wheelieTime, pts), Color3.fromRGB(255, 210, 90))
end
wheelieTime = 0
end
local function handleLanding()
local a = air
air = nil
local fa = flipAngle
flipAngle, flipTarget = 0, 0
if not a or a.time < 0.3 then return end
local rem = (fa + math.pi) % (2 * math.pi) - math.pi
if math.abs(rem) > 0.9 then
speed *= 0.35
showMessage("БЕЙЛ!", Color3.fromRGB(255, 80, 80))
return
end
local halves = math.floor(math.abs(a.spin) / math.pi + 0.3)
if halves >= 1 then
table.insert(a.tricks, { name = (halves * 180) .. "°", pts = 100 * halves })
end
if #a.tricks == 0 then return end
local sum, names = 0, {}
for _, t in ipairs(a.tricks) do
sum += t.pts
table.insert(names, t.name)
end
local total = sum * #a.tricks
addScore(total)
local combo = #a.tricks > 1 and (" (x" .. #a.tricks .. ")") or ""
showMessage(table.concat(names, " + ") .. " +" .. total .. combo, Color3.fromRGB(120, 255, 140))
end
-- ---------- начало и конец поездки ----------
local function stopRide()
if not active then return end
active = false
ContextActionService:UnbindAction("ScooterJump")
if stepConn then stepConn:Disconnect() stepConn = nil end
if poseTrack then poseTrack:Stop(0.2) poseTrack = nil end
if align then align:Destroy() align = nil end
if attach then attach:Destroy() attach = nil end
if ui then ui:Destroy() ui = nil end
wheelieBtn = nil
buttonHeld = false
wheelieAmt, wheelieTime = 0, 0
seat, core, air = nil, nil, nil
end
local function jumpAction(_, state)
if state == Enum.UserInputState.Begin then
jumpQueued = true
end
return Enum.ContextActionResult.Sink
end
local step
local function startRide(s)
if active then return end
local c = s.Parent and s.Parent:FindFirstChild("Core")
if not c then return end
active = true
seat = s
core = c
local look = core.CFrame.LookVector
heading = math.atan2(-look.X, -look.Z)
speed, jumpTimer, wasGrounded, air = 0, 0, true, nil
flipAngle, flipTarget = 0, 0
wheelieAmt, wheelieTime = 0, 0
jumpQueued = false
attach = Instance.new("Attachment")
attach.Name = "ScooterAttach"
attach.Parent = core
align = Instance.new("AlignOrientation")
align.Mode = Enum.OrientationAlignmentMode.OneAttachment
align.Attachment0 = attach
align.RigidityEnabled = false
align.MaxTorque = 1e9
align.MaxAngularVelocity = math.huge
align.Responsiveness = 60
align.CFrame = core.CFrame.Rotation
align.Parent = core
local animator = humanoid:FindFirstChildOfClass("Animator")
if animator and Settings.PoseAnimationId ~= "" then
local a = Instance.new("Animation")
a.AnimationId = Settings.PoseAnimationId
poseTrack = animator:LoadAnimation(a)
poseTrack.Priority = Enum.AnimationPriority.Action
poseTrack.Looped = true
poseTrack:Play(0.2)
end
ContextActionService:BindActionAtPriority(
"ScooterJump", jumpAction, false,
Enum.ContextActionPriority.High.Value + 10, Enum.KeyCode.Space
)
makeUI()
stepConn = RunService.Heartbeat:Connect(step)
end
-- ---------- физика каждый кадр ----------
step = function(dt)
if not active then return end
if not core or not core.Parent or not humanoid or humanoid.Health <= 0 or humanoid.SeatPart ~= seat then
stopRide()
return
end
local pos = core.Position
jumpTimer = math.max(jumpTimer - dt, 0)
local typing = UserInputService:GetFocusedTextBox() ~= nil
local throttle, steer = 0, 0
local wheelieHeld = buttonHeld
if not typing then
if UserInputService:IsKeyDown(Enum.KeyCode.W) then throttle += 1 end
if UserInputService:IsKeyDown(Enum.KeyCode.S) then throttle -= 1 end
if UserInputService:IsKeyDown(Enum.KeyCode.D) then steer += 1 end
if UserInputService:IsKeyDown(Enum.KeyCode.A) then steer -= 1 end
if UserInputService:IsKeyDown(Settings.WheelieKey) then wheelieHeld = true end
end
local pwPrev = wheelieAmt * Settings.WheelieAngle
local hoverPrev = Settings.HoverHeight + 1.55 * math.sin(pwPrev) + 0.15 * (math.cos(pwPrev) - 1)
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character, core.Parent }
local ray = workspace:Raycast(pos, Vector3.new(0, -(hoverPrev + Settings.GroundReach), 0), params)
local grounded = ray ~= nil and jumpTimer <= 0
-- взлёт и приземление
if grounded and not wasGrounded then
handleLanding()
elseif (not grounded) and wasGrounded then
air = { time = 0, spin = 0, tricks = {}, bar = false, whip = false }
flipAngle, flipTarget = 0, 0
end
wasGrounded = grounded
if air then air.time += dt end
-- поворот
local speedFactor = math.clamp(math.abs(speed) / 8, 0, 1)
local turn = Settings.TurnRate * (0.35 + 0.65 * speedFactor)
* (1 - 0.4 * math.clamp(math.abs(speed) / Settings.MaxSpeed, 0, 1))
local dh = -steer * turn * dt
if grounded then
heading += dh
else
heading += dh * Settings.AirSteer
if air then air.spin += dh * Settings.AirSteer end
end
seat:SetAttribute("Heading", heading)
local fwdFlat = Vector3.new(-math.sin(heading), 0, -math.cos(heading))
local v = core.AssemblyLinearVelocity
local targetCF
if grounded then
local n = ray.Normal
local planeDir = fwdFlat - n * fwdFlat:Dot(n)
planeDir = planeDir.Magnitude > 0.01 and planeDir.Unit or fwdFlat
local actual = v:Dot(planeDir)
if actual < speed - 6 then speed = math.max(actual, 0) end
if throttle > 0 then
speed = math.min(speed + Settings.Accel * dt, Settings.MaxSpeed)
elseif throttle < 0 then
if speed > 0 then
speed = math.max(speed - Settings.Brake * dt, 0)
else
speed = math.max(speed - Settings.Accel * 0.5 * dt, -Settings.MaxReverse)
end
else
if speed > 0 then
speed = math.max(speed - Settings.Drag * dt, 0)
else
speed = math.min(speed + Settings.Drag * dt, 0)
end
end
-- вили: нос вверх, самокат едет на заднем колесе
local want = wheelieHeld and speed >= Settings.WheelieMinSpeed
wheelieAmt += ((want and 1 or 0) - wheelieAmt) * math.clamp(dt * Settings.WheelieSmooth, 0, 1)
local pw = wheelieAmt * Settings.WheelieAngle
local hoverNow = Settings.HoverHeight + 1.55 * math.sin(pw) + 0.15 * (math.cos(pw) - 1)
if pw > 0.3 then
wheelieTime += dt
elseif wheelieTime > 0 then
endWheelie()
end
local dist = (pos.Y - ray.Position.Y) * n.Y
local err = hoverNow - dist
local vn = math.clamp(err * 15, -20, 25)
local vt = (v - n * v:Dot(n)):Lerp(planeDir * speed, math.clamp(dt * Settings.Grip, 0, 1))
core.AssemblyLinearVelocity = vt + n * vn
targetCF = CFrame.fromMatrix(Vector3.zero, planeDir:Cross(n), n) * CFrame.Angles(pw, 0, 0)
else
wheelieAmt -= wheelieAmt * math.clamp(dt * 8, 0, 1)
if wheelieTime > 0 then endWheelie() end
if flipAngle ~= flipTarget then
local rate = 2 * math.pi / Settings.FlipTime
local diff = flipTarget - flipAngle
flipAngle += math.sign(diff) * math.min(math.abs(diff), rate * dt)
end
targetCF = CFrame.Angles(0, heading, 0) * CFrame.Angles(flipAngle, 0, 0)
end
-- прыжок
if jumpQueued then
jumpQueued = false
if grounded then
jumpTimer = 0.2
local cv = core.AssemblyLinearVelocity
core.AssemblyLinearVelocity = Vector3.new(cv.X, Settings.JumpPower, cv.Z)
end
end
align.CFrame = targetCF
-- подсветка кнопки вили
if wheelieBtn then
wheelieBtn.BackgroundColor3 = DARKCOL:Lerp(ACCENT, math.clamp(wheelieAmt, 0, 1))
end
end
-- ---------- клавиши ----------
UserInputService.InputBegan:Connect(function(input, processed)
if not active or processed then return end
local k = input.KeyCode
if k == Settings.DismountKey then
humanoid.Sit = false
humanoid:UnequipTools()
elseif k == Settings.BarspinKey then
doTrick("Barspin")
elseif k == Settings.TailwhipKey then
doTrick("Tailwhip")
elseif k == Settings.BackflipKey then
doTrick("Backflip")
elseif k == Settings.FrontflipKey then
doTrick("Frontflip")
end
end)
-- ---------- привязка к персонажу ----------
local function onSeatChanged()
local s = humanoid and humanoid.SeatPart
if s and s:GetAttribute("IsScooter") then
startRide(s)
else
stopRide()
end
end
local function setup(char)
stopRide()
character = char
humanoid = char:WaitForChild("Humanoid")
humanoid:GetPropertyChangedSignal("SeatPart"):Connect(onSeatChanged)
onSeatChanged()
end
player.CharacterAdded:Connect(setup)
if player.Character then setup(player.Character) end