CFrame:Lerp or Tweenservice not working for my FPS Viewmodel. What do I do?

Going to keep it short and sweet: working on a First Person Weapon System, and I’m attempting to get an Aim Down Sight working using CFrame:Lerp to go to a new position.

This isn’t working, however, because I’m constantly setting the CFrame of the PrimaryPrimary part of my Viewmodel to the camera’s CFrame. I have this code so far and I don’t have any idea on what to do.

RunService.RenderStepped:Connect(function(deltaTime)
	this.DeltaTime = deltaTime
	if this.Viewmodel then
		-- viewmodel initialized, render stepped
		if Character.Head.LocalTransparencyModifier == 1 and this.Weapon then
			this.WeaponEnabled = true
			this.Weapon.Parent = this.Camera
			local CF = this.Camera.CFrame * CFrame.new(0.025, -0.25, -1.3)

			-- Return updated CFrame with config
			CF = this:ReturnCFrameFromConfiguration(CF)

			if not this.ADS then
				-- if ADS (bool) == false, if ADS is true that means we're holding right click and attempting to "ADS"
				-- In this situation, we're not, so we can simply not do anything
				this.ADS_Anim_Finished = false
				this.Weapon:SetPrimaryPartCFrame(CF)
			else
				-- We're attempting to ADS, Lerp/Tween position to ADS Position
				if this.ADS_Position then
					-- If the ADS Position actually exists yet, we can go
					if this.ADS_Anim_Finished == false then
						-- insert animation
					end
					this.ADS_Anim_Finished = true
					this.Weapon.PrimaryPart.CFrame = CF * this.ADS_Position
				end
			end
		else
			-- We're zoomed out or the weapon does not exist
			if this.Viewmodel then
				-- Viewmodel exists?
				this.WeaponEnabled = false
				this.Weapon.Parent = game.ReplicatedStorage
				this.Weapon:SetPrimaryPartCFrame(this.Camera.CFrame * CFrame.new(0.025, -0.25, 1))
				-- Pretty much "disable" the Viewmodel and put it in ReplicatedStorage
			end
		end
	end
end)

I’ve used the Devforum to try and look for answers, Tweenservice, CFrame:Lerp, etc. It right now seems that the only way to get this working is by usin animations.

2 Likes

Hey, I don’t have a solution but I’m going through the same issue right now. If you find a solution, please let me know :+1:

1 Like

You generally want any effects on the viewmodel to affect it relative to the camera.

That means you can’t use TweenService on the view model CFrame because it sets the CFrame in world space, i.e. relative to the world i.e. not relative to the camera.

That also means that to interpolate some effect on the view model CFrame cannot directly interpolate it’s CFrame.

The solution is to work with an offset relative to the camera CFrame instead of directly working with the view model CFrame in world space. It can look like this:

--This CFrame is relative to the camera ("camera space")
local viewModelOffset = CFrame.new()

function updateViewModelCFrame(dt)
    --This CFrame is relative to the world ("world space")
    local viewModelCFrame = camera.CFrame * viewModelOffset

    viewModel:PivotTo(viewModelCFrame)
end

RunS.RenderStepped:Connect(function(dt)
    updateViewModelCFrame(dt)
end)

Now you’re free to animate viewModelOffset however you want, without worrying about the view model moving with the camera since that’s being handled. An example of how effects could be applied:


local t --The current time. Updated first thing every RenderStepped.

--Some basic examples of how this system allows several independent-ish animations to be applied on top of setting the viewmodel CFrame to the camera CFrame
local viewModelOffsets = {
    base = CFrame.new(),
    walkBobbing = CFrame.new(),
    recoil = CFrame.new()
}

function applyRecoil() --Call when the gun is fired
    viewModelOffset.recoil *= --Don't overwrite the old recoil
        CFrame.new(0, 0.1, 0.1) * CFrame.Angles(0.1, 0, 0)
end

function animateRecoil(dt)
    if not isReloading then
        --Recoil recovery
        viewModelOffsets.recoil = 
        viewModelOffset:Lerp(CFrame.new(), math.min(1, dt * 5))
    end
end

function animateWalkBobbing(dt)
    if isWalking and not isReloading then
        viewModelOffsets.walkBobbing = 
        CFrame.Angles(0, math.sin(t*2)/10, 0) * CFrame.new(0, math.sin(t), 0)
    else
        viewModelOffsets.walkBobbing = CFrame.new()
    end
end

function animateViewModel(dt)
    animateRecoil(dt)
    animateWalkBobbing(dt)
end

function updateViewModelCFrame(dt)
    --This CFrame is relative to the camera ("camera space")
    local viewModelOffset = multiply(viewModelOffsets)
    --This CFrame is relative to the world ("world space")
    local viewModelCFrame = camera.CFrame * viewModelOffset

    viewModel:PivotTo(viewModelCFrame)
end

RunS.RenderStepped:Connect(function(dt)
    t = tick()
    animateViewModel(dt)
    updateViewModelCFrame(dt)
end)

--Helper functions that I use all over my code, so I usually put them in a module so I can use them
function multiply(items)
    return reduce(
        items, 
        function(partialProduct, value) return partialProduct * value end
    )
end

function reduce(items, reducer)
    local result
    for _, item in pairs(items) do
        if not result then
            result = item
            continue
        end
        result = reducer(result, item)
    end
    return result
end
1 Like

I understand that, and I’ve got that working properly, though how would I go about making a working animation that goes from Point A to Point B using Lerp? I’ve tried several ways though using RenderStepped is a pain with animating the Camera’s offset.

Here’s an example of how you could use lerp:

local playingAnimations = {}

function animateReloadAnimationTrack(animationTrack)
    local progress = (t - animationTrack.startTick) / 2 --length of the animation
    progress = math.min(1, progress)
    viewModelOffsets.reload = RELOAD_TWEEN_POINT_1:Lerp(RELOAD_TWEEN_POINT_2, progress)
    if progress == 1 then
        table.remove(playingAnimations, table.find(playingAnimations, animationTrack))
    end
end

function playReloadAnimation()
    local animationTrack = {startTick = t, animate = animateReloadAnimationTrack}
    table.insert(playingAnimations, animationTrack)
    animationTrack:animate(0) --Start animating the same frame it's played
end

function animateAnimations(dt) --Probably shouldn't use the verb animate like this xD
    for _, animationTrack in ipairs(playingAnimations) do
        animationTrack:animate(dt)
    end
end

function animateViewModel(dt)
    animateRecoil(dt)
    animateWalkBobbing(dt)
    animateAnimations(dt)
end

I figured I’d use a completely new way of animating to animate the ADS, but I’m getting another issue where the animation gets a little wonky.

this.i = 10 * this.ADS_Speed -- 50
-- initialize the iterating temp variable
RunService.RenderStepped:Connect(function(deltaTime)
	this.DeltaTime = deltaTime
	if this.Viewmodel then
		-- viewmodel initialized, render stepped
		if Character.Head.LocalTransparencyModifier == 1 and this.Weapon then
			this.WeaponEnabled = true
			this.Weapon.Parent = this.Camera
			local CF = this.Camera.CFrame * CFrame.new(0.025, -0.25, -1.3)

			-- Return updated CFrame with config
			CF = this:ReturnCFrameFromConfiguration(CF)
			
			local x
			local y 
			local z
			-- x, y, z vectors
			
			if this.ADS == true then
				-- ads is on (user right-clicked)
				if this.i > 1 then
					-- if the iterating variable is greater than 1, subtract 1
					this.i -= 1
					x = this.ADS_Position.X / this.i
					y = this.ADS_Position.Y / this.i
					z = this.ADS_Position.Z / this.i
					-- set x, y, and z vectors to the positions / the iterating variable
				else
					x = this.ADS_Position.X
					y = this.ADS_Position.Y
					z = this.ADS_Position.Z
					-- last index, set it to its normal stationary position
				end
			else
				x = 0
				y = 0
				z = 0
				this.i = 10
				-- ads not on (user stopped right-clicking), set everything back to zero
			end
			this.Weapon:SetPrimaryPartCFrame(CF * CFrame.new(Vector3.new(x, y, z)))
		else
			-- We're zoomed out or the weapon does not exist
			if this.Viewmodel then
				-- Viewmodel exists?
				this.WeaponEnabled = false
				this.Weapon.Parent = game.ReplicatedStorage
				this.Weapon:SetPrimaryPartCFrame(this.Camera.CFrame * CFrame.new(0.025, -0.25, 1))
				-- Pretty much "disable" the Viewmodel and put it in ReplicatedStorage
			end
		end
	end
end)

As you can see, it gives me an exponential growth in speed instead of a constant speed.

The only other way I can think of is making an invisible part that would move to the specific position and then the Viewmodel would move to that part’s CFrame, but that’s messy and I’d rather not do that.