How do I replicate Real Arm movements to Fake Arms (ViewModel)?

Yeah, though that might be a little hacky and at that point it’d be more worth attempting to move the actual rig instead of resorting to any cloning. I don’t have any experience with this so I can try working away at a repro.

Hexcede’s post is worth referring to for attempting something of this nature.

1 Like

I don’t really understand about this, can you further explain a bit more with examples if possible?
What’s the difference between RenderStepped and Stepped?

RenderStepped is run when the game is rendered, while Stepped is run before physics (thanks @ScriptingSupport for just teaching me that just now haha). Stepped is used by animations and it’s the only time the Transform property of Motor6Ds can be used.

This is what I meant (there’s purposely room for improvement but I think I explained what I am doing well enough in the comments):

RunService.RenderStepped:Connect(function()
	for _, part in ipairs(character:GetDescendants()) do -- Definitely don't use GetDescendants in here if you have a lot of instances (e.g. 40 or more) in the player's character since it'll cause a lot of lag
		if part:IsA("BasePart") then -- Not all of these will be parts so we need to check
			part.LocalTransparencyModifier = 0 -- Read the documentation for more information on this property. It basically is used to hide the character's parts when they zoom in without actually using .Transparency. Setting it to 0 makes the part's "actual" transparency equal to its Transparency property which is 0, so the part is fully visible.
		end
	end
end)
RunService.Stepped:Connect(function()
	local camera = workspace.CurrentCamera
	local cameraCFrame = camera.CFrame
	for _, motor in ipairs(baseArmMotors) do
		local cameraOffset = motor.Part0 * camera.CFrame:Inverse() -- An offset to move/rotate the viewmodel by. We calculate it by converting the camera's position to the actual position of the root part. That means the parts will display in their "real" location but we can easily adjust their location by applying a CFrame to this value.
		local offset = motor.C0 * motor.C1:Inverse() -- This is the formula used by joints to get an offset from Part0 to display Part1. We will make the camera act as if it were the Part0 of the weld by applying the offset to the camera's CFrame instead of the Part0's CFrame.
		local newLocation = camera.CFrame * cameraOffset * offset -- We apply the offset to the desired cframe which is the camera CFrame offset by another cframe. This cameraOffset CFrame can be used to move the model forward/backward, left/right.
		local oldLocation = motor.Part0.CFrame * offset -- This is the offset produced by the Motor, not including movements from animations
		motor.Transform = motor.Transform*oldLocation:Inverse()*newLocation -- We subtract the old location from the transform to "0" the viewed position of the arms. Now they're centered on the origin with the offsets from their animations still applied. That means we can add the new location which reapplies the weld offsets with the camera CFrame and now the arms are centered around the camera, offset to their proper positions away from the camera, and they include the playing animation's offsets.
	end
end)
6 Likes

For baseArmMotors ,is that the Motor6D inside the ViewModel?

The ViewModel wouldn’t be a seperate model, it would be using the character’s arms. You’d be using the shoulder joints located in Torso (or arms/legs for R15) of the character. Essentially you’re moving their arms on the client to be centered around the camera using the Transform property used by animations. This is more desirable since the Transform property doesn’t need to be handled in your scripts, you can just apply offsets to it on Stepped. That means less cleanup and your C0 and C1 properties on the joints are free for use by any of your scripts.

For R6 characters the should joints have a space in the name (e.g. “Right Shoulder”). For R15 the joints are properly named imo so they don’t have a space in the name (e.g. “RightShoulder”). The main reason this hasn’t been changed yet is because of backwards compatibility. Old games (and new games) use the joints and are expecting a space in the name for R6 characters.

R15 format:
image
R6 format:
image

2 Likes

Yes! That’s the exact behavior I want to achieve, this will not require fake arms but making the arm to be in the center of the screen.

What is baseArmMotors? The Left Shoulder and Right Shoulder Motor6D?

Yep! Those are the joints which attach the character’s arms to their Torso/UpperTorso so those are the joints you want to offset. You should read the comments in my code so you can try and understand it.

The role of cameraOffset is important in the location of your ViewModel. Whatever it’s set to is the offset from the camera that their arms appear (you may want to move them back/forward). In my script you’ll see I set it to motor.Part0.CFrame and subtract the camera’s CFrame. If you think about CFrames like a single number instead of a position you’ll be able to understand it much better. (That’s actually often how I figure out my CFrame logic)

If this overall viewmodel offset from it’s real root part is the offset from the root location to get a cameraOffset which achieves the original position you want to subtract your root CFrame so that the part is “0 plus the animation offsets” and then you’d add the motor.Part0 location to get the original CFrame.

If you want the arms to be centered directly around the camera instead, you’d want to change the cameraOffset value to a different offset from the camera instead of the one I come up with there.

For example: If you used CFrame.new(0, 0, 2) would move the arms 2 studs backwards on their camera.

I’m still a bit confused but I’m trying to understand it!

How come it is an Object?


local cameraOffset = char.Torso["Left Shoulder"].Part0 * camera.CFrame:Inverse()

Oops that should be .Part0.CFrame not .Part0. (That’s a common mistake with CFrames haha)

The reason that you get that error is because Part0 is an Instance not a CFrame.

image
:thinking::thinking:

Hmm may be a problem with my math. Try using the Inverse of Part0.CFrame instead of the Inverse of camera.CFrame.

Seems that the Offset and the way it rotates is wrong?
https://gyazo.com/a90fcd07dcd36e299292149a4bc5ff0a

I highly considered and attempt this idea (Which is the one from @Hexcede), it’s a fact that it doesn’t require any hacky way, including creating new rigs.

I low-key achieved what I want it to be, I sightly modified the rig that you gave me and I found it differs and offset from the real character rig, that’s probably the reason why it offsets a little bit.
After that I modified the CFrame and now it seems to be working fine! What I need to do now is replicate the weapon to the ViewModel. It might be a little bit frustrating because I do not use the legacy Right Grip by the default Roblox Tool. The tool is connected to the Torso by Motor6D. I’m considering on connect to a real part that’s similar to the ViewModel’s Torso, maybe HumanoidRootPart or I’ll create a new part.

https://gyazo.com/d249bc2da5f152a682046980e9fbf33a

3 Likes

If you want the gun to attach to a seperate viewmodel on the client you can simply change the Part1 (might be Part0) of the RightGrip weld to attach to the viewmodel in a LocalScript. That should not unequip the gun from the player so you should have the gun attached to the fake arms.

As long as the tool remains parented to the character you should be fine.

1 Like

I modified the legacy Roblox tools, I do not use the RightGrip weld, instead, I used a Motor6D that connect the Torso and a part named “BodyAttach” inside the weapon. It acts like a handle in terms of Roblox legacy tool.

FYI check out this:

My current problem is how do I clone the tool to the ViewModel with all the CFrame positioining correct and animations being played.
Right now during the RenderStep, I’ll check is there a tool inside the player, if yes, I’ll clone it to the ViewModel.
@ScriptingSupport You might want to check out this problem since you are more familiar to this ViewModel.

I also added a new Motor named “ToolGrip”, which is exactly the one that the real character uses to connect the tool and the torso. I directly copied it from the real character so they should be the same, this is the ViewModel right now.
image

This is the Rig, which is same as the character’s one.

	if Character:FindFirstChildOfClass("Tool")~= nil and ViewModel.ReplicatedWeapon.Value == false then
		ViewModel.ReplicatedWeapon.Value = true
		local a = Character:FindFirstChildOfClass("Tool"):Clone() -- Clone the entire tool
		for i,v in pairs (a:GetChildren()) do -- Put the tool inside a model
			v.Parent = ViewModel.VMWeapon 
		end
		
		ViewModelRoot.ToolGrip.Part1 = ViewModel.VMWeapon:WaitForChild("BodyAttach")
		
	end

It did clone it and put it inside the ViewModel, however, it’s position is completely wrong.

I tried to use the transform property on ViewModel’s ToolGrip Motor to the real character.Torso.ToolGrip motor, however nothing changes. The gun animations such as pulling out the magazine is not even showing, how do I resolve this?

This is probably the final step of this entire ViewModel, thanks for helping :wink:

4 Likes

Can’t you just reconnect the tool on the client? There’s no need to clone it if you don’t have to.

You mean Attach the ViewModel ToolGrip Motor to the Tool’s BodyAttach?

I mean attach the weapon to the viewmodel arms instead of anywhere on the character. If you do that client side the player should see the tool properly.

I can’t get the view model to stop pushing the character. To have this compatible with R15 would I have to write a script that glues the arms to the humanoidrootpart?

Yeah, performing that should be better than cloning the weapon, however the animations did not even replicate.

if Character:FindFirstChildOfClass("Tool")~= nil then
	Character.Torso.ToolGrip.Part1 = nil
	ViewModelRoot.ToolGrip.Part1 = Character:FindFirstChildOfClass("Tool").BodyAttach
	ViewModelRoot.ToolGrip.C0 = ViewModelRoot.ToolGrip.C0 * CFrame.new(0,0,0)
end

1 Like