Rotating arms script overrides animation

my script rotates the arms to the y axis of the mouse. my problem is that i have an animation that has the arms together. the arms dont stay together when the arms are rotating.

script:

function module.ArmsFunction(p:Player, LeftShoulder:Motor6D, RightShoulder:Motor6D)
	local c = p.Character
	local h = c:WaitForChild("Humanoid")
	local mo = p:GetMouse()
	local rs:RunService = game:GetService("RunService")

	local bpTable = {
		Head = 	c:WaitForChild("Head"),
		RightHand = c:WaitForChild("RightHand"),
		LeftHand = c:WaitForChild("LeftHand"),
		LowerTorso = c:WaitForChild("LowerTorso"),
		RightLowerArm = c:WaitForChild("RightLowerArm"),
		LeftUpperArm = c:WaitForChild("LeftUpperArm"),
		LeftLowerArm = c:WaitForChild("LeftLowerArm"),
		RightUpperArm = c:WaitForChild("RightUpperArm"),
		UpperTorso = c:WaitForChild("UpperTorso"),
		RightShoulder = c:WaitForChild("RightUpperArm"):WaitForChild("RightShoulder");
		LeftShoulder = c:WaitForChild("LeftUpperArm"):WaitForChild("LeftShoulder");
		RightElbow = c:WaitForChild("RightLowerArm"):WaitForChild("RightElbow");
		LeftElbow = c:WaitForChild("LeftLowerArm"):WaitForChild("LeftElbow");
		HumanoidRootPart = c:WaitForChild("HumanoidRootPart")
	}

	rs.PreSimulation:Connect(function()
		local leftShoulderCFrame, rightShoulderCFrame = bpTable.HumanoidRootPart.CFrame:ToWorldSpace(LeftShoulder.C0), bpTable.HumanoidRootPart.CFrame:ToWorldSpace(RightShoulder.C0)
		local desiredLCF, desiredRCF = CFrame.lookAt(leftShoulderCFrame.Position, mo.Hit.Position), CFrame.lookAt(rightShoulderCFrame.Position, mo.Hit.Position)
		local LSToObjectSpace = leftShoulderCFrame:ToObjectSpace(desiredLCF)
		local RSToObjectSpace = rightShoulderCFrame:ToObjectSpace(desiredRCF)
		local LSx, LSy, LSz = LSToObjectSpace:ToEulerAnglesXYZ()
		local RSx, RSy, RSz = RSToObjectSpace:ToEulerAnglesXYZ()
		local LSPos = CFrame.new(LSToObjectSpace.Position)
		local RSPos = CFrame.new(RSToObjectSpace.Position)
		local LSRotation = CFrame.Angles(LSx,0, 0)
		local RSRotation = CFrame.Angles(RSx, 0, 0)

		local rotationOFFSET = CFrame.Angles(math.rad(90),0,0)

		LeftShoulder.Transform = LSPos * LSRotation * rotationOFFSET
		RightShoulder.Transform = RSPos * RSRotation * rotationOFFSET
	end)
end

animation:
image

vid:

Manipulating the joints while keeping the structure of the animation will require a different approach.

First, edit the gun holding animation so that the character is pointing the gun forwards, perfectly straight. It’s ok if the character is bending their elbows as long as the hand that’s holding the gun is pointed forwards. Both pictures below are valid examples.

image

After doing this you can copy the code that I’ve provided at the bottom. The rest of this post will be explaining the math behind it.

Essentially we need to rotate the arms about a shared axis. Besides finding the axis of rotation, we need to find the angle of rotation as well:

image

To be able to calculate the angle between the gun and the mouse hit position, the mouse hit first needs to be aligned with a shoulder. The mouse hit position must be in object space. In this case we’ll use the right shoulder. In the image below you will see a representation of the right shoulder’s object space (denoted by the three arrows). Imagine a plane in the object space of the right shoulder that runs through the y and z axis. This will be called the “yz-plane”. As you can see, the mouse hit is not currently aligned with the yz-plane, so this will skew our angle calculations:

To fix this, we simply need to project the mouse hit position onto the yz-plane. After finding the projection, we have a point in object space that’s aligned with the right shoulder, so we can find the angle between this new vector and some other vector. We could use the direction the shoulder is facing in, but we will achieve more desirable results if we use Vector3.new(0, -1, 0) as a reference vector (the length of the arrows in the diagram below are not to scale, just focus on their direction):

To find the angle between the reference vector and the projected mouse hit, we rearrange the dot product formula image to solve for theta. This will be the angle that we need to offset the arms so that the gun points at the mouse.

Now that we have the rotation angle, we need to determine the axis of rotation. We will use CFrame.fromAxisAngle to construct a rotation CFrame that we will use to offset the shoulders from their initial positions. If you look at the image with the object space of the right shoulder, you might guess that we need to rotate the arms up and down along the x-axis:

Although this is the axis that we want to rotate around, because we’re playing an animation, the shoulders of the character are offset from their default position:

If we attempt to rotate the right shoulder along the x-axis, it will rotate along the new x vector, instead of the original/default one. We need to find the default x vector in the object space of the current shoulder’s orientation using one of the three methods below:

-- This:
RightShoulder.Transform:ToObjectSpace(CFrame.new()).XVector
-- is the same thing as this:
(RightShoulder.Transform:Inverse() * CFrame.new()).XVector -- multiplying a CFrame by an empty CFrame results in the same CFrame
-- which is the same thing as this:
RightShoulder.Transform:Inverse().XVector

Initially I used the third method when finding the solution but you can use the first method if it’s easier for you to read and understand conceptually. This concludes our calculations. We can now create a rotation CFrame and apply it to the initial CFrames of the shoulders.

-- The animation MUST be playing before this code runs.
-- We need the initial CFrames of the shoulders when the animation is played.
local LSInitialCF = LeftShoulder.Transform
local RSInitialCF = RightShoulder.Transform
rs.PreSimulation:Connect(function()
		local rightShoulderCFrame = bpTable.HumanoidRootPart.CFrame:ToWorldSpace(RightShoulder.C0)
		
		-- Mouse position in the object space of the right shoulder.
		local mouseObj = rightShoulderCFrame:PointToObjectSpace(mo.Hit.Position)
		
		-- Project mouseObj onto the yz-plane by removing mouseObj.X
		local yzProjection = mouseObj * Vector3.new(0, 1, 1) -- equal to Vector3.new(0, mouseObj.Y, mouseObj.Z)
		
		-- Getting the angle between the reference vector 'v' and the yzProjection by
        -- rearranging the dot product formula.
		local _90Degrees = math.pi/2 -- we need this offset because the animation points forward, not down
		local v = Vector3.new(0, -1, 0) -- reference angle
		local angle = math.acos(v:Dot(yzProjection) / (v.Magnitude * yzProjection.Magnitude)) - _90Degrees
		
        -- Finding the axis of rotation.
		local LSRotationAxis = LeftShoulder.Transform:Inverse().XVector
		local RSRotationAxis = RightShoulder.Transform:Inverse().XVector
		-- or you can use RightShoulder.Transform:ToObjectSpace(CFrame.new()).XVector

        -- Apply rotation offset to initial CFrames of the shoulder Motor6Ds
		LeftShoulder.Transform = LSInitialCF * CFrame.fromAxisAngle(LSRotationAxis, angle)
		RightShoulder.Transform = RSInitialCF * CFrame.fromAxisAngle(RSRotationAxis, angle)
	end)

Edit: Updated yzProjection to be simpler. The yz-plane plane is where x = 0, so mouseObj.X will be equal to 0. Instead of using a general projection formula we can simply remove the x-coordinate of mouseObj to align it with the yz-plane.

1 Like

hey man,
thanks a lot for your help honestly that helped me a ton. i started studying about the math of the vectors because i haven’t learnt about them before in a lot of detail. i just have a few questions if u dont mind.

so for this line here, how did u project the mouse onto the plane; like where did you get the formula or what is the formula to be exact?

i vaguely know how to use the dot product method but i can’t seem to understand what’s going on here, if you would be so kind to explain. :slight_smile: :

and also for the reference vector, could we not use the y vector as the reference instead of the inverse y vector or is that not possible because we already used it above?

im starting to understand this quite fine now i would say, so i’m fine on this

for this part i’m not entirely sure, i went over CFrame.fromAxisAngle last week but i don’t really get what it does but i assume this line is rotating it along the x axis of the shoulder…?

nonetheless, i really appreciate the help mate, thanks for spending the time to write all that up to explain it to me. i’m quite new to this so my apologies for my lack of experience with vectors and cframes. :slight_smile:

It’s the orthogonal projection of a vector onto a plane that I learned in my linear algebra class. It’s a standard formula we learned from the textbook, but since we just need to project onto the yz-plane there is a simpler more trivial approach by setting x = 0 of mouseObj.


The formula below is the formula for any two-dimensional plane, in this case the yz-plane. These are the definitions of the variables (all variables are in the object space of the right shoulder):

  • x = mouseObj
  • x_w = projection of mouseObj onto the yz-plane
  • u_1 = y vector (first vector of yz-plane)
  • u_2 = z vector (second vector of yz-plane)
    image

We totally could.

I just learned that Roblox added a method Vector:Angle to get the angle between two vectors. You could use that instead to skip having to type the formula every time.

Pretty much, it’s rotating along the x axis of the shoulder as if it was in its neutral/default CFrame with no animations. Since the animation offsets the x axis of the shoulder, we need to use a custom axis of rotation to rotate the current transform property of the shoulder in its own object space.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.