如何利用固定肘部旋转的2D IK实现2D骨骼角色枪械瞄准?
Alright, let’s break down how to tackle this 2D IK problem for your character’s arm—this is a common setup for top-down or side-scroller shooters, and we can solve it with straight-up trigonometry instead of relying solely on Unity’s built-in IK solvers (though you could combine them later if needed).
First, Define Your Key Parameters
Let’s list out all the fixed values and variables you’ll need to work with:
- Shoulder Position:
Vector2 shoulderPos(fixed world position of your ArmUpper’s root) - Upper Arm Length:
float upperArmLength(calculate this once usingVector2.Distance(shoulderPos, armUpperEndTransform.position)) - Lower Arm Length:
float lowerArmLength(same logic—distance between ArmLower’s start and end (hand) positions) - Fixed Lower Arm Angle:
float fixedLowerArmAngle(radians! This is the static angle between upper and lower arm, e.g.,-Mathf.PI/4for a 45-degree downward tilt for a pistol. Remember Unity’s 2D Y-axis is up, so clockwise angles are negative.) - Target (Mouse) Position:
Vector2 targetPos(convert your mouse screen position to world space first) - Green Circle Constraint:
Vector2 greenCircleCenterandfloat greenCircleRadius(the area your hand must stay within)
Step 1: Build the Trig Equation
The core idea is that we need the lower arm’s direction to point directly at the mouse target. Let’s model this with vectors and trig identities:
- Let
thetabe the upper arm’s rotation angle (radians, counterclockwise from the X-axis). - The end of the upper arm (start of the lower arm) is:
Vector2 upperArmEnd = shoulderPos + new Vector2(Mathf.Cos(theta), Mathf.Sin(theta)) * upperArmLength; - The hand position (end of lower arm) uses the fixed angle relative to the upper arm:
Vector2 handPos = upperArmEnd + new Vector2(Mathf.Cos(theta + fixedLowerArmAngle), Mathf.Sin(theta + fixedLowerArmAngle)) * lowerArmLength;
For the lower arm to point at the target, the vector from upperArmEnd to handPos must align perfectly with the vector from upperArmEnd to targetPos. After simplifying the trigonometry, we end up with an equation we can solve for theta.
Step 2: Solve for Theta (and Handle Constraints)
Here’s the practical code to compute the valid rotation angle, with checks for edge cases and the green circle constraint:
// Calculate vector from shoulder to mouse target Vector2 shoulderToTarget = targetPos - shoulderPos; float distShoulderToTarget = shoulderToTarget.magnitude; // Reference angle: direction from shoulder to target float phi = Mathf.Atan2(shoulderToTarget.y, shoulderToTarget.x); // Calculate the sine term for our equation float sinTerm = (upperArmLength * Mathf.Sin(fixedLowerArmAngle)) / distShoulderToTarget; float targetTheta = 0f; float currentUpperArmAngleRad = Mathf.Deg2Rad * armUpperTransform.rotation.eulerAngles.z; // Check if a valid solution exists (sinTerm can't be outside [-1, 1]) if (Mathf.Abs(sinTerm) > 1f) { // Target is too far/close to reach with the fixed arm angle—use the closest possible rotation targetTheta = phi - fixedLowerArmAngle; } else { // We get two possible solutions (since sine is periodic) float theta1 = phi - fixedLowerArmAngle + Mathf.Asin(sinTerm); float theta2 = phi - fixedLowerArmAngle + Mathf.PI - Mathf.Asin(sinTerm); // Compute hand positions for both solutions Vector2 hand1 = shoulderPos + new Vector2(Mathf.Cos(theta1), Mathf.Sin(theta1)) * upperArmLength + new Vector2(Mathf.Cos(theta1 + fixedLowerArmAngle), Mathf.Sin(theta1 + fixedLowerArmAngle)) * lowerArmLength; Vector2 hand2 = shoulderPos + new Vector2(Mathf.Cos(theta2), Mathf.Sin(theta2)) * upperArmLength + new Vector2(Mathf.Cos(theta2 + fixedLowerArmAngle), Mathf.Sin(theta2 + fixedLowerArmAngle)) * lowerArmLength; // Check which solution keeps the hand in the green circle bool hand1InCircle = Vector2.Distance(hand1, greenCircleCenter) <= greenCircleRadius; bool hand2InCircle = Vector2.Distance(hand2, greenCircleCenter) <= greenCircleRadius; if (hand1InCircle && hand2InCircle) { // Pick the solution closest to the current rotation to avoid sudden jumps targetTheta = Mathf.Abs(theta1 - currentUpperArmAngleRad) < Mathf.Abs(theta2 - currentUpperArmAngleRad) ? theta1 : theta2; } else if (hand1InCircle) { targetTheta = theta1; } else if (hand2InCircle) { targetTheta = theta2; } else { // Neither solution is in the circle—pick the one with the hand closest to the circle targetTheta = Vector2.Distance(hand1, greenCircleCenter) < Vector2.Distance(hand2, greenCircleCenter) ? theta1 : theta2; } } // Optional: Smooth the rotation to avoid jitter targetTheta = Mathf.Lerp(currentUpperArmAngleRad, targetTheta, Time.deltaTime * 8f); // Apply the rotation to the upper arm (convert radians to degrees for Unity's Euler angles) armUpperTransform.rotation = Quaternion.Euler(0, 0, Mathf.Rad2Deg * targetTheta);
Quick Notes for Edge Cases
- If your "lower arm points to target" requirement means the hand itself must be at the target (instead of the lower arm direction), you’ll use the Law of Cosines instead—just let
handPos = targetPosand solve forthetausing the triangle formed by shoulder, upper arm end, and target. - If you want to use Unity’s built-in IK system, you can set the hand’s target position to the calculated
handPos(that fits the green circle) and let Unity handle the bone rotations—but this manual trig approach gives you more control over the fixed lower arm angle.
内容的提问来源于stack exchange,提问作者Niclas

