Grapple Effect | Created with beams,

Quick and short,

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

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local RootPart = Character:WaitForChild("HumanoidRootPart")
local Camera = workspace.CurrentCamera

local GrappleSpeed = 800
local NodeCount = 20
local BeamThickness = 0.8
local WaveCount = 3
local WaveHeight = 1
local SpringDamper = 5
local SpringVelocity = 10
local StraightenSpeed = 10

local Nodes = table.create(NodeCount)
local Beams = table.create(NodeCount - 1)
local GrapplePoint = nil
local IsGrappling = false
local SpringValue = 0
local GrappleProgress = 0
local IsStraightening = false

local function CreateNode(position)
	local Node = Instance.new("Part")
	Node.Anchored = true
	Node.CanCollide = false
	Node.Size = Vector3.new(0.1, 0.1, 0.1)
	Node.Transparency = 0.5
	Node.Color = Color3.new(0, 0, 0)
	Node.Position = position
	Node.Parent = workspace
	return Node
end

local function CreateBeam(StartNode, EndNode)
	local Beam = Instance.new("Beam")
	Beam.Attachment0 = Instance.new("Attachment", StartNode)
	Beam.Attachment1 = Instance.new("Attachment", EndNode)
	Beam.Width0 = BeamThickness
	Beam.Width1 = BeamThickness
	Beam.Color = ColorSequence.new(Color3.new(0, 0, 0))
	Beam.Parent = workspace
	Beam.FaceCamera = true
	return Beam
end

local function CleanupGrapple()
	for _, Node in ipairs(Nodes) do
		Node:Destroy()
	end
	for _, Beam in ipairs(Beams) do
		Beam:Destroy()
	end
	table.clear(Nodes)
	table.clear(Beams)
end

local function CreateGrapple()
	CleanupGrapple()
	for i = 1, NodeCount do
		Nodes[i] = CreateNode(RootPart.Position)
		if i > 1 then
			Beams[i - 1] = CreateBeam(Nodes[i - 1], Nodes[i])
		end
	end
end

local function UpdateSpring(DeltaTime)
	if IsStraightening then
		SpringValue = 0
	else
		local Delta = SpringVelocity * DeltaTime
		SpringValue = SpringValue + Delta
		SpringValue = SpringValue * (1 - SpringDamper * DeltaTime)
	end
end

local function UpdateGrapple(DeltaTime)
	if not GrapplePoint or not RootPart then return end

	local Direction = (GrapplePoint - RootPart.Position).Unit
	local Distance = (GrapplePoint - RootPart.Position).Magnitude

	GrappleProgress = math.min(GrappleProgress + GrappleSpeed * DeltaTime, Distance)
	local T = GrappleProgress / Distance

	if IsStraightening then
		for i = 1, NodeCount do
			local NodeT = i / NodeCount
			local TargetPosition = RootPart.Position + Direction * (NodeT * Distance)
			Nodes[i].Position = Nodes[i].Position:Lerp(TargetPosition, DeltaTime * StraightenSpeed)
		end
	else
		for i = 1, NodeCount do
			local NodeT = i / NodeCount
			local CurveOffset = CFrame.lookAt(RootPart.Position, GrapplePoint).UpVector * WaveHeight * math.sin(NodeT * WaveCount * math.pi) * SpringValue
			local NodePosition = RootPart.Position + Direction * (NodeT * GrappleProgress) + CurveOffset
			Nodes[i].Position = Nodes[i].Position:Lerp(NodePosition, DeltaTime * 10)
		end

		if GrappleProgress >= Distance then
			IsStraightening = true
			SpringValue = 0
		end
	end
end

UserInputService.InputBegan:Connect(function(Input, GameProcessed)
	if GameProcessed then return end
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		local MouseRay = Camera:ScreenPointToRay(Input.Position.X, Input.Position.Y)
		local RaycastParams = RaycastParams.new()
		RaycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		RaycastParams.FilterDescendantsInstances = {Character}

		local Result = workspace:Raycast(MouseRay.Origin, MouseRay.Direction * 1000, RaycastParams)
		if Result then
			GrapplePoint = Result.Position
			IsGrappling = true
			IsStraightening = false
			GrappleProgress = 0
			SpringValue = 1 
			CreateGrapple()
		end
	end
end)

UserInputService.InputEnded:Connect(function(Input, GameProcessed)
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		IsGrappling = false
		IsStraightening = false
		CleanupGrapple()
	end
end)

RunService.RenderStepped:Connect(function(DeltaTime)
	if IsGrappling then
		UpdateSpring(DeltaTime)
		UpdateGrapple(DeltaTime)
	end
end)
12 Likes

quite promising here, it was one of the features that I’ve been trying to make since Path2D releases but it fails to rotate to face on camera, i always blamed how Beams only limit to just 4 points only.

it’s sort of similar to my second attempt except mine is way awfully pain,.,…,.,.,.,. gonna give this a try

3 Likes

I have made this effect with a roblox rope before, im trying to figure it out. Its a way better way of this

nice work. you may also calculate the CurveSize0 and CurveSize1 to make it even more wiggly and smooth between two nodes.

1 Like

I wonder if this can be used to make a repel system where you can climb walls.