You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用固定肘部旋转的2D IK实现2D骨骼角色枪械瞄准?

Solving Upper Arm Rotation for 2D Gun-to-Mouse IK in Unity

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 using Vector2.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/4 for 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 greenCircleCenter and float 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:

  1. Let theta be the upper arm’s rotation angle (radians, counterclockwise from the X-axis).
  2. The end of the upper arm (start of the lower arm) is:
    Vector2 upperArmEnd = shoulderPos + new Vector2(Mathf.Cos(theta), Mathf.Sin(theta)) * upperArmLength;
    
  3. 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 = targetPos and solve for theta using 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:04:16