How To Replicate Viewmodel Arm Movement To Real Arm

So I’ve made a viewmodel recently, works fine and dandy!

Problem is, I just can’t figure out a way to actually make the real arms follow the viewmodel arms.


I want to do this because I want the gun to actually follow the viewmodel.

I have tried to do this by copying the internal Motor6D CFrame (.Transform) of the viewmodel arms to the real arms.

connections[2] = RNS.RenderStepped:Connect(function()
	if viewmodel then
		torso["Right Shoulder"].Transform = modelTorso["Right Shoulder"].Transform
		torso["Left Shoulder"].Transform = modelTorso["Left Shoulder"].Transform
		
		viewmodel:SetPrimaryPartCFrame(camera.CFrame)
	end
end)

I tried it and it didn’t work. I believe it’s because the arms technically aren’t actually moving up and down, but it looks like it because I am rotating the entire model to follow the camera.

How can I fix this? Any help would be appreciated.

Here’s my entire script:

local RNS = game:GetService("RunService")
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")
local PYS = game:GetService("PhysicsService")

local camera = workspace.CurrentCamera
local char: Model = script:FindFirstAncestorWhichIsA("Model") do
	char.Archivable = true
end
local viewmodelCollision: string = "ViewmodelCollision"
local player: Player = PS:GetPlayerFromCharacter(char)
local hum: Humanoid = char:WaitForChild("Humanoid")
local torso = char:WaitForChild("Torso")
local animator: Animator = hum:WaitForChild("Animator")
local modelHum: Humanoid = nil
local modelAnimator: Animator = nil
local modelTorso: BasePart = nil
local connections = {}
local viewmodel = nil
local loaded = {}

local function SetCollsionGroup(base: any, group: string)
	for _,v: BasePart in ipairs(base:GetDescendants()) do
		if v:IsA("BasePart") then
			PYS:SetPartCollisionGroup(v, group)
		end
	end
end

connections[1] = player.CharacterAppearanceLoaded:Connect(function()
	viewmodel = RS.Viewmodel:Clone() do
		viewmodel.Parent = camera
		viewmodel.Name = "Viewmodel"
		viewmodel.PrimaryPart = viewmodel:WaitForChild("Head")
		
		modelTorso = viewmodel:WaitForChild("Torso")
		modelHum = viewmodel:WaitForChild("Humanoid")
		modelAnimator = modelHum:WaitForChild("Animator")
		
		local blacklist = {"left leg", "right leg"}
		
		for _,v in ipairs(char:GetDescendants()) do
			if v:IsA("Shirt") or v:IsA("Pants") or v:IsA("BodyColors") then
				local clone = v:Clone() do
					clone.Parent = viewmodel
				end
			end
		end
		
		SetCollsionGroup(viewmodel, viewmodelCollision)

		char.Archivable = false
		
		connections[1]:Disconnect()
	end
end)

connections[2] = RNS.RenderStepped:Connect(function()
	if viewmodel then
		torso["Right Shoulder"].Transform = modelTorso["Right Shoulder"].Transform
		torso["Left Shoulder"].Transform = modelTorso["Left Shoulder"].Transform
		
		viewmodel:SetPrimaryPartCFrame(camera.CFrame)
	end
end)

connections[3] = animator.AnimationPlayed:Connect(function(track)
	if loaded[track] or not modelHum or not modelAnimator then
		return
	end
	
	loaded[track] = modelAnimator:LoadAnimation(track.Animation)
	loaded[track]:Play()
	
	track.Stopped:Connect(function()
		loaded[track]:Stop()
		loaded[track] = nil
	end)
end)
3 Likes

Try applying the viewmodel’s X rotation to the C0 of the shoulder joints in addition to copying the Transforms. This should work since you will be modifying the joints’ base rotation.

-- Store the original C0
local rightShoulderC0 = torso["Right Shoulder"].C0
local leftShoulderC0 = torso["Left Shoulder"].C0

-- Run this every frame in RenderStepped
local x, y, z = viewmodel.PrimaryPart.CFrame:ToOrientation()
torso["Right Shoulder"].C0 = rightShoulderC0 * CFrame.fromOrientation(x, 0, 0)
torso["Left Shoulder"].C0 = leftShoulderC0 * CFrame.fromOrientation(x, 0, 0)

-- Apply viewmodel arms transforms
1 Like

It doesn’t work, did I do something wrong?

local RNS = game:GetService("RunService")
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")
local PYS = game:GetService("PhysicsService")

local camera = workspace.CurrentCamera
local char: Model = script:FindFirstAncestorWhichIsA("Model") do
	char.Archivable = true
end
local viewmodelCollision: string = "ViewmodelCollision"
local player: Player = PS:GetPlayerFromCharacter(char)
local hum: Humanoid = char:WaitForChild("Humanoid")
local torso = char:WaitForChild("Torso")
local animator: Animator = hum:WaitForChild("Animator")
local modelHum: Humanoid = nil
local modelAnimator: Animator = nil
local modelTorso: BasePart = nil
local rightShoulderC0 = torso["Right Shoulder"].C0
local leftShoulderC0 = torso["Left Shoulder"].C0
local connections = {}
local viewmodel = nil
local loaded = {}

local function SetCollsionGroup(base: any, group: string)
	for _,v: BasePart in ipairs(base:GetDescendants()) do
		if v:IsA("BasePart") then
			PYS:SetPartCollisionGroup(v, group)
		end
	end
end

connections[1] = player.CharacterAppearanceLoaded:Connect(function()
	viewmodel = RS.Viewmodel:Clone() do
		viewmodel.Parent = camera
		viewmodel.Name = "Viewmodel"
		viewmodel.PrimaryPart = viewmodel:WaitForChild("Head")
		
		modelTorso = viewmodel:WaitForChild("Torso")
		modelHum = viewmodel:WaitForChild("Humanoid")
		modelAnimator = modelHum:WaitForChild("Animator")
		
		local blacklist = {"left leg", "right leg"}
		
		for _,v in ipairs(char:GetDescendants()) do
			if v:IsA("Shirt") or v:IsA("Pants") or v:IsA("BodyColors") then
				local clone = v:Clone() do
					clone.Parent = viewmodel
				end
			end
		end
		
		SetCollsionGroup(viewmodel, viewmodelCollision)

		char.Archivable = false
		
		connections[1]:Disconnect()
	end
end)

connections[2] = RNS.RenderStepped:Connect(function()
	if viewmodel then
		local x, y, z = viewmodel.PrimaryPart.CFrame:ToOrientation()
		torso["Right Shoulder"].C0 = rightShoulderC0 * CFrame.fromOrientation(x, 0, 0)
		torso["Left Shoulder"].C0 = leftShoulderC0 * CFrame.fromOrientation(x, 0, 0)
		
		torso["Right Shoulder"].Transform = modelTorso["Right Shoulder"].Transform
		torso["Left Shoulder"].Transform = modelTorso["Left Shoulder"].Transform
		
		viewmodel:SetPrimaryPartCFrame(camera.CFrame)
	end
end)

connections[3] = animator.AnimationPlayed:Connect(function(track)
	if loaded[track] or not modelHum or not modelAnimator then
		return
	end
	
	loaded[track] = modelAnimator:LoadAnimation(track.Animation)
	loaded[track]:Play()
	
	track.Stopped:Connect(function()
		loaded[track]:Stop()
		loaded[track] = nil
	end)
end)

--connections[4] = char.DescendantAdded:Connect(function(obj)
--	if obj:IsA("Weld") and obj.Name == "RightGrip" and viewmodel then
--		task.wait()
		
--		for _,v in ipairs(viewmodel:GetChildren()) do
--			if v:IsA("Tool") or v:IsA("Weld") and v.Name == "RightGrip" then
--				v:Destroy()
--			end
--		end
		
--		local weld = obj:Clone() do
--			weld.Parent = viewmodel
--		end
--		local tool = obj.Part1.Parent:Clone() do
--			tool.Parent = viewmodel
			
--			weld.Part0 = viewmodel[obj.Part0.Name]
--			weld.Part1 = tool.Handle
--		end
		
--		for _,v in ipairs(obj.Part1.Parent:GetChildren()) do
--			if v:IsA("BasePart") then
--				v.LocalTransparencyModifier = 1
--			end
--		end
--	end
--end)

it doesn’t work because the arms are slanted

the only simple though time taking solution you can apply is to make an axe for the viewmodel as well, using C0s require unnecessary and massive mathematical efforts if you are simply going for that route

2 Likes

What do you mean by that?

Even if I do something like that, the real axe is still in the same position as if I didn’t have the viewmodel in the first place.

so if you want this problem to be fixed then you might need to redo the entire script because the way how im gonna explain will require a viewmodel and just the tool i suppose

1 Like

but it will still not show the viewmodel to other players since it’s a client model though

Alright.

I’ll look into your solution tomorrow. Goodnight!

ok ill just explain it so you can take a look at it tomorrow.

so basically you need the create a viewmodel and setup the viewmodel however you want it

personally i like to set up mine like this
image
but thats not important whats important is fixing this problem

so parent your viewmodel somewhere like replicatedstorage or replicatedfirst

then you should start working on the viewmodel / tool

wait(0.1)

local equipped = false
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local VM = nil
local Character = game.Players.LocalPlayer.Character
local Camera = workspace.CurrentCamera

script.Parent.Equipped:Connect(function()
	equipped = true
	
	local newVM = ReplicatedStorage.test:Clone()
	newVM.Parent = Camera
	VM = newVM
end)

script.Parent.Unequipped:Connect(function()
	equipped = false
end)

RunService.RenderStepped:Connect(function()
	if equipped then
                Character["Left Arm"].LocalTransparencyModifier = 0
		Character["Right Arm"].LocalTransparencyModifier = 0

		VM.HumanoidRootPart.CFrame = Camera.CFrame

		Character.Torso["Left Shoulder"].Enabled = false
		Character.Torso["Right Shoulder"].Enabled = false

		Character["Left Arm"].Anchored = true
		Character["Left Arm"].CFrame = VM.LeftArm.CFrame

		Character["Right Arm"].Anchored = true
		Character["Right Arm"].CFrame = VM.RightArm.CFrame
	else
		if VM then
			Character.Torso["Left Shoulder"].Enabled = true
			Character.Torso["Right Shoulder"].Enabled = true

			Character["Left Arm"].Anchored = false

			Character["Right Arm"].Anchored = false

			VM:remove()
			VM = nil
		else
			Character.Torso["Left Shoulder"].Enabled = true
			Character.Torso["Right Shoulder"].Enabled = true

			Character["Left Arm"].Anchored = false

			Character["Right Arm"].Anchored = false
		end
	end
end)

and result should look like this;

and thats it
you can do anything else with the viewmodel like animate
and the cat model you can just add the tool and add it to the arms or something dunno how you want it
same with sway or animation you can script it anyway how you want it

if you dont have a handle for the tool or this doesnt work then just turn off RequiresHandle for the tool

In the FPS game I’m developing, I used the actual arms instead of a viewmodel. It works fine for me.

Thank you all so much for your help!

I managed to find a solution by following a different method from a youtube video. It works well!

local RNS = game:GetService("RunService")
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")
local PYS = game:GetService("PhysicsService")

local camera = workspace.CurrentCamera
local viewmodelCollision: string = "ViewmodelCollision"
local char = script.Parent
local player = PS:GetPlayerFromCharacter(char)
local torso = char:WaitForChild("Torso")
local head = char:WaitForChild("Head")
local hum = char:WaitForChild("Humanoid")
local rShoulder, lShoulder = torso:WaitForChild("Left Shoulder"), torso:WaitForChild("Right Shoulder")
local viewmodelChar = RS.Viewmodel:Clone() do
	viewmodelChar.Parent = camera
	viewmodelChar.Name = "Viewmodel"
end
local viewmodelTorso = viewmodelChar:WaitForChild("Torso")
local con = {}

local function IsPlayerFirstPerson(): boolean
	local distance: number = (camera.CFrame.Position - head.Position).Magnitude
	
	return distance < 1
end

local function ReplicateAnimationToViewmodel()
	for _,v: Motor6D in ipairs(viewmodelChar:GetDescendants()) do
		if v:IsA("Motor6D") then
			v.Transform = char:FindFirstChild(v.Name, true).Transform
		end
	end
end

local function SetTransparency(base: any, num: number)
	local blacklist = {"Torso", "HumanoidRootPart"}
	
	for _,v: BasePart in ipairs(base:GetDescendants()) do
		if v:IsA("BasePart") and not table.find(blacklist, v.Name) then
			v.LocalTransparencyModifier = num
		end
	end
end

local function SetCollsionGroup(base: any, group: string)
	for _,v: BasePart in ipairs(base:GetDescendants()) do
		if v:IsA("BasePart") then
			PYS:SetPartCollisionGroup(v, group)
		end
	end
end

con[1] = player.CharacterAppearanceLoaded:Connect(function()
	if viewmodelChar then
		local blacklist = {"left leg", "right leg"}

		for _,v in ipairs(char:GetDescendants()) do
			if v:IsA("Shirt") or v:IsA("Pants") or v:IsA("BodyColors") then
				local clone = v:Clone() do
					clone.Parent = viewmodelChar
				end
			end
		end

		SetCollsionGroup(viewmodelChar, viewmodelCollision)
		
		con[1]:Disconnect()
	end
end)

con[2] = RNS.RenderStepped:Connect(function()
	viewmodelChar:SetPrimaryPartCFrame(camera.CFrame)

	if IsPlayerFirstPerson() then
		SetTransparency(viewmodelChar, 0)
		rShoulder.Part0 = viewmodelTorso
		lShoulder.Part0 = viewmodelTorso
	else
		SetTransparency(viewmodelChar, 1)
		rShoulder.Part0 = torso
		lShoulder.Part0 = torso
	end
	
	ReplicateAnimationToViewmodel()
end)

con[3] = hum.Died:Connect(function()
	for _,v: RBXScriptConnection in ipairs(con) do
		v:Disconnect()
	end
end)

3 Likes