Capsule collisions with sphere casts. (First person controller)

TL;DR

Spherecasting from the top of the player to the bottom doesn’t let me detect wall collisions due to how RaycastResult is handled. How would I detect wall collisions on an FPS character controller? (With custom, non-roblox, physics.)

  • I’m trying to use as little spherecasts/raycasts as possible (1 spherecast right now)
  • I use it for floor detection (grounded detection) which works well
  • I tried using GetPartsInPart using a cylinder but it didn’t really work
  • Multiple collision checks/steps take a lot of resources on each axis (X, Z)

The code is on the bottom of the post.

Hey, I’m making an FPS character controller. I’m coding the physics entirely by hand and so I needed a better way to create hitboxes. I’ve been using SphereCasts which seem to work and provide me the hit positions of the raycast result.

However, it’s really hard to do wall-based collisions. Currently, I’m just sphere casting from the top of the player, to the bottom, with a radius of 1 stud.

I’ve seen and read that spherecasts don’t work if they start inside an object, and hence raycast results can’t say where the collision occur. But this also means that I can’t find the collision point or detect if the player is on colliding with a wall.

Spherecasts end up taking a lot of resources when used in bulk, and I still want to add functionality that might use them. Handling all of these on the client could cause lag (for me as well).

This code works great for the grounded/is on floor detection and let’s me correct for the ground but not for walls.

Here’s the code for the single spherecast collision:

get_collision()
function Platformer:get_collision() : RaycastResult
	return workspace:Spherecast(self.position + Vector3.yAxis * (self.height - self.radius)/2, self.radius, -Vector3.yAxis * (self.height -  self.radius))
end

Here’s some context: I’m using OOP, so no Roblox physics or parts are being used, hence I store all the properties in self. The Spherecast starts at the top of the character to the bottom.

is_on_wall()
-- rayRes param is the raycast result from the collision function.
function Platformer:is_on_wall(rayRes) : RaycastResult
	if rayRes then
		local norm_pos = self.position - Vector3.yAxis * self.height/2
		local distance = rayRes.Position.Y - norm_pos.Y --height of the wall
		
		if distance > 0 and math.abs(distance) > self.slope then
			return rayRes
		end
	end

	return nil
end

Where the ‘slope’ is how many studs high a wall should be to be detected a wall, anything else can be simply walked over, like a stair or slope. And norm_pos is the farthest position the raycast can go.