Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- grab core services
- local Players = game:GetService("Players")
- local RunService = game:GetService("RunService")
- local UserInputService = game:GetService("UserInputService")
- local TweenService = game:GetService("TweenService")
- local StarterGui = game:GetService("StarterGui")
- local ContextActionService = game:GetService("ContextActionService")
- -- get basic refs
- local player = Players.LocalPlayer
- local camera = workspace.CurrentCamera
- -- wait till we got a char
- local character = player.Character or player.CharacterAdded:Wait()
- local humanoid = character:WaitForChild("Humanoid")
- local rootPart = character:WaitForChild("HumanoidRootPart")
- -- simple state thing to track what we're doing
- local state = {}
- state.__index = state
- state.current = "Idle"
- function state:set(newState)
- self.current = newState
- end
- function state:is(val)
- return self.current == val
- end
- -- basic config stuff
- local GRAPPLE_SPEED = 120
- local CLIMB_SPEED = 1
- local CLIMB_DURATION = 1.5
- local WALL_CHECK_DISTANCE = 5
- local COOLDOWN_TIME = 1.2
- -- visuals and state flags
- local grapplePoint = nil
- local isGrappling = false
- local isClimbing = false
- local beam = nil
- local attachments = {}
- -- ui setup for cooldown bar
- local ui = Instance.new("ScreenGui")
- ui.Name = "GrappleUI"
- ui.ResetOnSpawn = false
- ui.Parent = player:WaitForChild("PlayerGui")
- local cooldownBar = Instance.new("Frame")
- cooldownBar.Size = UDim2.new(0, 100, 0, 6)
- cooldownBar.Position = UDim2.new(0.5, -50, 0.95, 0)
- cooldownBar.BackgroundColor3 = Color3.fromRGB(60, 180, 255)
- cooldownBar.BorderSizePixel = 0
- cooldownBar.Visible = false
- cooldownBar.Parent = ui
- local cooldownFill = Instance.new("Frame")
- cooldownFill.Size = UDim2.new(1, 0, 1, 0)
- cooldownFill.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
- cooldownFill.BorderSizePixel = 0
- cooldownFill.Parent = cooldownBar
- -- buttons for mobile users
- if UserInputService.TouchEnabled then
- local grappleButton = Instance.new("TextButton")
- grappleButton.Size = UDim2.new(0, 100, 0, 40)
- grappleButton.Position = UDim2.new(1, -110, 1, -100)
- grappleButton.Text = "Grapple"
- grappleButton.BackgroundColor3 = Color3.fromRGB(70, 70, 255)
- grappleButton.TextColor3 = Color3.new(1, 1, 1)
- grappleButton.Font = Enum.Font.SourceSansBold
- grappleButton.TextSize = 20
- grappleButton.Parent = ui
- local climbButton = Instance.new("TextButton")
- climbButton.Size = UDim2.new(0, 100, 0, 40)
- climbButton.Position = UDim2.new(1, -110, 1, -150)
- climbButton.Text = "Climb"
- climbButton.BackgroundColor3 = Color3.fromRGB(70, 255, 70)
- climbButton.TextColor3 = Color3.new(1, 1, 1)
- climbButton.Font = Enum.Font.SourceSansBold
- climbButton.TextSize = 20
- climbButton.Parent = ui
- -- hook up buttons to actions
- grappleButton.MouseButton1Click:Connect(function()
- fireGrapple()
- end)
- climbButton.MouseButton1Click:Connect(function()
- startClimbing()
- end)
- end
- -- does the cooldown bar anim
- local function showCooldown(duration)
- cooldownBar.Visible = true
- cooldownFill.Size = UDim2.new(1, 0, 1, 0)
- local tween = TweenService:Create(cooldownFill, TweenInfo.new(duration), {
- Size = UDim2.new(0, 0, 1, 0)
- })
- tween:Play()
- tween.Completed:Wait()
- cooldownBar.Visible = false
- end
- -- draws the beam from player to point
- local function setupGrappleVisual()
- local a1 = Instance.new("Attachment")
- a1.Name = "GrappleStart"
- a1.Position = Vector3.zero
- a1.Parent = rootPart
- local a2 = Instance.new("Attachment")
- a2.Name = "GrappleEnd"
- a2.Parent = workspace.Terrain
- local b = Instance.new("Beam")
- b.Attachment0 = a1
- b.Attachment1 = a2
- b.Width0 = 0.1
- b.Width1 = 0.1
- b.Color = ColorSequence.new(Color3.fromRGB(200, 200, 255))
- b.LightEmission = 1
- b.FaceCamera = true
- b.Parent = a1
- attachments.start = a1
- attachments.end_ = a2
- beam = b
- end
- -- kills the visual
- local function removeGrappleVisual()
- if beam then beam:Destroy() end
- if attachments.start then attachments.start:Destroy() end
- if attachments.end_ then attachments.end_:Destroy() end
- attachments = {}
- beam = nil
- end
- -- checks if there's a wall in front
- local function isNearWall()
- local rayOrigin = rootPart.Position
- local rayDirection = rootPart.CFrame.LookVector * WALL_CHECK_DISTANCE
- local raycastParams = RaycastParams.new()
- raycastParams.FilterDescendantsInstances = {character}
- raycastParams.FilterType = Enum.RaycastFilterType.Exclude
- local result = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
- if result and result.Instance and not result.Instance:IsDescendantOf(character) then
- return true, result.Position
- end
- return false
- end
- -- handles grapple move step
- local function moveTo(position)
- local direction = (position - rootPart.Position)
- local distance = direction.Magnitude
- local duration = distance / GRAPPLE_SPEED
- local t0 = tick()
- state:set("Grappling")
- isGrappling = true
- RunService:BindToRenderStep("GrappleStep", Enum.RenderPriority.Character.Value + 1, function()
- local dt = tick() - t0
- if dt >= duration or not grapplePoint then
- RunService:UnbindFromRenderStep("GrappleStep")
- isGrappling = false
- state:set("Cooldown")
- task.delay(COOLDOWN_TIME, function()
- if not isClimbing then
- state:set("Idle")
- end
- end)
- showCooldown(COOLDOWN_TIME)
- removeGrappleVisual()
- return
- end
- local step = rootPart.CFrame.Position:Lerp(grapplePoint, math.clamp(dt / duration, 0, 1))
- local look = (grapplePoint - rootPart.Position).Unit
- rootPart.CFrame = CFrame.new(step, step + look)
- if attachments.end_ then
- attachments.end_.WorldPosition = grapplePoint
- end
- end)
- end
- -- shoot ray from mouse, see what we hit
- function fireGrapple()
- if isGrappling or isClimbing or state.current ~= "Idle" then return end
- local mousePos = UserInputService:GetMouseLocation()
- local unitRay = camera:ViewportPointToRay(mousePos.X, mousePos.Y)
- local rayOrigin = unitRay.Origin
- local rayDirection = unitRay.Direction * 1000
- local raycastParams = RaycastParams.new()
- raycastParams.FilterDescendantsInstances = {character}
- raycastParams.FilterType = Enum.RaycastFilterType.Exclude
- local result = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
- if not result or not result.Instance then return end
- grapplePoint = result.Position
- setupGrappleVisual()
- moveTo(result.Position)
- end
- -- starts climbing if near wall
- function startClimbing()
- if isClimbing or isGrappling or state.current ~= "Idle" then return end
- local nearWall, wallPos = isNearWall()
- if not nearWall then return end
- state:set("Climbing")
- isClimbing = true
- local climbStart = tick()
- humanoid.PlatformStand = true
- rootPart.Velocity = Vector3.new(0, 0, 0)
- RunService:BindToRenderStep("ClimbStep", Enum.RenderPriority.Character.Value + 2, function()
- local dt = tick() - climbStart
- if dt > CLIMB_DURATION then
- RunService:UnbindFromRenderStep("ClimbStep")
- humanoid.PlatformStand = false
- isClimbing = false
- state:set("Cooldown")
- task.delay(COOLDOWN_TIME, function()
- if not isGrappling then
- state:set("Idle")
- end
- end)
- showCooldown(COOLDOWN_TIME)
- return
- end
- local climbSpeed = CLIMB_SPEED * dt
- local newPos = rootPart.Position + Vector3.new(0, climbSpeed, 0)
- rootPart.CFrame = CFrame.new(newPos, newPos + rootPart.CFrame.LookVector)
- end)
- end
- -- right click = grapple, left = climb
- UserInputService.InputBegan:Connect(function(input, processed)
- if processed then return end
- if input.UserInputType == Enum.UserInputType.MouseButton2 then
- fireGrapple()
- elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
- startClimbing()
- end
- end)
- -- reset stuff on death
- humanoid.Died:Connect(function()
- state:set("Idle")
- isClimbing = false
- isGrappling = false
- removeGrappleVisual()
- end)
- -- rebind stuff when respawn
- player.CharacterAdded:Connect(function(char)
- character = char
- humanoid = char:WaitForChild("Humanoid")
- rootPart = char:WaitForChild("HumanoidRootPart")
- state:set("Idle")
- end)
- -- keep backpack gui on
- StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, true)
Advertisement
Add Comment
Please, Sign In to add comment