Need help with custom character controller

I am currently working on a non physics based charater controller.
I am using raycast for collusion.
The ground check works without problem, however the side collusion has a weird offset issue. I want the character to stop once it touches the wall, but the it always ends up stoping with a gap between.
I’ve been trying to fix this for hours with no success!
Thanks for taking your time.

Also please point out possible performace issues too.

my code (cm for character motor)

local cm = {}
cm.__index = cm

--variable
cm.targetPos = Vector3.zero
cm.input = Vector3.zero
cm.velocity = Vector3.zero
cm.falling = false

--setting
cm.walkSpeed = 20
cm.gravity = 2

--internal_variables
cm._minMove = 0.01
cm._rayMultiplier = 1.5
cm._hitboxRadius = 1
cm._hitboxHeightHalf = 1
cm._hitboxSideRays = {}

function cm:update(dt)
	local currentPos = self.hitbox.Position
	local diff = self.targetPos - currentPos
	local diffXZ = diff * Vector3.new(1, 0, 1)
	local diffXZMag = diffXZ.Magnitude
	
	local velocity = Vector3.zero
	local prevVelocity = self.velocity
	
	--calc walk
	if diffXZMag > self._minMove then
		if diffXZMag > self.walkSpeed * dt then
			velocity = diffXZ.Unit * self.walkSpeed * dt
		else
			velocity = diffXZ
		end
	end
	
	--gravity
	if self.falling then
		velocity += Vector3.yAxis * ( prevVelocity.Y - (self.gravity * dt) )
	end
	
	--check ground collusion
	local groundCastResult = workspace:Raycast(
		currentPos + velocity, 
		Vector3.yAxis * -1 * self._rayMultiplier * self._hitboxHeightHalf,
		self._rayPara
	)
	if groundCastResult then
		local dist = groundCastResult.Distance
		local diff = dist - self._hitboxHeightHalf
		if diff > self._minMove then
			self.falling = true
		elseif diff < self._minMove then
			self.falling = false
			velocity -= Vector3.yAxis * diff
		else
			self.falling = false
		end
	else
		self.falling = true
	end
	
	--check side collusion
	local sidePush = Vector3.zero
	for i, vec in ipairs(self._hitboxSideRays) do
		local result = workspace:Raycast(currentPos + velocity, vec * self._hitboxRadius * self._rayMultiplier, self._rayPara)
		if result then
			local dist = result.Distance - self._hitboxRadius
			if dist < 0 then
				sidePush += vec * dist
			end
		end
	end
	
	velocity += sidePush
	
	--apply min move
	local xor = Vector3.yAxis
	if math.abs(velocity.X) > self._minMove then
		xor += Vector3.xAxis
	end
	if math.abs(velocity.Z) > self._minMove then
		xor += Vector3.zAxis
	end
	 velocity = velocity * xor
	
	self.velocity = velocity
	self.model:TranslateBy(velocity)
end

function cm.new(m)
	local new = {
		model = m,
		hitbox = m.PrimaryPart,
		_hitboxHeightHalf = m.PrimaryPart.Size.Y / 2,
		_hitboxRadius = math.max(m.PrimaryPart.Size.X, m.PrimaryPart.Size.Z),
		
		targetPoint = m.PrimaryPart.Position
	}
	setmetatable(new ,cm)
	new._hitboxSideRays = {
		Vector3.new(1, 0, 0),
		Vector3.new(-1, 0, 0),
		Vector3.new(0, 0, 1),
		Vector3.new(0, 0, -1)
	}
	
	local rayPara = RaycastParams.new()
	rayPara.IgnoreWater = true
	rayPara.FilterDescendantsInstances = {m}
	new._rayPara = rayPara
	
	return new
end

return cm

1 Like
	_hitboxRadius = math.max(m.PrimaryPart.Size.X, m.PrimaryPart.Size.Z),

The radius should be half the size of the diameter, which is what Size.X is

Thank you soo much!!
I dont know how I missed that…

1 Like