Search Unity

Flip 2D IK bone?

Discussion in '2D' started by GamerXP, Mar 3, 2020.

  1. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    How do you flip a bone, that is used with 2D Inverse Kinematics + 2D animation packages (with skinning)? If I try to flip image using SpriteRendere - it goes at some weird position instead of flipping along the center of image. If I flip the bone itself with scale - IK starts rotating bone at some weird angles.

    Is it possible to properly flip the bone without making additional flipped images in the source PSB file?
     
  2. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    I tried to fix it myself since packages are actually editable (you need to move package from library to Packages folder, removing version number at the end of folder's name). Not sure if it's the best method, but at least this fixes weird IK rotations if you use negative scale for any transform of the limb.

    LimbSolver2D script:

    Code (CSharp):
    1.  
    2. protected override void DoUpdateIK(List<Vector3> effectorPositions)
    3. {
    4.     Vector3 effectorPosition = effectorPositions[0];
    5.     Transform limb1 = m_Chain.transforms[0];
    6.     Transform limb2 = m_Chain.transforms[1];
    7.    
    8.     float scaleLimb1 = Mathf.Sign(limb1.localScale.x * limb1.localScale.y);
    9.     float scaleLimb2 = Mathf.Sign(limb2.localScale.x * limb2.localScale.y);
    10.    
    11.     Vector2 effectorLocalPosition2D = limb1.InverseTransformPoint(effectorPosition);
    12.  
    13.     if (effectorLocalPosition2D.sqrMagnitude > 0f && Limb.Solve(effectorPosition, m_Lengths, m_Positions, ref m_Angles))
    14.     {
    15.         float flipSign = flip ? -scaleLimb1 : scaleLimb1;
    16.         m_Chain.transforms[0].localRotation *= Quaternion.FromToRotation(Vector3.right, effectorLocalPosition2D * new Vector2(1f, scaleLimb1)) * Quaternion.FromToRotation(m_Chain.transforms[1].localPosition, Vector3.right);
    17.         m_Chain.transforms[0].localRotation *= Quaternion.AngleAxis(flipSign * m_Angles[0], Vector3.forward);
    18.         m_Chain.transforms[1].localRotation *= Quaternion.FromToRotation(Vector3.right, m_Chain.transforms[1].InverseTransformPoint(effectorPosition) * new Vector2(1f, scaleLimb2)) * Quaternion.FromToRotation(m_Chain.transforms[2].localPosition, Vector3.right);
    19.     }
    20. }
    21.  
    edit: 2nd transform of the limb now can also be flipped.
     
    Last edited: Mar 4, 2020
    Disavowed and mochakingup like this.
  3. Disavowed

    Disavowed

    Joined:
    Nov 18, 2014
    Posts:
    1
    Thank you, I have been trying to solve same problem.
    This piece of code should be included in next version for com.unity.2d.animation
     
  4. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    Some time after that, I actually made some more changes to the solver. Mainy, I enabled to smoothly lerp between "flip" states of the leg. At 1 knee faces right, at -1 it faces left, at 0 it's centred (it actually rotates toward camera, but you don't see that in 2D if you're using SortingGroups). I mostly made it so animations blend properly - it was terrible with bool-flips.

    Here, I tried separating it as a different class so you don't need to edit source package to use it. It's not compatible with old one to begin with because flip value works differently.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3.  
    4. namespace UnityEngine.U2D.IK
    5. {
    6.     [Solver2DMenu("Limb (Smooth)")]
    7.     public class LimbSolver2DSmooth : Solver2D
    8.     {
    9.         [SerializeField]
    10.         private IKChain2D m_Chain = new IKChain2D();
    11.        
    12.         [Range(-1f, 1f)]
    13.         [SerializeField]
    14.         private float m_Flip = 1f;
    15.  
    16.         private Vector3[] m_Positions = new Vector3[3];
    17.         private float[] m_Lengths = new float[2];
    18.         private float[] m_Angles = new float[2];
    19.  
    20.         public bool flip
    21.         {
    22.             get { return m_Flip < 0f; }
    23.             set { m_Flip = value? -1f: 1f; }
    24.         }
    25.  
    26.         public float flipFloat
    27.         {
    28.             get => m_Flip;
    29.             set => m_Flip = value;
    30.         }
    31.  
    32.         protected override void OnValidate()
    33.         {
    34.             m_Chain.transformCount = 3;
    35.  
    36.             base.OnValidate();
    37.         }
    38.  
    39.         protected override int GetChainCount()
    40.         {
    41.             return 1;
    42.         }
    43.  
    44.         public override IKChain2D GetChain(int index)
    45.         {
    46.             return m_Chain;
    47.         }
    48.        
    49.         protected override void DoPrepare()
    50.         {
    51.             float[] lengths = m_Chain.lengths;
    52.            
    53.             m_Positions[0] = (Vector2) m_Chain.transforms[0].position;
    54.             m_Positions[1] = (Vector2) m_Chain.transforms[1].position;
    55.             m_Positions[2] = (Vector2) m_Chain.transforms[2].position;
    56.             m_Lengths[0] = lengths[0];
    57.             m_Lengths[1] = lengths[1];
    58.         }
    59.  
    60.         private static float GetFlip( Vector3 _scale )
    61.         {
    62.             return Mathf.Sign(_scale.x * _scale.y);
    63.         }
    64.        
    65.         protected override void DoUpdateIK(List<Vector3> effectorPositions)
    66.         {
    67.             Vector3 effectorPosition = effectorPositions[0];
    68.             Transform limb1 = m_Chain.transforms[0];
    69.             Transform limb2 = m_Chain.transforms[1];
    70.  
    71.             if (!solveFromDefaultPose)
    72.             {
    73.                 limb1.localRotation = Quaternion.identity;
    74.                 limb2.localRotation = Quaternion.identity;
    75.             }
    76.  
    77.             Vector2 effectorLocalPosition2D = limb1.InverseTransformPoint(effectorPosition);
    78.  
    79.             if (effectorLocalPosition2D.sqrMagnitude > 0f && Limb.Solve(effectorPosition, m_Lengths, m_Positions, ref m_Angles))
    80.             {
    81.                 float scaleLimb1 = GetFlip(limb1.localScale);
    82.                 float scaleLimb2 = GetFlip(limb2.localScale);
    83.                
    84.                 float flipSign = m_Flip >= 0f? 1f: -1f;
    85.                 float rotationKoef = m_Flip * flipSign;
    86.                
    87.                 Vector3 fwd = Vector3.Slerp(Vector3.up, Vector3.forward * flipSign, rotationKoef);
    88.  
    89.                 m_Chain.transforms[0].localRotation *= Quaternion.FromToRotation(Vector3.right, effectorLocalPosition2D * new Vector2(1f, scaleLimb1)) * Quaternion.FromToRotation(m_Chain.transforms[1].localPosition, Vector3.right);
    90.                 m_Chain.transforms[0].localRotation *= Quaternion.AngleAxis( m_Angles[0], fwd);
    91.                 if (rotationKoef < 1f)
    92.                     m_Chain.transforms[1].localRotation *= Quaternion.AngleAxis(m_Angles[0] * -2f, fwd);
    93.                 m_Chain.transforms[1].localRotation *= Quaternion.FromToRotation(Vector3.right, m_Chain.transforms[1].InverseTransformPoint(effectorPosition) * new Vector2(1f, scaleLimb2)) * Quaternion.FromToRotation(m_Chain.transforms[2].localPosition, Vector3.right);
    94.             }
    95.         }
    96.     }
    97. }
    98.  
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.U2D.IK;
    3.  
    4. namespace UnityEditor.U2D.IK
    5. {
    6.     /// <summary>
    7.     /// Custom Inspector for LimbSolver2D.
    8.     /// </summary>
    9.     [CustomEditor(typeof(LimbSolver2DSmooth))]
    10.     [CanEditMultipleObjects]
    11.     internal class LimbSolver2DSmoothEditor : Solver2DEditor
    12.     {
    13.         private static class Contents
    14.         {
    15.             public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
    16.             public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
    17.             public static readonly GUIContent flipLabel = new GUIContent("Flip", "Select between the two possible solutions of the solver");
    18.         }
    19.  
    20.         private SerializedProperty m_ChainProperty;
    21.         private SerializedProperty m_FlipProperty;
    22.         private void OnEnable()
    23.         {
    24.             m_ChainProperty = serializedObject.FindProperty("m_Chain");
    25.             m_FlipProperty = serializedObject.FindProperty("m_Flip");
    26.         }
    27.  
    28.         public override void OnInspectorGUI()
    29.         {
    30.             serializedObject.Update();
    31.             EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_EffectorTransform"), Contents.effectorLabel);
    32.             EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_TargetTransform"), Contents.targetLabel);
    33.  
    34.             EditorGUILayout.BeginHorizontal();
    35.             bool isFlipped = m_FlipProperty.floatValue < 0f;
    36.             EditorGUI.BeginChangeCheck();
    37.             isFlipped = EditorGUILayout.Toggle(Contents.flipLabel, isFlipped);
    38.             if (EditorGUI.EndChangeCheck())
    39.             {
    40.                 m_FlipProperty.floatValue = isFlipped ? -1f : 1f;
    41.             }
    42.  
    43.             EditorGUILayout.PropertyField(m_FlipProperty, GUIContent.none);
    44.             EditorGUILayout.EndHorizontal();
    45.  
    46.             DrawCommonSolverInspector();
    47.  
    48.             serializedObject.ApplyModifiedProperties();
    49.         }
    50.     }
    51. }
     
  5. SachinKumar29

    SachinKumar29

    Joined:
    Mar 23, 2023
    Posts:
    1
    To flip bones in 2D inverse kinematics and 2D skinning animation packages, you should use 2D animation components such as Animator or Animation instead of SpriteRenderer.
    Add the Animator component to the bone.
    run the animation in Animator with the main main frames: the start frame and the end frame.
    Set the Scale.x property for the key position frame to 1, and for the end position to -1. This will reflect the bone horizontally.
    Run the animation using the Play() method.
    This approach should correctly flip the bone in the center of its image, without analyzing on the IK. If you are still running into problems with IK, you can purchase your own IK component to run and enable it again after completing the add-on.