Ledge/Climbing system

Hello everyone,

I am interested in creating a ledge climbing system similar to Assassins Creed and/or Hitman series. The player can jump onto ledges and then from the ledge can only jump to another ledge if one is found. Is multiple ledges are found, the player must combine a+spacebar to jump towards the ledge closest to the left, and vice versa for the right. I have drawn a rough sketch to try express what I am talking about

I know I will need to use raycasting and runservice for this but I have little experience with using both services and wondering how I should go about doing this. Code examples would be appreciated, I tend to learn better being shown than being lectured.

Thank you

Raycasting Documentation

I’d also go about it by creating some kind of movement loop (while wait() do or something) for the client, and when the user inputs a+jump, the script checks to see if there is a second ledge (and if there is), a certain animation plays and the player’s rig follows a certain path while the animation plays.

There’s not really another way for me to put this: this is an ambitious project. Make sure you know what you’re doing in terms of programming. This won’t be the main hurdle in this project you are taking on.

2 Likes

This is the general structure of the script. Do you think this might work? I know its in a local script and preferably I would like to play anims and stuff on the server so other players can see the user jumping to and from ledges.

Local script:

  • Checks for ledges on heartbeat
  • If ledge found AND player userinputservice keycode == jump+a then
  • Play jump left animation and move move character to the ledge (what can i use to make the character actually jump to the ledge and follow a path?)
  • Elseif no ledge was found then strafe character to the left
  • same thing for jump+a, jump+s, and jump+w

Anything I am missing?

1 Like

You can use pathfinding and/or raycasting to make this work I believe. Or, you can do character.humanoid:Jump() and use :Move() to move forward. I believe pathfinding will be your best bet though.

Looks like you included everything.

Great, thank you. I’ll attach what I’ve got so far, all seems to be working except I’m not sure what I should use to actually attach the player to the ledge. I was originally going to use welds but problem with welds is I dont know how to make it weld the player to the ledge on an offset (e.g 1 stud off the part so the body isnt inside the ledge) or how to weld the player to the ledge facing the direction that they jumped onto it.

Code in local script:

--[[ local hitbox

local success, err = pcall(function()
	hitbox = workspace:FindFirstChild("testLedge"):FindFirstChild("Hitbox")
end)

if not success then
	print("Failed to locate HITBOX: ", err)
end

if not (hitbox == nil) then
	hitbox.Touched:Connect(function(hit)
		if hit:IsA("Part") or hit:IsA("Accessory") and hit.Parent:FindFirstChild("Humanoid") then
			
			
			--if ledge found and value "CanUse" == true then weld to ledge
			
			--player UIS "jump+a" then raycast for ledges on left
			
			--	if NO ledges found, move character to the left X studs (X represents however many studs animation moves)
			--	if ledge IS FOUND then jump character to ledge using cframe or vector3 + 1 stud (smooth so might have to be +0.1 we will see) to simulate jumping + jump animation
			--	if NO ledges found AND cannot move left then return (nil) end
			
			--player UIS "a" 
			
			--	move character X studs to left
			--	if no room on left to move return (nil) end
			
			--REPEAT EVERYTHING FOR UIS "W, A, S, D" (excluding a if already done above ^)
		end
	end)
end

--]]

wait(2)
local char = game.Players.LocalPlayer.Character
local humanoid = char:FindFirstChild("Humanoid")
local root = char:WaitForChild("HumanoidRootPart")


--SERVICES
local UIS = game:GetService("UserInputService")
local runService = game:GetService("RunService")

--raycast for ledges on heartbeat
runService.Heartbeat:Connect(function(dt)

	--Create weld
	local weld = Instance.new("Weld")
	weld.Parent = root
	weld.Part1 = root
	
	if weld.Enabled then
		weld.Enabled = false
		weld.Part0 = nil
	end
	
	--Set raycast properties
	local rayOrigin = root.Position
	local rayDirection = root.CFrame.LookVector
	local raycastParams = RaycastParams.new()
	local raydist = 100
	local rootCF = root.CFrame
	raycastParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character:GetDescendants(), workspace.Essentials:GetDescendants(), workspace.Excludes:GetDescendants()}
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	raycastParams.IgnoreWater = true
	
	
	local result = workspace:Raycast(rayOrigin, rayDirection * raydist, raycastParams)
	
	if result then
		print("Result! ", tostring(result))
		--[[ local distance = (rayOrigin - result.Position).Magnitude
		local p = Instance.new("Part")
		local folder = workspace.Excludes
		p.Parent = folder
		p.Anchored = true
		p.CanCollide = false
		p.Size = Vector3.new(0.1, 0.1, distance)
		p.CFrame = CFrame.lookAt(rayOrigin, rayDirection)*CFrame.new(0, 0, -distance/2)
		--]]
		
		--if ledge found and value "CanUse" == true then weld to ledge
		if result and result.Instance then
			if result.Instance.Name == "Hitbox" then
				warn("Hitbox found")
				local hitbox = result.Instance
				local ledge = hitbox.Parent
				
				if (root.Position - ledge.Position).Magnitude <= 3 then
					if ledge:FindFirstChild("CanUse").Value == true then
						weld.Enabled = true
						local move = humanoid.MoveDirection:Dot(rootCF.LookVector)
						weld.C0 = result.Instance.CFrame:ToObjectSpace(rootCF) * CFrame.new(0, dt*move*5, -dt*move)
						weld.Part0 = ledge
--Weld part that I might change ^^
					end
				end
			end
		end
		
		
		
	else
		print("No result!")
	end
end)

Only part left after I figure out the welding is the userinputservice and jumping bit.

you could try just anchoring it instead of using welds.