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.
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:
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 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.