Issue w/ 3rd Person Arms

I’ve been using this module of mine for many months, but after the end of july/beginning of august, without any edits by me, it had started acting up. I’ve been using cosine law on the shoulder motor to get the rotation, then applying a rotation with a look point to the elbow joint, pointing towards the target point. Gun itself with both target points on it is offsetted from the head. For testing purposes I have not yet adjusted the offset based on the gun’s length.

This behavior only happens on one of two conditions:

-Hip firing before using Aim Down Sights, which is strange given I’m using the same offset before as I am after, and it works fine after. Happens no matter the gun size.

-Sprinting with a pistol, both target points are near each other on the gun but this does not happen in any other case with pistols, exception being the first condition.

I’ve looked through my code and nothing else seems to be interfering with it. I’ve asked multiple scripters before making this post and they had said there was nothing visibly wrong with my code. Tweaking the side lengths have had no effect either, nor did ‘nuking’ the Z component of the shoulder C0. The only thing I can possibly think of doing now is to manually cframe the arms themselves, which could potentially cause a lot of issues and prevent me from applying animations to the fingers.

If it helps any, the character models are set up like R15.

MODULE: https://pastebin.com/zWsxupPf

Examples (2nd has a hinge on the front face for reference)

Edit: This is cosine law for those who are wondering: R15 IK Foot Placement

4 Likes

I put together another version using the cosine example I provided above [ made by @WhoBloxedWho ] yet it still produced the same issue.

https://pastebin.com/KrNan8Lx

I’m pretty lost right now and it’s holding up a demo release.

Do you know what the localized vector is in solveIK when the bug occurs? If localized is colinear with forwardV3 then axis wont be an orthogonal vector. For example:

print(Vector3.new(0, 0, 1):Cross(Vector3.new(0, 0, 1))) --> 0, 0, 0
print(Vector3.new(0, 0, 1):Cross(Vector3.new(0, 0, -1))) --> -0, 0, 0

If this is what is going on, then it explains why the issue appeared without changing the script. It is a rather rare edge case, but can happen if positioning is changed. Even if it isn’t the issue, it should be fixed so it doesn’t create problems in the future. Hopefully this is what was causing it, but if not let me know. I’d also be curious what l1 and l2 represent (including an explanation of UA_Len and LA_Len). I assume they are lengths of different lines, but I’m not sure which.

I’ve printed localized,localizedUnit, and axis, and they print the same vectors when the bug occurs (before ADS) as they do after when the bug does not occur (after ADS).

UA_Len is the length of the upper arm, while LA_Len is the length of the lower arm, and plug in as l1 & l2 respectively. Adjusting these values does not change whether or not the bug occurs.

for the lols and just to cover that possibility I switched a & b when I call solveIK and that did not solve the issue.

Edit: I was offline all yesterday, but a bit more information: Vector3:Cross wasn’t a part of my original spaghetti code, but at this point I’m gonna make sure I rule out all the possibilities.

It’s also important to mention in the first spaghetti module i wrote i include 3 parts visually measuring where the arms should be, and those are always correct

I would start by removing any calls like this from your code:

newsho = CFrame.new(newsho.p, endpos.p)

As we’ve discussed on these forums many times, this constructor does not fully specify a CFrame, and it has erratic behavior if the direction from point A to B is close to +/-(0,1,0). Your shoulder certainly satisfies the condition that is problematic here, it’s almost vertically aligned at times. That constructor is fine for getting a direction vector, but you’re using the potentially-arbitrary orientation component it returns too, which will give you weird bugs for sure.

So what should I be doing in the place of CFrame.new(Vector3, Vector3)?

It depends on what you’re actually trying to solve for. In many cases, explicit cross-products to get your basis vectors is the best approach, and you can feed the components right into the 12-element CFrame constructor. Other times, depending on what information you have to work with, using one of the orientation constuctors plus a translation vector may be the better option. For example, in the specific case I pasted in above, the following line looks like this:

newsho = CFrame.new(newsho.p, endpos.p)
newsho = newsho * CFrame.Angles(math.rad(90),0,0)

The question is, did you really want to compose the 90-degree rotation with the potentially-arbitrary one the CFrame.new(vector,vector) comes up with, or did you actually mean to replace it?

I did want to compose it, so the arm would be rotated upward, otherwise since I’m applying this to the shoulder motor the arm would be pointing down + the rotation, which wouldn’t be what I was aiming for.

Admittedly I’m behind on the terminology, could you elaborate on what you meant by…?

Also I’m still unsure if it being close to Vector3.new(0,1,0) is the full issue, as I’m using the same offsets before and after Aim Down Sights and the bug only occurs before.

I meant building up a CFrame from a function like CFrame.Angles() or CFrame.fromOrientation, and then adding the translation to it by adding a Vector3 (if you want to add a world/parent space translation) or by a post-multiply with a pure translational CFrame (like one made via CFrame.new(positionVector)) if you want a local-space translation.

I don’t understand why you’d want the shoulder rotation in world space multiplied by a 90 degree world-X-axis rotation (isn’t that meant to be a 90-degree bend in shoulder-part local space?). What I think you’re describing would only make sense to me if it was all in local space of the shoulder part (i.e. adding 2 joint angles).

I found myself having to start off re-writing the desired C0 in worldspace, then having to translate it back into objectspace before I could apply it to the motor directly, as I had found applying these rotations in the past in objectspace directly had problematic results, mainly incorrect rotations. Applying the 90 degree fix before or after the translation back into object space to apply it to the motor did not make a difference.

(Edited to clarify the process)

What is the purpose of the boolean input to the Righty function?

To tell the function either to calculate the arms or reset them.

I’ve working on setting up a testing bed to reproduce this, however I’m missing a couple pieces of information. Could you give me a hand?

  1. What is the initial position of rig as stored in ReplicatedStorage?
  2. Is there anything special about the endpart's position that I should know about? Where is it in relation to the rig when you were producing the bug? It looks like it is the endpart's position that determines if the weapon is shot from the hip or not.
  1. I keep both rigs backed up in ReplicatedStorage for Motor6D references.
  • Male HumanoidRootPart Position | -35, 3.677, -75.485
  • Female HumanoidRootPart Position | -35, 3.689, -80.709
  1. endpart is either the LeftHand in the gun model, or the Middle (main part/ RightHand reference) in the gun model. Middle is offset via Motor6D from the character’s head, and a rotation is applied based on where you’re aiming.

Here are the two offsets for the rest position and position when the gun is being fired

m.gunOffset = CFrame.new(0.35, -0.88, -0.85)
m.gunRest = CFrame.new(0.25, -1.2, -0.5) * CFrame.Angles(math.rad(20), math.rad(20),math.rad(-10))

When ADS is true, the following is applied to the offset:

local off = Root.gunOffset
if _G.ads then
 off = CFrame.new(off.X * 0.8,off.Y * 0.7,off.Z * 0.6)
end
Root.Services.TS:Create(gunoffval, TweenInfo.new(0.2),{Value = off}):Play()

Is there any more information important in the backup rigs? You said they are used for Motor6D references, are there important starting values?

Also, what does the following line of code do:

Root.Services.TS:Create(gunoffval, TweenInfo.new(0.2),{Value = off}):Play()

Nah, they’re more like StarterCharacter backups, I just read the C0s off of them as well.

That line calls TweenService:Create() to tween the offset value from where it is to where I want it to be. A function on RenderStepped then applies the offset (with a rotation based on where you’re aiming) to the main Motor6D in Middle

The names gunOffset and off make me think that this CFrame is added onto the gun’s current position to result in the ADS position. Is this the case?