3rd Person Camera Clipping Wall

I’m working on a 3rd person camera and I’m having trouble ensuring the camera does not clip the wall.
The camera does not clip through the wall, unless you look down and lean next to a wall. Once you do that, you can see past the object next to you.
This is my current implementation of this:

local gizmo = require(game:GetService('ReplicatedStorage').Shared.gizmo)

local RS = game:GetService('RunService')
local TS = game:GetService('TweenService')
local UIS = game:GetService('UserInputService')
local CAS = game:GetService('ContextActionService')

local camera = game.Workspace.CurrentCamera
local character = game.Workspace:WaitForChild(game.Players.LocalPlayer.Name)
local hrp: Part = character:WaitForChild('HumanoidRootPart')


local xAxis = 0
local yAxis = 0

local xValue = Instance.new('NumberValue')
local yValue = Instance.new('NumberValue')

local targetCameraOffset = Vector3.new(2,2,8)
local yCameraOffset = 2

local cameraRotationCFrame = CFrame.new(hrp.CFrame.Position)

local mostRecentCameraOffset = targetCameraOffset
local currentCameraOffset = Instance.new('Vector3Value')
local yRotationMultiplier = .4
local cameraSmoothnessMultiplier = 20

local function CustomCamera(_, inputState, inputObject)
    if inputState == Enum.UserInputState.Change then
        xAxis -= inputObject.Delta.X
        yAxis = math.clamp(yAxis - inputObject.Delta.Y * yRotationMultiplier, -75, 75)
    end
end


local function RenderCamera(deltaTime)
    local rootPosition = hrp.CFrame.Position

    cameraRotationCFrame = CFrame.new(rootPosition) * CFrame.Angles(0, math.rad(xValue.Value), 0) * CFrame.Angles(math.rad(yValue.Value), 0, 0)
    mostRecentCameraOffset = CameraOffsetRaycast()

    TS:Create(xValue,
    TweenInfo.new(deltaTime * cameraSmoothnessMultiplier, Enum.EasingStyle.Quint, Enum.EasingDirection.Out, 0, false, 0),
    {Value = xAxis})
    :Play()

    TS:Create(yValue,
    TweenInfo.new(deltaTime * cameraSmoothnessMultiplier, Enum.EasingStyle.Quint, Enum.EasingDirection.Out, 0, false, 0),
    {Value = yAxis})
    :Play()

    TS:Create(currentCameraOffset,
    TweenInfo.new(deltaTime, Enum.EasingStyle.Quint, Enum.EasingDirection.In, 0, false, 0),
    {Value = mostRecentCameraOffset})
    :Play()

    local finalCameraCFrame = cameraRotationCFrame * CFrame.new(mostRecentCameraOffset)
    camera.CFrame = finalCameraCFrame
end

CAS:BindAction('Camera', CustomCamera, false, Enum.UserInputType.MouseMovement)

RS:BindToRenderStep('CameraMovement', Enum.RenderPriority.Camera.Value, RenderCamera)

function CameraOffsetRaycast() : Vector3
    local cameraLookVector = cameraRotationCFrame.LookVector
    local raycastOrigin = hrp.CFrame.Position
    local rcp = RaycastParams.new()
    rcp.FilterType = Enum.RaycastFilterType.Exclude
    rcp.FilterDescendantsInstances = {character}

    local xOffset = (cameraLookVector:Cross(Vector3.new(0,1,0))) * targetCameraOffset.X
    local yOffset = (targetCameraOffset.Y * camera.CFrame.UpVector)
    local zOffset = (targetCameraOffset.Z * -cameraLookVector)
    local totalOffset = camera.CFrame.Position + xOffset + yOffset + zOffset

    local initalLookVector = CFrame.lookAt(raycastOrigin, totalOffset).LookVector
    local initalDistance = (raycastOrigin - totalOffset).Magnitude
    local initalResult = workspace:Raycast(raycastOrigin, initalLookVector * initalDistance, rcp)
    if initalResult then
        local dist = (initalResult.Position - raycastOrigin).Magnitude
        local multiplyer = dist/initalDistance
        return (targetCameraOffset * .95) * multiplyer

    else
        return targetCameraOffset

    end
end

Any help would be greatly appreciated and if you need any clarification, let me know.

You need to cast a ray and then use math.min(ray.Distance,currentCameraDistance) like so to properly set the cam distance and prevent clipping.

1 Like

Would currentCameraDistance be the target offset in this example?

currentCameraDistance if your offset. Which you setted using mousewheel or whatever.

1 Like

The camera would be locked at a certain offset and is only changed if there is an obstacle in the way of the targetCameraOffset.

Although, I now realize that I can use ray.Distance instead of computing it again. With that being said, that is practically what I am doing with (initalResult.Position - raycastOrigin).Magnitude, which still leads to clipping.

Does that make sense or am I not understanding what you are pointing out?

I understand it now.
I am not able to solve your code because it is a bit hard for me to understand, probably because I just woke up :sweat_smile:.

Anywho I had made my own camera system a day or two ago for my game so you can probably see how I do it:

local offset = CFrame.new(2,0.5,0)

local rayOrigin = (HeadCFrame*offset).Position
local rayDirection = Camera.CFrame.LookVector*-cameraDistance
local ray = workspace:Raycast(rayOrigin,rayDirection,rayCast_Params)
local final_cameraDistance = ray2 ~= nil and ray.Distance-0.25 or math.huge

offset *= CFrame.new(0,0,math.min(cameraDistance,final_cameraDistance))

Where offset will be applied later to the camera and HeadCFrame is basically the target part, in my case the head.

As you can see I first calculate the rayOrigin based on my target part and then add the offset to it.

Then I calculate the rayDirection going towards the camera basically. And with that I see if ray exists or not and depending on that add the ray.Distance with a small offset to prevent clipping.

Hope this helped!

1 Like

Thanks for the reply,
I’ll try this out later since its pretty late atm. I’ll get back to you on how that goes.
Thanks once more!

Alright! No worries!
I hope this works for you.

1 Like