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

but you want it to, because you want them to use the same cframe, correct?

Not exactly the same CFrame, because fake arm’s CFrame are set to the Camera’s offset on every RenderStepped, I’m just wondering how do I replicate real arm’s movement to fake arms.

I think it low-key works with Motor6D.Transform! But the orientation is wrong, how do I address that?

This is most likely an issue in regards to the way that the view model is constructed. Ensure all your parts are in correct orientation and the Motor6Ds are rotated the right way not with code but the actual model used.

To ensure you have correct orientation, define the front face of the view model - this is the outwards facing surfaces of each part. Make sure that each part faces front with the left surfaces facing the left and the same for the right.

As for Motor6Ds, you can use a character rig editor for that, such as the one by DaMrNelson. You’ll need an actual R6 reference to see how the Motor6Ds are oriented to use for your view model.

If you are unsure of how to do this or someone doesn’t resolve this before I do, I can make a crude repro tomorrow regarding orientations. Otherwise, it looks to me like everything’s gold now.

image
I checked and they are facing front though, do I have to do something on scripting about it?

I whipped up a repro in a couple of minutes in the concept of the view model you’re currently attempting to achieve. I had to make a few respective adjustments from what I suggested to accomodate for R6 rigs. Namely, the following was done:

  • Changed HumanoidRootPart to Torso, as all character limbs are held in the Torso for R6 rigs

  • I used the transform method rather than my originally suggested AnimationPlayed method

In addition to all this, I went ahead and created a rough rig that has correct orientations for the limbs and Motor6Ds. The success of the repro leads me to believe that what you have right now is an incorrect orientation of the Motor6Ds. Observe what my repro does:

One of the only necks is that the Torso doesn’t move as well but that can easily be fixed with a couple of changes here and there. I only accounted for arm movement. You may need to add an extra limb for the torso for accurate movement and use the HumanoidRootPart as a real root.

Here’s what it looks like in first person when touched up:

Code:
local LocalPlayer = game:GetService("Players").LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local RenderStepped = game:GetService("RunService").RenderStepped
local ViewModel = game:GetService("ReplicatedStorage").ViewModel:Clone()
local ViewModelRoot = ViewModel.PrimaryPart

ViewModel.Parent = workspace

RenderStepped:Connect(function ()
	ViewModelRoot.CFrame = workspace.CurrentCamera.CFrame * CFrame.new(0, -1.5, 0)
	-- Hardcoded logic test
	ViewModel.Torso["Left Shoulder"].Transform = Character.Torso["Left Shoulder"].Transform
	ViewModel.Torso["Right Shoulder"].Transform = Character.Torso["Right Shoulder"].Transform
end)

Repro file: ViewModelRepro.rbxl (19.4 KB)

Just a warning that collisions may interfere due to the way Humanoids hack them together. I’ve applied CollisionGroups but I’m not sure if that resolves the problem. You’ll need to experiment a bit to find something that fits.

44 Likes

You can used RunService.Stepped for this purpose. When the player zooms in (or if all players are first person you can simply set this) you’ll want to set the LocalTransparencyModifier of the arms to 0 on RenderStepped (not Stepped!). This property is used by the camera script to hide the player’s arms.

Next you’ll want to bind to Stepped (not RenderStepped!) This event is used by animations to create movement. Finally you’ll want to set the Transform property of the Humanoid’s arm joints within the event bind to anything you desire. You can simply translate the base of the arms to somewhere around the camera. This property is not replicated to other clients at all so it is entirely local.

This will create your arm viewmodel without any hacky solutions with new rigs or parts and it will be fully compatible with all Humanoid assets since it is part of the same Humanoid.

2 Likes

Woah! That repro is nearly exactly same as mine! :open_mouth:

Does the name matters? I changed the name from Torso to Motor6D and the collision problem in first person seems not to be happening anymore.

How do you create the rigs? With plugin? For me, I just simply build three parts and connect them with Motor6D, then position them with the correct CFrame, and that’s probably wrong.

Also, I turned on all parts’ Massless will make it looks more smooth, thanks anyways! :wink:

Edit:
Is there any ways to position it to the exactly same position as the real arm?
I modified the CFrame a little bit.

1 Like

Animations are weirdly hard-coded but at the same time aren’t really. There are some conventions that are required, such as where a motor is located and what it’s named, but other than that I don’t think the part name matters. I’m not fully sure myself.

I create rigs by hand and rotate motors with the help of a weld editor of some sort.

As far as moving the hands go, that is also unknown to me. It’d require playing around with CFrames some or separating what others see of the character (as well as the player themselves) from what the client sees.

One last option that can be considered is to modify the actual character parts when in first person to reflect this behaviour, but I’m fairly skeptical when it comes to that.

Is it possible to copy the rig from the Character to the ViewModel such that they have the same position?

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: