LinearVelocity sticking to walls/laggy collisions

I have developed a crude wall collision system. It works partially, but as you can see below, it does not wrap around the player with a huge margin of space, and when I run at a shallow angle towards it, I seemingly ‘merge’ with the wall.


The red dot is a representation of the player’s velocity with a radius of 2.

My existing collision detection is heavily based on nicopatty’s solution, but he uses velocity derived just from the HumanoidRootPart itself, and I have a feeling that his solution will not work with mine, as I use an external force.

My linear velocity is set on an axis to a plane parallel to the ground, so my LinearVelocity is represented as a Vector2.

Here is the code below for the collision detection:

local function WallCheck()
	local overlapParams = OverlapParams.new()
	overlapParams.FilterDescendantsInstances = {Character}
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude
	
	local getParts = workspace:GetPartBoundsInRadius(HumanoidRootPart.Position, 2, overlapParams)
	
	if #getParts > 0 then
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {Character, p}
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude
		
		local origin = HumanoidRootPart.Position
		
		local baseDirection
		if InputDirection.magnitude > 0 then
			baseDirection = InputDirection.Unit
		else
			baseDirection = PlayerVelocity.Unit
		end
		
		local angleRange = math.rad(2) -- total wedge = 2 degrees
		local rayCount = 5
		local half = math.floor(rayCount / 2)

		local closestResult = nil
		local closestDistance = math.huge

		for i = -half, half do
			local angle = i * (angleRange / rayCount)

			-- rotate the baseDirection around the Y-axis
			local rotatedDirection = CFrame.Angles(0, angle, 0):VectorToWorldSpace(baseDirection)
			local result = workspace:Raycast(origin, rotatedDirection * 3, raycastParams)

			if result and result.Distance < closestDistance then
				closestResult = result
				closestDistance = result.Distance
			end
		end
		
		return closestResult
	end
	
	return nil
end

This function is called before I execute my movement functions:

local function Move()
	local result = WallCheck()
	if result and result.Instance and result.Normal then
		bodyvelo.ForceLimitsEnabled = false
		PlayerVelocity -= result.Normal * PlayerVelocity:Dot(result.Normal) 
	end

	-- accelerate PlayerVelocity from target velocity
	if InputDirection.magnitude ~= 0 then
		if result and result.Instance and result.Normal then
			targetVelocity -= result.Normal * targetVelocity:Dot(result.Normal)  
		end
	end
end

I also made a separate one to handle collisions in the air because I want the character to bounce off the walls:

local function AirMove()
	local result = WallCheck()
	if result and result.Instance and result.Normal and PlayerVelocity.Magnitude > 0 then
		bodyvelo.ForceLimitsEnabled = false
		local normal = result.Normal
		local impact = PlayerVelocity:Dot(normal)
		PlayerVelocity -= (1 + "damping value here") * impact * normal
	end
	
	
	-- player velocity accelerated by target velocity
	if InputDirection.Magnitude ~= 0 then
		if result and result.Instance and result.Normal then
			targetVelocity -= result.Normal * targetVelocity:Dot(result.Normal)  
		end
	end

end

I don’t know if this requires a few tweaks or an entire redesign of my collision physics, but any help would be appreciated.

1 Like

bumping this
(character limit)

1 Like

lemme read through it rq
(character limit)

1 Like

right so the first issue i spot is you’re not actually performing any positional correction. you remove the velocity going into the wall if you’re colliding but you’re not fixing the position if they’re actually within it. you gotta counteract.

i also just wouldn’t do this stuff, unsure why you’re doing custom collision aside from experiments but im all for it, ive done similar before with bouncing ball physics lol

but, lets keep what u got, try these (im not gonna code rn admittedly)

  1. add a skin width (this is just lingo for a buffer space that is used to know how to push the player outside of erroneous collision) so you know how far to push the player back if they’re within a collider. that’ll fix your merging.
  2. remove the only velocity component into the wall normal to allow sliding down.
  3. i noticed ur not accounting for thickness here because ur assuming the player is a point (origin). thus if the player is already in the wall, they will probably stay in the wall because the ray cannot detect result if it is fired from within the part. try accounting for radius, like origin + direction.Unit * thickness.

i am curious though, why are you doing this?

2 Likes

Ok so after adding your suggestions, It works better, but there are some residual glitches, such as the collisons not being detected sometimes or the player being pushed out randomly.

Here is the modified wall collision:

local function WallCheck()
	local overlapParams = OverlapParams.new()
	overlapParams.FilterDescendantsInstances = {Character}
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude

	local getParts = workspace:GetPartBoundsInRadius(HumanoidRootPart.Position, 2, overlapParams)
	if #getParts > 0 then
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {Character, p}
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude

		local baseDirection
		if InputDirection.magnitude > 0 then
			baseDirection = InputDirection.Unit
		else
			baseDirection = PlayerVelocity.Unit
		end

		local PLAYER_RADIUS = 1
		local SKIN_WIDTH = 0.1       
		local origin = HumanoidRootPart.Position + baseDirection * PLAYER_RADIUS

		local angleRange = math.rad(2)
		local rayCount = 5
		local half = math.floor(rayCount / 2)

		local closestResult = nil
		local closestDistance = math.huge

		for i = -half, half do
			local angle = i * (angleRange / rayCount)
			local rotatedDirection = CFrame.Angles(0, angle, 0):VectorToWorldSpace(baseDirection)
			local result = workspace:Raycast(origin, rotatedDirection * 3, raycastParams)

			if result and result.Distance < closestDistance then
				closestResult = result
				closestDistance = result.Distance
			end
		end

		if closestResult then
			local minDist = PLAYER_RADIUS + SKIN_WIDTH
			if closestDistance < minDist then
				HumanoidRootPart.CFrame = HumanoidRootPart.CFrame + closestResult.Normal * (minDist - closestDistance)
			end

			PlayerVelocity -= closestResult.Normal * PlayerVelocity:Dot(closestResult.Normal)
			return closestResult
		end
	end
	return nil
end

And then I modified the air movement to add a buffer around the HumanoidRootPart

if InputDirection.Magnitude ~= 0 then
		local targetVelocity = InputDirection.Unit * PlayerVelocity.Magnitude * BHOP_MULTIPLIER
		
		-- Remove wall-normal from air input if there’s a wall
		if result and result.Instance and result.Normal then
			targetVelocity -= result.Normal * targetVelocity:Dot(result.Normal)  
		end
		
		PlayerVelocity = PlayerVelocity:Lerp(targetVelocity, dt * ACCELERATION)
	end

I did some digging, and I found a Lua script that uses linear velocity but WITHOUT using raycasting. i do not know how it was done, but the collisions use Roblox’s default collision. Im wondering if i can modify and tweak it into my existing code:
jdoodle.lua (2.5 KB)

Also, I’m truly sorry for the late reply. I rarely script on Roblox anymore; this is not meant to be a full-blown project. I just attempted to make my own bhop script and wanted to see if I could emulate the physics of one. I got all of it working except the wall collisions, so that’s why I asked for help.

bump!

(character limit exception)

bump again

(charatcer limit again)